From baf6251f2705fea6885f3c41b1a5295aefe29d8f Mon Sep 17 00:00:00 2001 From: "Bjoern A. Zeeb" Date: Sun, 28 Nov 2021 18:35:53 +0000 Subject: [PATCH] iwlwifi: import Intel's iwlwifi/mvm driver. Over the past few months we published multiple snapshots for this Linux derived driver and it has become fairly stable in terms of minimal local changes needed for new updates. The current version is based on iwlwifi-next update at cbaa6aeedee5f92dafa5982eceea2a1f98ce4f7d with the addition of a hand full of files replaced for FreeBSD. Given the lack of full license texts on non-local files this is imported under the draft policy for handling SPDX files (D29226). [1] Do not yet hook this to the build until the remaining compat code is all in. Along with the firmware import this will make publishing the last bits and final testing a lot easier. Sponsored by: The FreeBSD Foundation Approved by: core (imp) [1] (cherry picked from commit bfcc09ddd422c95a1a2e4e794b63ee54c4902398) --- sys/contrib/dev/iwlwifi/cfg/22000.c | 909 +++ sys/contrib/dev/iwlwifi/cfg/7000.c | 298 + sys/contrib/dev/iwlwifi/cfg/8000.c | 157 + sys/contrib/dev/iwlwifi/cfg/9000.c | 192 + sys/contrib/dev/iwlwifi/fw/acpi.h | 305 + sys/contrib/dev/iwlwifi/fw/api/alive.h | 174 + sys/contrib/dev/iwlwifi/fw/api/binding.h | 134 + sys/contrib/dev/iwlwifi/fw/api/cmdhdr.h | 156 + sys/contrib/dev/iwlwifi/fw/api/coex.h | 188 + sys/contrib/dev/iwlwifi/fw/api/commands.h | 617 ++ sys/contrib/dev/iwlwifi/fw/api/config.h | 150 + sys/contrib/dev/iwlwifi/fw/api/context.h | 38 + sys/contrib/dev/iwlwifi/fw/api/d3.h | 698 +++ sys/contrib/dev/iwlwifi/fw/api/datapath.h | 198 + sys/contrib/dev/iwlwifi/fw/api/dbg-tlv.h | 468 ++ sys/contrib/dev/iwlwifi/fw/api/debug.h | 407 ++ sys/contrib/dev/iwlwifi/fw/api/filter.h | 127 + sys/contrib/dev/iwlwifi/fw/api/led.h | 17 + sys/contrib/dev/iwlwifi/fw/api/location.h | 1694 +++++ sys/contrib/dev/iwlwifi/fw/api/mac-cfg.h | 151 + sys/contrib/dev/iwlwifi/fw/api/mac.h | 659 ++ sys/contrib/dev/iwlwifi/fw/api/nvm-reg.h | 505 ++ sys/contrib/dev/iwlwifi/fw/api/offload.h | 70 + sys/contrib/dev/iwlwifi/fw/api/paging.h | 29 + sys/contrib/dev/iwlwifi/fw/api/phy-ctxt.h | 160 + sys/contrib/dev/iwlwifi/fw/api/phy.h | 216 + sys/contrib/dev/iwlwifi/fw/api/power.h | 633 ++ sys/contrib/dev/iwlwifi/fw/api/rfi.h | 60 + sys/contrib/dev/iwlwifi/fw/api/rs.h | 720 +++ sys/contrib/dev/iwlwifi/fw/api/rx.h | 852 +++ sys/contrib/dev/iwlwifi/fw/api/scan.h | 1190 ++++ sys/contrib/dev/iwlwifi/fw/api/sf.h | 82 + sys/contrib/dev/iwlwifi/fw/api/soc.h | 35 + sys/contrib/dev/iwlwifi/fw/api/sta.h | 494 ++ sys/contrib/dev/iwlwifi/fw/api/stats.h | 873 +++ sys/contrib/dev/iwlwifi/fw/api/tdls.h | 161 + sys/contrib/dev/iwlwifi/fw/api/time-event.h | 418 ++ sys/contrib/dev/iwlwifi/fw/api/tx.h | 952 +++ sys/contrib/dev/iwlwifi/fw/api/txq.h | 115 + sys/contrib/dev/iwlwifi/fw/dbg.c | 2936 +++++++++ sys/contrib/dev/iwlwifi/fw/dbg.h | 327 + sys/contrib/dev/iwlwifi/fw/debugfs.h | 17 + sys/contrib/dev/iwlwifi/fw/dump.c | 425 ++ sys/contrib/dev/iwlwifi/fw/error-dump.h | 542 ++ sys/contrib/dev/iwlwifi/fw/file.h | 984 +++ sys/contrib/dev/iwlwifi/fw/img.c | 90 + sys/contrib/dev/iwlwifi/fw/img.h | 282 + sys/contrib/dev/iwlwifi/fw/init.c | 160 + sys/contrib/dev/iwlwifi/fw/notif-wait.c | 135 + sys/contrib/dev/iwlwifi/fw/notif-wait.h | 96 + sys/contrib/dev/iwlwifi/fw/paging.c | 296 + sys/contrib/dev/iwlwifi/fw/pnvm.c | 356 ++ sys/contrib/dev/iwlwifi/fw/pnvm.h | 38 + sys/contrib/dev/iwlwifi/fw/rs.c | 252 + sys/contrib/dev/iwlwifi/fw/runtime.h | 200 + sys/contrib/dev/iwlwifi/fw/smem.c | 114 + sys/contrib/dev/iwlwifi/fw/uefi.c | 262 + sys/contrib/dev/iwlwifi/fw/uefi.h | 45 + sys/contrib/dev/iwlwifi/iwl-agn-hw.h | 59 + sys/contrib/dev/iwlwifi/iwl-config.h | 639 ++ .../dev/iwlwifi/iwl-context-info-gen3.h | 267 + sys/contrib/dev/iwlwifi/iwl-context-info.h | 184 + sys/contrib/dev/iwlwifi/iwl-csr.h | 633 ++ sys/contrib/dev/iwlwifi/iwl-dbg-tlv.c | 1313 ++++ sys/contrib/dev/iwlwifi/iwl-dbg-tlv.h | 74 + sys/contrib/dev/iwlwifi/iwl-debug.c | 163 + sys/contrib/dev/iwlwifi/iwl-debug.h | 193 + sys/contrib/dev/iwlwifi/iwl-devtrace.c | 82 + sys/contrib/dev/iwlwifi/iwl-devtrace.h | 70 + sys/contrib/dev/iwlwifi/iwl-drv.c | 1926 ++++++ sys/contrib/dev/iwlwifi/iwl-drv.h | 92 + sys/contrib/dev/iwlwifi/iwl-eeprom-parse.c | 879 +++ sys/contrib/dev/iwlwifi/iwl-eeprom-parse.h | 88 + sys/contrib/dev/iwlwifi/iwl-eeprom-read.c | 401 ++ sys/contrib/dev/iwlwifi/iwl-eeprom-read.h | 12 + sys/contrib/dev/iwlwifi/iwl-fh.h | 720 +++ sys/contrib/dev/iwlwifi/iwl-io.c | 526 ++ sys/contrib/dev/iwlwifi/iwl-io.h | 102 + sys/contrib/dev/iwlwifi/iwl-modparams.h | 110 + sys/contrib/dev/iwlwifi/iwl-nvm-parse.c | 1756 ++++++ sys/contrib/dev/iwlwifi/iwl-nvm-parse.h | 84 + sys/contrib/dev/iwlwifi/iwl-op-mode.h | 215 + sys/contrib/dev/iwlwifi/iwl-phy-db.c | 440 ++ sys/contrib/dev/iwlwifi/iwl-phy-db.h | 23 + sys/contrib/dev/iwlwifi/iwl-prph.h | 489 ++ sys/contrib/dev/iwlwifi/iwl-scd.h | 84 + sys/contrib/dev/iwlwifi/iwl-trans.c | 253 + sys/contrib/dev/iwlwifi/iwl-trans.h | 1504 +++++ sys/contrib/dev/iwlwifi/mvm/binding.c | 163 + sys/contrib/dev/iwlwifi/mvm/coex.c | 685 +++ sys/contrib/dev/iwlwifi/mvm/constants.h | 126 + sys/contrib/dev/iwlwifi/mvm/ftm-initiator.c | 1368 ++++ sys/contrib/dev/iwlwifi/mvm/ftm-responder.c | 481 ++ sys/contrib/dev/iwlwifi/mvm/fw-api.h | 41 + sys/contrib/dev/iwlwifi/mvm/fw.c | 1739 ++++++ sys/contrib/dev/iwlwifi/mvm/led.c | 119 + sys/contrib/dev/iwlwifi/mvm/mac-ctxt.c | 1639 +++++ sys/contrib/dev/iwlwifi/mvm/mac80211.c | 5474 +++++++++++++++++ sys/contrib/dev/iwlwifi/mvm/mvm.h | 2169 +++++++ sys/contrib/dev/iwlwifi/mvm/nvm.c | 598 ++ sys/contrib/dev/iwlwifi/mvm/offloading.c | 207 + sys/contrib/dev/iwlwifi/mvm/ops.c | 1764 ++++++ sys/contrib/dev/iwlwifi/mvm/phy-ctxt.c | 340 + sys/contrib/dev/iwlwifi/mvm/power.c | 956 +++ sys/contrib/dev/iwlwifi/mvm/quota.c | 298 + sys/contrib/dev/iwlwifi/mvm/rfi.c | 118 + sys/contrib/dev/iwlwifi/mvm/rs-fw.c | 482 ++ sys/contrib/dev/iwlwifi/mvm/rs.c | 92 + sys/contrib/dev/iwlwifi/mvm/rs.h | 89 + sys/contrib/dev/iwlwifi/mvm/rx.c | 916 +++ sys/contrib/dev/iwlwifi/mvm/rxmq.c | 2230 +++++++ sys/contrib/dev/iwlwifi/mvm/scan.c | 3107 ++++++++++ sys/contrib/dev/iwlwifi/mvm/sf.c | 279 + sys/contrib/dev/iwlwifi/mvm/sta.c | 3993 ++++++++++++ sys/contrib/dev/iwlwifi/mvm/sta.h | 549 ++ sys/contrib/dev/iwlwifi/mvm/tdls.c | 681 ++ sys/contrib/dev/iwlwifi/mvm/time-event.c | 1221 ++++ sys/contrib/dev/iwlwifi/mvm/time-event.h | 212 + sys/contrib/dev/iwlwifi/mvm/tt.c | 905 +++ sys/contrib/dev/iwlwifi/mvm/tx.c | 2180 +++++++ sys/contrib/dev/iwlwifi/mvm/utils.c | 1137 ++++ sys/contrib/dev/iwlwifi/pcie/ctxt-info-gen3.c | 342 + sys/contrib/dev/iwlwifi/pcie/ctxt-info.c | 263 + sys/contrib/dev/iwlwifi/pcie/drv.c | 1678 +++++ sys/contrib/dev/iwlwifi/pcie/internal.h | 812 +++ sys/contrib/dev/iwlwifi/pcie/rx.c | 2328 +++++++ sys/contrib/dev/iwlwifi/pcie/trans-gen2.c | 466 ++ sys/contrib/dev/iwlwifi/pcie/trans.c | 3679 +++++++++++ sys/contrib/dev/iwlwifi/pcie/tx-gen2.c | 261 + sys/contrib/dev/iwlwifi/pcie/tx.c | 1639 +++++ sys/contrib/dev/iwlwifi/queue/tx.c | 1843 ++++++ sys/contrib/dev/iwlwifi/queue/tx.h | 184 + sys/modules/iwlwifi/Makefile | 90 + 133 files changed, 86183 insertions(+) create mode 100644 sys/contrib/dev/iwlwifi/cfg/22000.c create mode 100644 sys/contrib/dev/iwlwifi/cfg/7000.c create mode 100644 sys/contrib/dev/iwlwifi/cfg/8000.c create mode 100644 sys/contrib/dev/iwlwifi/cfg/9000.c create mode 100644 sys/contrib/dev/iwlwifi/fw/acpi.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/alive.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/binding.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/cmdhdr.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/coex.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/commands.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/config.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/context.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/d3.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/datapath.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/dbg-tlv.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/debug.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/filter.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/led.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/location.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/mac-cfg.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/mac.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/nvm-reg.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/offload.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/paging.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/phy-ctxt.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/phy.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/power.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/rfi.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/rs.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/rx.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/scan.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/sf.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/soc.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/sta.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/stats.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/tdls.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/time-event.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/tx.h create mode 100644 sys/contrib/dev/iwlwifi/fw/api/txq.h create mode 100644 sys/contrib/dev/iwlwifi/fw/dbg.c create mode 100644 sys/contrib/dev/iwlwifi/fw/dbg.h create mode 100644 sys/contrib/dev/iwlwifi/fw/debugfs.h create mode 100644 sys/contrib/dev/iwlwifi/fw/dump.c create mode 100644 sys/contrib/dev/iwlwifi/fw/error-dump.h create mode 100644 sys/contrib/dev/iwlwifi/fw/file.h create mode 100644 sys/contrib/dev/iwlwifi/fw/img.c create mode 100644 sys/contrib/dev/iwlwifi/fw/img.h create mode 100644 sys/contrib/dev/iwlwifi/fw/init.c create mode 100644 sys/contrib/dev/iwlwifi/fw/notif-wait.c create mode 100644 sys/contrib/dev/iwlwifi/fw/notif-wait.h create mode 100644 sys/contrib/dev/iwlwifi/fw/paging.c create mode 100644 sys/contrib/dev/iwlwifi/fw/pnvm.c create mode 100644 sys/contrib/dev/iwlwifi/fw/pnvm.h create mode 100644 sys/contrib/dev/iwlwifi/fw/rs.c create mode 100644 sys/contrib/dev/iwlwifi/fw/runtime.h create mode 100644 sys/contrib/dev/iwlwifi/fw/smem.c create mode 100644 sys/contrib/dev/iwlwifi/fw/uefi.c create mode 100644 sys/contrib/dev/iwlwifi/fw/uefi.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-agn-hw.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-config.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-context-info-gen3.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-context-info.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-csr.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-dbg-tlv.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-dbg-tlv.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-debug.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-debug.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-devtrace.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-devtrace.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-drv.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-drv.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-eeprom-parse.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-eeprom-parse.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-eeprom-read.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-eeprom-read.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-fh.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-io.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-io.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-modparams.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-nvm-parse.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-nvm-parse.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-op-mode.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-phy-db.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-phy-db.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-prph.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-scd.h create mode 100644 sys/contrib/dev/iwlwifi/iwl-trans.c create mode 100644 sys/contrib/dev/iwlwifi/iwl-trans.h create mode 100644 sys/contrib/dev/iwlwifi/mvm/binding.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/coex.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/constants.h create mode 100644 sys/contrib/dev/iwlwifi/mvm/ftm-initiator.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/ftm-responder.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/fw-api.h create mode 100644 sys/contrib/dev/iwlwifi/mvm/fw.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/led.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/mac-ctxt.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/mac80211.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/mvm.h create mode 100644 sys/contrib/dev/iwlwifi/mvm/nvm.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/offloading.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/ops.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/phy-ctxt.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/power.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/quota.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/rfi.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/rs-fw.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/rs.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/rs.h create mode 100644 sys/contrib/dev/iwlwifi/mvm/rx.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/rxmq.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/scan.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/sf.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/sta.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/sta.h create mode 100644 sys/contrib/dev/iwlwifi/mvm/tdls.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/time-event.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/time-event.h create mode 100644 sys/contrib/dev/iwlwifi/mvm/tt.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/tx.c create mode 100644 sys/contrib/dev/iwlwifi/mvm/utils.c create mode 100644 sys/contrib/dev/iwlwifi/pcie/ctxt-info-gen3.c create mode 100644 sys/contrib/dev/iwlwifi/pcie/ctxt-info.c create mode 100644 sys/contrib/dev/iwlwifi/pcie/drv.c create mode 100644 sys/contrib/dev/iwlwifi/pcie/internal.h create mode 100644 sys/contrib/dev/iwlwifi/pcie/rx.c create mode 100644 sys/contrib/dev/iwlwifi/pcie/trans-gen2.c create mode 100644 sys/contrib/dev/iwlwifi/pcie/trans.c create mode 100644 sys/contrib/dev/iwlwifi/pcie/tx-gen2.c create mode 100644 sys/contrib/dev/iwlwifi/pcie/tx.c create mode 100644 sys/contrib/dev/iwlwifi/queue/tx.c create mode 100644 sys/contrib/dev/iwlwifi/queue/tx.h create mode 100644 sys/modules/iwlwifi/Makefile diff --git a/sys/contrib/dev/iwlwifi/cfg/22000.c b/sys/contrib/dev/iwlwifi/cfg/22000.c new file mode 100644 index 00000000000..1572097bccf --- /dev/null +++ b/sys/contrib/dev/iwlwifi/cfg/22000.c @@ -0,0 +1,909 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#include +#include +#include "iwl-config.h" +#include "iwl-prph.h" + +/* Highest firmware API version supported */ +#define IWL_22000_UCODE_API_MAX 67 + +/* Lowest firmware API version supported */ +#define IWL_22000_UCODE_API_MIN 39 + +/* NVM versions */ +#define IWL_22000_NVM_VERSION 0x0a1d + +/* Memory offsets and lengths */ +#define IWL_22000_DCCM_OFFSET 0x800000 /* LMAC1 */ +#define IWL_22000_DCCM_LEN 0x10000 /* LMAC1 */ +#define IWL_22000_DCCM2_OFFSET 0x880000 +#define IWL_22000_DCCM2_LEN 0x8000 +#define IWL_22000_SMEM_OFFSET 0x400000 +#define IWL_22000_SMEM_LEN 0xD0000 + +#define IWL_QU_B_HR_B_FW_PRE "iwlwifi-Qu-b0-hr-b0-" +#define IWL_QNJ_B_HR_B_FW_PRE "iwlwifi-QuQnj-b0-hr-b0-" +#define IWL_QU_C_HR_B_FW_PRE "iwlwifi-Qu-c0-hr-b0-" +#define IWL_QU_B_JF_B_FW_PRE "iwlwifi-Qu-b0-jf-b0-" +#define IWL_QU_C_JF_B_FW_PRE "iwlwifi-Qu-c0-jf-b0-" +#define IWL_QUZ_A_HR_B_FW_PRE "iwlwifi-QuZ-a0-hr-b0-" +#define IWL_QUZ_A_JF_B_FW_PRE "iwlwifi-QuZ-a0-jf-b0-" +#define IWL_QNJ_B_JF_B_FW_PRE "iwlwifi-QuQnj-b0-jf-b0-" +#define IWL_CC_A_FW_PRE "iwlwifi-cc-a0-" +#define IWL_SO_A_JF_B_FW_PRE "iwlwifi-so-a0-jf-b0-" +#define IWL_SO_A_HR_B_FW_PRE "iwlwifi-so-a0-hr-b0-" +#define IWL_SO_A_GF_A_FW_PRE "iwlwifi-so-a0-gf-a0-" +#define IWL_TY_A_GF_A_FW_PRE "iwlwifi-ty-a0-gf-a0-" +#define IWL_SO_A_GF4_A_FW_PRE "iwlwifi-so-a0-gf4-a0-" +#define IWL_SNJ_A_GF4_A_FW_PRE "iwlwifi-SoSnj-a0-gf4-a0-" +#define IWL_SNJ_A_GF_A_FW_PRE "iwlwifi-SoSnj-a0-gf-a0-" +#define IWL_SNJ_A_HR_B_FW_PRE "iwlwifi-SoSnj-a0-hr-b0-" +#define IWL_SNJ_A_JF_B_FW_PRE "iwlwifi-SoSnj-a0-jf-b0-" +#define IWL_MA_A_HR_B_FW_PRE "iwlwifi-ma-a0-hr-b0-" +#define IWL_MA_A_GF_A_FW_PRE "iwlwifi-ma-a0-gf-a0-" +#define IWL_MA_A_GF4_A_FW_PRE "iwlwifi-ma-a0-gf4-a0-" +#define IWL_MA_A_MR_A_FW_PRE "iwlwifi-ma-a0-mr-a0-" +#define IWL_MA_A_FM_A_FW_PRE "iwlwifi-ma-a0-fm-a0-" +#define IWL_SNJ_A_MR_A_FW_PRE "iwlwifi-SoSnj-a0-mr-a0-" +#define IWL_BZ_A_HR_B_FW_PRE "iwlwifi-bz-a0-hr-b0-" +#define IWL_BZ_A_GF_A_FW_PRE "iwlwifi-bz-a0-gf-a0-" +#define IWL_BZ_A_GF4_A_FW_PRE "iwlwifi-bz-a0-gf4-a0-" +#define IWL_BZ_A_MR_A_FW_PRE "iwlwifi-bz-a0-mr-a0-" +#define IWL_BZ_A_FM_A_FW_PRE "iwlwifi-bz-a0-fm-a0-" +#define IWL_GL_A_FM_A_FW_PRE "iwlwifi-gl-a0-fm7-a0-" + + +#define IWL_QU_B_HR_B_MODULE_FIRMWARE(api) \ + IWL_QU_B_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QNJ_B_HR_B_MODULE_FIRMWARE(api) \ + IWL_QNJ_B_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QUZ_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_QUZ_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QUZ_A_JF_B_MODULE_FIRMWARE(api) \ + IWL_QUZ_A_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QU_C_HR_B_MODULE_FIRMWARE(api) \ + IWL_QU_C_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QU_B_JF_B_MODULE_FIRMWARE(api) \ + IWL_QU_B_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QNJ_B_JF_B_MODULE_FIRMWARE(api) \ + IWL_QNJ_B_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_CC_A_MODULE_FIRMWARE(api) \ + IWL_CC_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SO_A_JF_B_MODULE_FIRMWARE(api) \ + IWL_SO_A_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_SO_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_SO_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_SO_A_GF_A_MODULE_FIRMWARE(api) \ + IWL_SO_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_TY_A_GF_A_MODULE_FIRMWARE(api) \ + IWL_TY_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_GF4_A_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_GF4_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_GF_A_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_JF_B_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_HR_B_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_GF_A_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_GF4_A_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_GF4_A_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_MR_A_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_MR_A_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_FM_A_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_FM_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_MR_A_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_MR_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_BZ_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_GF_A_MODULE_FIRMWARE(api) \ + IWL_BZ_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_GF4_A_MODULE_FIRMWARE(api) \ + IWL_BZ_A_GF4_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_MR_A_MODULE_FIRMWARE(api) \ + IWL_BZ_A_MR_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_FM_A_MODULE_FIRMWARE(api) \ + IWL_BZ_A_FM_A_FW_PRE __stringify(api) ".ucode" +#define IWL_GL_A_FM_A_MODULE_FIRMWARE(api) \ + IWL_GL_A_FM_A_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl_22000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, + .num_of_queues = 512, + .max_tfd_queue_size = 256, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, +}; + +static const struct iwl_base_params iwl_ax210_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, + .num_of_queues = 512, + .max_tfd_queue_size = 65536, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, +}; + +static const struct iwl_ht_params iwl_22000_ht_params = { + .stbc = true, + .ldpc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ) | + BIT(NL80211_BAND_6GHZ), +}; + +#define IWL_DEVICE_22000_COMMON \ + .ucode_api_max = IWL_22000_UCODE_API_MAX, \ + .ucode_api_min = IWL_22000_UCODE_API_MIN, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 10, \ + .non_shared_ant = ANT_B, \ + .dccm_offset = IWL_22000_DCCM_OFFSET, \ + .dccm_len = IWL_22000_DCCM_LEN, \ + .dccm2_offset = IWL_22000_DCCM2_OFFSET, \ + .dccm2_len = IWL_22000_DCCM2_LEN, \ + .smem_offset = IWL_22000_SMEM_OFFSET, \ + .smem_len = IWL_22000_SMEM_LEN, \ + .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, \ + .apmg_not_supported = true, \ + .trans.mq_rx_supported = true, \ + .vht_mu_mimo_supported = true, \ + .mac_addr_from_csr = 0x380, \ + .ht_params = &iwl_22000_ht_params, \ + .nvm_ver = IWL_22000_NVM_VERSION, \ + .trans.use_tfh = true, \ + .trans.rf_id = true, \ + .trans.gen2 = true, \ + .nvm_type = IWL_NVM_EXT, \ + .dbgc_supported = true, \ + .min_umac_error_event_table = 0x400000, \ + .d3_debug_data_base_addr = 0x401000, \ + .d3_debug_data_length = 60 * 1024, \ + .mon_smem_regs = { \ + .write_ptr = { \ + .addr = LDBG_M2S_BUF_WPTR, \ + .mask = LDBG_M2S_BUF_WPTR_VAL_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = LDBG_M2S_BUF_WRAP_CNT, \ + .mask = LDBG_M2S_BUF_WRAP_CNT_VAL_MSK, \ + }, \ + } + +#define IWL_DEVICE_22500 \ + IWL_DEVICE_22000_COMMON, \ + .trans.device_family = IWL_DEVICE_FAMILY_22000, \ + .trans.base_params = &iwl_22000_base_params, \ + .gp2_reg_addr = 0xa02c68, \ + .mon_dram_regs = { \ + .write_ptr = { \ + .addr = MON_BUFF_WRPTR_VER2, \ + .mask = 0xffffffff, \ + }, \ + .cycle_cnt = { \ + .addr = MON_BUFF_CYCLE_CNT_VER2, \ + .mask = 0xffffffff, \ + }, \ + } + +#define IWL_DEVICE_AX210 \ + IWL_DEVICE_22000_COMMON, \ + .trans.umac_prph_offset = 0x300000, \ + .trans.device_family = IWL_DEVICE_FAMILY_AX210, \ + .trans.base_params = &iwl_ax210_base_params, \ + .min_txq_size = 128, \ + .gp2_reg_addr = 0xd02c68, \ + .min_256_ba_txq_size = 1024, \ + .mon_dram_regs = { \ + .write_ptr = { \ + .addr = DBGC_CUR_DBGBUF_STATUS, \ + .mask = DBGC_CUR_DBGBUF_STATUS_OFFSET_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = DBGC_DBGBUF_WRAP_AROUND, \ + .mask = 0xffffffff, \ + }, \ + .cur_frag = { \ + .addr = DBGC_CUR_DBGBUF_STATUS, \ + .mask = DBGC_CUR_DBGBUF_STATUS_IDX_MSK, \ + }, \ + } + +#define IWL_DEVICE_BZ_COMMON \ + .ucode_api_max = IWL_22000_UCODE_API_MAX, \ + .ucode_api_min = IWL_22000_UCODE_API_MIN, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 10, \ + .non_shared_ant = ANT_B, \ + .dccm_offset = IWL_22000_DCCM_OFFSET, \ + .dccm_len = IWL_22000_DCCM_LEN, \ + .dccm2_offset = IWL_22000_DCCM2_OFFSET, \ + .dccm2_len = IWL_22000_DCCM2_LEN, \ + .smem_offset = IWL_22000_SMEM_OFFSET, \ + .smem_len = IWL_22000_SMEM_LEN, \ + .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, \ + .apmg_not_supported = true, \ + .trans.mq_rx_supported = true, \ + .vht_mu_mimo_supported = true, \ + .mac_addr_from_csr = 0x30, \ + .ht_params = &iwl_22000_ht_params, \ + .nvm_ver = IWL_22000_NVM_VERSION, \ + .trans.use_tfh = true, \ + .trans.rf_id = true, \ + .trans.gen2 = true, \ + .nvm_type = IWL_NVM_EXT, \ + .dbgc_supported = true, \ + .min_umac_error_event_table = 0x400000, \ + .d3_debug_data_base_addr = 0x401000, \ + .d3_debug_data_length = 60 * 1024, \ + .mon_smem_regs = { \ + .write_ptr = { \ + .addr = LDBG_M2S_BUF_WPTR, \ + .mask = LDBG_M2S_BUF_WPTR_VAL_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = LDBG_M2S_BUF_WRAP_CNT, \ + .mask = LDBG_M2S_BUF_WRAP_CNT_VAL_MSK, \ + }, \ + } + +#define IWL_DEVICE_BZ \ + IWL_DEVICE_BZ_COMMON, \ + .trans.umac_prph_offset = 0x300000, \ + .trans.device_family = IWL_DEVICE_FAMILY_BZ, \ + .trans.base_params = &iwl_ax210_base_params, \ + .min_txq_size = 128, \ + .gp2_reg_addr = 0xd02c68, \ + .min_256_ba_txq_size = 1024, \ + .mon_dram_regs = { \ + .write_ptr = { \ + .addr = DBGC_CUR_DBGBUF_STATUS, \ + .mask = DBGC_CUR_DBGBUF_STATUS_OFFSET_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = DBGC_DBGBUF_WRAP_AROUND, \ + .mask = 0xffffffff, \ + }, \ + .cur_frag = { \ + .addr = DBGC_CUR_DBGBUF_STATUS, \ + .mask = DBGC_CUR_DBGBUF_STATUS_IDX_MSK, \ + }, \ + } + +const struct iwl_cfg_trans_params iwl_qnj_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, +}; + +const struct iwl_cfg_trans_params iwl_qu_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, + .integrated = true, + .xtal_latency = 500, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_200US, +}; + +const struct iwl_cfg_trans_params iwl_qu_medium_latency_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, + .integrated = true, + .xtal_latency = 1820, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_1820US, +}; + +const struct iwl_cfg_trans_params iwl_qu_long_latency_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, + .integrated = true, + .xtal_latency = 12000, + .low_latency_xtal = true, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_2500US, +}; + +const struct iwl_cfg_trans_params iwl_snj_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_AX210, + .base_params = &iwl_ax210_base_params, + .umac_prph_offset = 0x300000, +}; + +const struct iwl_cfg_trans_params iwl_so_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_AX210, + .base_params = &iwl_ax210_base_params, + .umac_prph_offset = 0x300000, + .integrated = true, + /* TODO: the following values need to be checked */ + .xtal_latency = 500, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_200US, +}; + +const struct iwl_cfg_trans_params iwl_so_long_latency_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_AX210, + .base_params = &iwl_ax210_base_params, + .umac_prph_offset = 0x300000, + .integrated = true, + .low_latency_xtal = true, + .xtal_latency = 12000, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_2500US, +}; + +/* + * If the device doesn't support HE, no need to have that many buffers. + * 22000 devices can split multiple frames into a single RB, so fewer are + * needed; AX210 cannot (but use smaller RBs by default) - these sizes + * were picked according to 8 MSDUs inside 256 A-MSDUs in an A-MPDU, with + * additional overhead to account for processing time. + */ +#define IWL_NUM_RBDS_NON_HE 512 +#define IWL_NUM_RBDS_22000_HE 2048 +#define IWL_NUM_RBDS_AX210_HE 4096 + +/* + * All JF radio modules are part of the 9000 series, but the MAC part + * looks more like 22000. That's why this device is here, but called + * 9560 nevertheless. + */ +const struct iwl_cfg iwl9560_qu_b0_jf_b0_cfg = { + .fw_name_pre = IWL_QU_B_JF_B_FW_PRE, + IWL_DEVICE_22500, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg iwl9560_qu_c0_jf_b0_cfg = { + .fw_name_pre = IWL_QU_C_JF_B_FW_PRE, + IWL_DEVICE_22500, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg iwl9560_quz_a0_jf_b0_cfg = { + .fw_name_pre = IWL_QUZ_A_JF_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg iwl9560_qnj_b0_jf_b0_cfg = { + .fw_name_pre = IWL_QNJ_B_JF_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg_trans_params iwl_ax200_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .bisr_workaround = 1, +}; + +const struct iwl_cfg_trans_params iwl_ma_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_AX210, + .base_params = &iwl_ax210_base_params, + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .integrated = true, + .umac_prph_offset = 0x300000 +}; + +const struct iwl_cfg_trans_params iwl_bz_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_BZ, + .base_params = &iwl_ax210_base_params, + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .integrated = true, + .umac_prph_offset = 0x300000, + .xtal_latency = 12000, + .low_latency_xtal = true, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_2500US, +}; + +const char iwl_ax101_name[] = "Intel(R) Wi-Fi 6 AX101"; +const char iwl_ax200_name[] = "Intel(R) Wi-Fi 6 AX200 160MHz"; +const char iwl_ax201_name[] = "Intel(R) Wi-Fi 6 AX201 160MHz"; +const char iwl_ax203_name[] = "Intel(R) Wi-Fi 6 AX203"; +const char iwl_ax211_name[] = "Intel(R) Wi-Fi 6E AX211 160MHz"; +const char iwl_ax221_name[] = "Intel(R) Wi-Fi 6E AX221 160MHz"; +const char iwl_ax231_name[] = "Intel(R) Wi-Fi 6E AX231 160MHz"; +const char iwl_ax411_name[] = "Intel(R) Wi-Fi 6E AX411 160MHz"; +const char iwl_bz_name[] = "Intel(R) TBD Bz device"; + +const char iwl_ax200_killer_1650w_name[] = + "Killer(R) Wi-Fi 6 AX1650w 160MHz Wireless Network Adapter (200D2W)"; +const char iwl_ax200_killer_1650x_name[] = + "Killer(R) Wi-Fi 6 AX1650x 160MHz Wireless Network Adapter (200NGW)"; +const char iwl_ax201_killer_1650s_name[] = + "Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201D2W)"; +const char iwl_ax201_killer_1650i_name[] = + "Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201NGW)"; +const char iwl_ax210_killer_1675w_name[] = + "Killer(R) Wi-Fi 6E AX1675w 160MHz Wireless Network Adapter (210D2W)"; +const char iwl_ax210_killer_1675x_name[] = + "Killer(R) Wi-Fi 6E AX1675x 160MHz Wireless Network Adapter (210NGW)"; +const char iwl_ax211_killer_1675s_name[] = + "Killer(R) Wi-Fi 6E AX1675s 160MHz Wireless Network Adapter (211NGW)"; +const char iwl_ax211_killer_1675i_name[] = + "Killer(R) Wi-Fi 6E AX1675i 160MHz Wireless Network Adapter (211NGW)"; +const char iwl_ax411_killer_1690s_name[] = + "Killer(R) Wi-Fi 6E AX1690s 160MHz Wireless Network Adapter (411D2W)"; +const char iwl_ax411_killer_1690i_name[] = + "Killer(R) Wi-Fi 6E AX1690i 160MHz Wireless Network Adapter (411NGW)"; + +const struct iwl_cfg iwl_qu_b0_hr1_b0 = { + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .tx_with_siso_diversity = true, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_qu_b0_hr_b0 = { + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax201_cfg_qu_hr = { + .name = "Intel(R) Wi-Fi 6 AX201 160MHz", + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_qu_c0_hr1_b0 = { + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .tx_with_siso_diversity = true, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_qu_c0_hr_b0 = { + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax201_cfg_qu_c0_hr_b0 = { + .name = "Intel(R) Wi-Fi 6 AX201 160MHz", + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_quz_a0_hr1_b0 = { + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .tx_with_siso_diversity = true, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax201_cfg_quz_hr = { + .name = "Intel(R) Wi-Fi 6 AX201 160MHz", + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax1650s_cfg_quz_hr = { + .name = "Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201D2W)", + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax1650i_cfg_quz_hr = { + .name = "Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201NGW)", + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax200_cfg_cc = { + .fw_name_pre = IWL_CC_A_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg killer1650s_2ax_cfg_qu_b0_hr_b0 = { + .name = "Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201NGW)", + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg killer1650i_2ax_cfg_qu_b0_hr_b0 = { + .name = "Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201D2W)", + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg killer1650s_2ax_cfg_qu_c0_hr_b0 = { + .name = "Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201NGW)", + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg killer1650i_2ax_cfg_qu_c0_hr_b0 = { + .name = "Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201D2W)", + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_qnj_b0_hr_b0_cfg = { + .fw_name_pre = IWL_QNJ_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwlax210_2ax_cfg_so_jf_b0 = { + .name = "Intel(R) Wireless-AC 9560 160MHz", + .fw_name_pre = IWL_SO_A_JF_B_FW_PRE, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg iwlax210_2ax_cfg_so_hr_a0 = { + .name = "Intel(R) Wi-Fi 6 AX210 160MHz", + .fw_name_pre = IWL_SO_A_HR_B_FW_PRE, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwlax211_2ax_cfg_so_gf_a0 = { + .name = iwl_ax211_name, + .fw_name_pre = IWL_SO_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwlax211_2ax_cfg_so_gf_a0_long = { + .name = iwl_ax211_name, + .fw_name_pre = IWL_SO_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, + .trans.xtal_latency = 12000, + .trans.low_latency_xtal = true, +}; + +const struct iwl_cfg iwlax210_2ax_cfg_ty_gf_a0 = { + .name = "Intel(R) Wi-Fi 6 AX210 160MHz", + .fw_name_pre = IWL_TY_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwlax411_2ax_cfg_so_gf4_a0 = { + .name = iwl_ax411_name, + .fw_name_pre = IWL_SO_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwlax411_2ax_cfg_so_gf4_a0_long = { + .name = iwl_ax411_name, + .fw_name_pre = IWL_SO_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, + .trans.xtal_latency = 12000, + .trans.low_latency_xtal = true, +}; + +const struct iwl_cfg iwlax411_2ax_cfg_sosnj_gf4_a0 = { + .name = iwl_ax411_name, + .fw_name_pre = IWL_SNJ_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwlax211_cfg_snj_gf_a0 = { + .name = iwl_ax211_name, + .fw_name_pre = IWL_SNJ_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_snj_hr_b0 = { + .fw_name_pre = IWL_SNJ_A_HR_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_snj_a0_jf_b0 = { + .fw_name_pre = IWL_SNJ_A_JF_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_hr_b0 = { + .fw_name_pre = IWL_MA_A_HR_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_gf_a0 = { + .fw_name_pre = IWL_MA_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_gf4_a0 = { + .fw_name_pre = IWL_MA_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_mr_a0 = { + .fw_name_pre = IWL_MA_A_MR_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_fm_a0 = { + .fw_name_pre = IWL_MA_A_FM_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_snj_a0_mr_a0 = { + .fw_name_pre = IWL_SNJ_A_MR_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_so_a0_hr_a0 = { + .fw_name_pre = IWL_SO_A_HR_B_FW_PRE, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_quz_a0_hr_b0 = { + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_hr_b0 = { + .fw_name_pre = IWL_BZ_A_HR_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_gf_a0 = { + .fw_name_pre = IWL_BZ_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_gf4_a0 = { + .fw_name_pre = IWL_BZ_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_mr_a0 = { + .fw_name_pre = IWL_BZ_A_MR_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_fm_a0 = { + .fw_name_pre = IWL_BZ_A_FM_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_gl_a0_fm_a0 = { + .fw_name_pre = IWL_GL_A_FM_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +MODULE_FIRMWARE(IWL_QU_B_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QNJ_B_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QU_C_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QU_B_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QUZ_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QUZ_A_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QNJ_B_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_CC_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SO_A_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SO_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SO_A_GF_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_TY_A_GF_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_GF4_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_GF_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_HR_B_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_GF_A_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_GF4_A_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_MR_A_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_FM_A_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_MR_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_GF_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_GF4_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_MR_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_FM_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_GL_A_FM_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); diff --git a/sys/contrib/dev/iwlwifi/cfg/7000.c b/sys/contrib/dev/iwlwifi/cfg/7000.c new file mode 100644 index 00000000000..b24dc5523a5 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/cfg/7000.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015 Intel Deutschland GmbH + */ +#include +#include +#include "iwl-config.h" + +/* Highest firmware API version supported */ +#define IWL7260_UCODE_API_MAX 17 +#define IWL7265_UCODE_API_MAX 17 +#define IWL7265D_UCODE_API_MAX 29 +#define IWL3168_UCODE_API_MAX 29 + +/* Lowest firmware API version supported */ +#define IWL7260_UCODE_API_MIN 17 +#define IWL7265_UCODE_API_MIN 17 +#define IWL7265D_UCODE_API_MIN 22 +#define IWL3168_UCODE_API_MIN 22 + +/* NVM versions */ +#define IWL7260_NVM_VERSION 0x0a1d +#define IWL3160_NVM_VERSION 0x709 +#define IWL3165_NVM_VERSION 0x709 +#define IWL3168_NVM_VERSION 0xd01 +#define IWL7265_NVM_VERSION 0x0a1d +#define IWL7265D_NVM_VERSION 0x0c11 + +/* DCCM offsets and lengths */ +#define IWL7000_DCCM_OFFSET 0x800000 +#define IWL7260_DCCM_LEN 0x14000 +#define IWL3160_DCCM_LEN 0x10000 +#define IWL7265_DCCM_LEN 0x17A00 + +#define IWL7260_FW_PRE "iwlwifi-7260-" +#define IWL7260_MODULE_FIRMWARE(api) IWL7260_FW_PRE __stringify(api) ".ucode" + +#define IWL3160_FW_PRE "iwlwifi-3160-" +#define IWL3160_MODULE_FIRMWARE(api) IWL3160_FW_PRE __stringify(api) ".ucode" + +#define IWL3168_FW_PRE "iwlwifi-3168-" +#define IWL3168_MODULE_FIRMWARE(api) IWL3168_FW_PRE __stringify(api) ".ucode" + +#define IWL7265_FW_PRE "iwlwifi-7265-" +#define IWL7265_MODULE_FIRMWARE(api) IWL7265_FW_PRE __stringify(api) ".ucode" + +#define IWL7265D_FW_PRE "iwlwifi-7265D-" +#define IWL7265D_MODULE_FIRMWARE(api) IWL7265D_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl7000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_16K, + .num_of_queues = 31, + .max_tfd_queue_size = 256, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, + .apmg_wake_up_wa = true, +}; + +static const struct iwl_tt_params iwl7000_high_temp_tt_params = { + .ct_kill_entry = 118, + .ct_kill_exit = 96, + .ct_kill_duration = 5, + .dynamic_smps_entry = 114, + .dynamic_smps_exit = 110, + .tx_protection_entry = 114, + .tx_protection_exit = 108, + .tx_backoff = { + {.temperature = 112, .backoff = 300}, + {.temperature = 113, .backoff = 800}, + {.temperature = 114, .backoff = 1500}, + {.temperature = 115, .backoff = 3000}, + {.temperature = 116, .backoff = 5000}, + {.temperature = 117, .backoff = 10000}, + }, + .support_ct_kill = true, + .support_dynamic_smps = true, + .support_tx_protection = true, + .support_tx_backoff = true, +}; + +static const struct iwl_ht_params iwl7000_ht_params = { + .stbc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +#define IWL_DEVICE_7000_COMMON \ + .trans.device_family = IWL_DEVICE_FAMILY_7000, \ + .trans.base_params = &iwl7000_base_params, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 0, \ + .non_shared_ant = ANT_A, \ + .dccm_offset = IWL7000_DCCM_OFFSET + +#define IWL_DEVICE_7000 \ + IWL_DEVICE_7000_COMMON, \ + .ucode_api_max = IWL7260_UCODE_API_MAX, \ + .ucode_api_min = IWL7260_UCODE_API_MIN + +#define IWL_DEVICE_7005 \ + IWL_DEVICE_7000_COMMON, \ + .ucode_api_max = IWL7265_UCODE_API_MAX, \ + .ucode_api_min = IWL7265_UCODE_API_MIN + +#define IWL_DEVICE_3008 \ + IWL_DEVICE_7000_COMMON, \ + .ucode_api_max = IWL3168_UCODE_API_MAX, \ + .ucode_api_min = IWL3168_UCODE_API_MIN + +#define IWL_DEVICE_7005D \ + IWL_DEVICE_7000_COMMON, \ + .ucode_api_max = IWL7265D_UCODE_API_MAX, \ + .ucode_api_min = IWL7265D_UCODE_API_MIN + +const struct iwl_cfg iwl7260_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 7260", + .fw_name_pre = IWL7260_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL7260_NVM_VERSION, + .host_interrupt_operation_mode = true, + .lp_xtal_workaround = true, + .dccm_len = IWL7260_DCCM_LEN, +}; + +const struct iwl_cfg iwl7260_2ac_cfg_high_temp = { + .name = "Intel(R) Dual Band Wireless AC 7260", + .fw_name_pre = IWL7260_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL7260_NVM_VERSION, + .high_temp = true, + .host_interrupt_operation_mode = true, + .lp_xtal_workaround = true, + .dccm_len = IWL7260_DCCM_LEN, + .thermal_params = &iwl7000_high_temp_tt_params, +}; + +const struct iwl_cfg iwl7260_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 7260", + .fw_name_pre = IWL7260_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL7260_NVM_VERSION, + .host_interrupt_operation_mode = true, + .lp_xtal_workaround = true, + .dccm_len = IWL7260_DCCM_LEN, +}; + +const struct iwl_cfg iwl7260_n_cfg = { + .name = "Intel(R) Wireless N 7260", + .fw_name_pre = IWL7260_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL7260_NVM_VERSION, + .host_interrupt_operation_mode = true, + .lp_xtal_workaround = true, + .dccm_len = IWL7260_DCCM_LEN, +}; + +const struct iwl_cfg iwl3160_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 3160", + .fw_name_pre = IWL3160_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3160_NVM_VERSION, + .host_interrupt_operation_mode = true, + .dccm_len = IWL3160_DCCM_LEN, +}; + +const struct iwl_cfg iwl3160_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 3160", + .fw_name_pre = IWL3160_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3160_NVM_VERSION, + .host_interrupt_operation_mode = true, + .dccm_len = IWL3160_DCCM_LEN, +}; + +const struct iwl_cfg iwl3160_n_cfg = { + .name = "Intel(R) Wireless N 3160", + .fw_name_pre = IWL3160_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3160_NVM_VERSION, + .host_interrupt_operation_mode = true, + .dccm_len = IWL3160_DCCM_LEN, +}; + +static const struct iwl_pwr_tx_backoff iwl7265_pwr_tx_backoffs[] = { + {.pwr = 1600, .backoff = 0}, + {.pwr = 1300, .backoff = 467}, + {.pwr = 900, .backoff = 1900}, + {.pwr = 800, .backoff = 2630}, + {.pwr = 700, .backoff = 3720}, + {.pwr = 600, .backoff = 5550}, + {.pwr = 500, .backoff = 9350}, + {0}, +}; + +static const struct iwl_ht_params iwl7265_ht_params = { + .stbc = true, + .ldpc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +const struct iwl_cfg iwl3165_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 3165", + .fw_name_pre = IWL7265D_FW_PRE, + IWL_DEVICE_7005D, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3165_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl3168_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 3168", + .fw_name_pre = IWL3168_FW_PRE, + IWL_DEVICE_3008, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3168_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, + .nvm_type = IWL_NVM_SDP, +}; + +const struct iwl_cfg iwl7265_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 7265", + .fw_name_pre = IWL7265_FW_PRE, + IWL_DEVICE_7005, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 7265", + .fw_name_pre = IWL7265_FW_PRE, + IWL_DEVICE_7005, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265_n_cfg = { + .name = "Intel(R) Wireless N 7265", + .fw_name_pre = IWL7265_FW_PRE, + IWL_DEVICE_7005, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265d_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 7265", + .fw_name_pre = IWL7265D_FW_PRE, + IWL_DEVICE_7005D, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265D_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265d_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 7265", + .fw_name_pre = IWL7265D_FW_PRE, + IWL_DEVICE_7005D, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265D_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265d_n_cfg = { + .name = "Intel(R) Wireless N 7265", + .fw_name_pre = IWL7265D_FW_PRE, + IWL_DEVICE_7005D, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265D_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +MODULE_FIRMWARE(IWL7260_MODULE_FIRMWARE(IWL7260_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL3160_MODULE_FIRMWARE(IWL7260_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL3168_MODULE_FIRMWARE(IWL3168_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL7265_MODULE_FIRMWARE(IWL7265_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL7265D_MODULE_FIRMWARE(IWL7265D_UCODE_API_MAX)); diff --git a/sys/contrib/dev/iwlwifi/cfg/8000.c b/sys/contrib/dev/iwlwifi/cfg/8000.c new file mode 100644 index 00000000000..a6454287d50 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/cfg/8000.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2014, 2018-2020 Intel Corporation + * Copyright (C) 2014-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#include +#include +#include "iwl-config.h" + +/* Highest firmware API version supported */ +#define IWL8000_UCODE_API_MAX 36 +#define IWL8265_UCODE_API_MAX 36 + +/* Lowest firmware API version supported */ +#define IWL8000_UCODE_API_MIN 22 +#define IWL8265_UCODE_API_MIN 22 + +/* NVM versions */ +#define IWL8000_NVM_VERSION 0x0a1d + +/* Memory offsets and lengths */ +#define IWL8260_DCCM_OFFSET 0x800000 +#define IWL8260_DCCM_LEN 0x18000 +#define IWL8260_DCCM2_OFFSET 0x880000 +#define IWL8260_DCCM2_LEN 0x8000 +#define IWL8260_SMEM_OFFSET 0x400000 +#define IWL8260_SMEM_LEN 0x68000 + +#define IWL8000_FW_PRE "iwlwifi-8000C-" +#define IWL8000_MODULE_FIRMWARE(api) \ + IWL8000_FW_PRE __stringify(api) ".ucode" + +#define IWL8265_FW_PRE "iwlwifi-8265-" +#define IWL8265_MODULE_FIRMWARE(api) \ + IWL8265_FW_PRE __stringify(api) ".ucode" + +#define DEFAULT_NVM_FILE_FAMILY_8000C "nvmData-8000C" + +static const struct iwl_base_params iwl8000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, + .num_of_queues = 31, + .max_tfd_queue_size = 256, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, +}; + +static const struct iwl_ht_params iwl8000_ht_params = { + .stbc = true, + .ldpc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +static const struct iwl_tt_params iwl8000_tt_params = { + .ct_kill_entry = 115, + .ct_kill_exit = 93, + .ct_kill_duration = 5, + .dynamic_smps_entry = 111, + .dynamic_smps_exit = 107, + .tx_protection_entry = 112, + .tx_protection_exit = 105, + .tx_backoff = { + {.temperature = 110, .backoff = 200}, + {.temperature = 111, .backoff = 600}, + {.temperature = 112, .backoff = 1200}, + {.temperature = 113, .backoff = 2000}, + {.temperature = 114, .backoff = 4000}, + }, + .support_ct_kill = true, + .support_dynamic_smps = true, + .support_tx_protection = true, + .support_tx_backoff = true, +}; + +#define IWL_DEVICE_8000_COMMON \ + .trans.device_family = IWL_DEVICE_FAMILY_8000, \ + .trans.base_params = &iwl8000_base_params, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 10, \ + .features = NETIF_F_RXCSUM, \ + .non_shared_ant = ANT_A, \ + .dccm_offset = IWL8260_DCCM_OFFSET, \ + .dccm_len = IWL8260_DCCM_LEN, \ + .dccm2_offset = IWL8260_DCCM2_OFFSET, \ + .dccm2_len = IWL8260_DCCM2_LEN, \ + .smem_offset = IWL8260_SMEM_OFFSET, \ + .smem_len = IWL8260_SMEM_LEN, \ + .default_nvm_file_C_step = DEFAULT_NVM_FILE_FAMILY_8000C, \ + .thermal_params = &iwl8000_tt_params, \ + .apmg_not_supported = true, \ + .nvm_type = IWL_NVM_EXT, \ + .dbgc_supported = true, \ + .min_umac_error_event_table = 0x800000 + +#define IWL_DEVICE_8000 \ + IWL_DEVICE_8000_COMMON, \ + .ucode_api_max = IWL8000_UCODE_API_MAX, \ + .ucode_api_min = IWL8000_UCODE_API_MIN \ + +#define IWL_DEVICE_8260 \ + IWL_DEVICE_8000_COMMON, \ + .ucode_api_max = IWL8000_UCODE_API_MAX, \ + .ucode_api_min = IWL8000_UCODE_API_MIN \ + +#define IWL_DEVICE_8265 \ + IWL_DEVICE_8000_COMMON, \ + .ucode_api_max = IWL8265_UCODE_API_MAX, \ + .ucode_api_min = IWL8265_UCODE_API_MIN \ + +const struct iwl_cfg iwl8260_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 8260", + .fw_name_pre = IWL8000_FW_PRE, + IWL_DEVICE_8260, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, +}; + +const struct iwl_cfg iwl8260_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 8260", + .fw_name_pre = IWL8000_FW_PRE, + IWL_DEVICE_8260, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, +}; + +const struct iwl_cfg iwl8265_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 8265", + .fw_name_pre = IWL8265_FW_PRE, + IWL_DEVICE_8265, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, + .vht_mu_mimo_supported = true, +}; + +const struct iwl_cfg iwl8275_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 8275", + .fw_name_pre = IWL8265_FW_PRE, + IWL_DEVICE_8265, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, + .vht_mu_mimo_supported = true, +}; + +const struct iwl_cfg iwl4165_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 4165", + .fw_name_pre = IWL8000_FW_PRE, + IWL_DEVICE_8000, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, +}; + +MODULE_FIRMWARE(IWL8000_MODULE_FIRMWARE(IWL8000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL8265_MODULE_FIRMWARE(IWL8265_UCODE_API_MAX)); diff --git a/sys/contrib/dev/iwlwifi/cfg/9000.c b/sys/contrib/dev/iwlwifi/cfg/9000.c new file mode 100644 index 00000000000..7a7ca06d46c --- /dev/null +++ b/sys/contrib/dev/iwlwifi/cfg/9000.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#include +#include +#include "iwl-config.h" +#include "fw/file.h" +#include "iwl-prph.h" + +/* Highest firmware API version supported */ +#define IWL9000_UCODE_API_MAX 46 + +/* Lowest firmware API version supported */ +#define IWL9000_UCODE_API_MIN 30 + +/* NVM versions */ +#define IWL9000_NVM_VERSION 0x0a1d + +/* Memory offsets and lengths */ +#define IWL9000_DCCM_OFFSET 0x800000 +#define IWL9000_DCCM_LEN 0x18000 +#define IWL9000_DCCM2_OFFSET 0x880000 +#define IWL9000_DCCM2_LEN 0x8000 +#define IWL9000_SMEM_OFFSET 0x400000 +#define IWL9000_SMEM_LEN 0x68000 + +#define IWL9000_FW_PRE "iwlwifi-9000-pu-b0-jf-b0-" +#define IWL9260_FW_PRE "iwlwifi-9260-th-b0-jf-b0-" +#define IWL9000_MODULE_FIRMWARE(api) \ + IWL9000_FW_PRE __stringify(api) ".ucode" +#define IWL9260_MODULE_FIRMWARE(api) \ + IWL9260_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl9000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, + .num_of_queues = 31, + .max_tfd_queue_size = 256, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, +}; + +static const struct iwl_ht_params iwl9000_ht_params = { + .stbc = true, + .ldpc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +static const struct iwl_tt_params iwl9000_tt_params = { + .ct_kill_entry = 115, + .ct_kill_exit = 93, + .ct_kill_duration = 5, + .dynamic_smps_entry = 111, + .dynamic_smps_exit = 107, + .tx_protection_entry = 112, + .tx_protection_exit = 105, + .tx_backoff = { + {.temperature = 110, .backoff = 200}, + {.temperature = 111, .backoff = 600}, + {.temperature = 112, .backoff = 1200}, + {.temperature = 113, .backoff = 2000}, + {.temperature = 114, .backoff = 4000}, + }, + .support_ct_kill = true, + .support_dynamic_smps = true, + .support_tx_protection = true, + .support_tx_backoff = true, +}; + +#define IWL_DEVICE_9000 \ + .ucode_api_max = IWL9000_UCODE_API_MAX, \ + .ucode_api_min = IWL9000_UCODE_API_MIN, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 10, \ + .non_shared_ant = ANT_B, \ + .dccm_offset = IWL9000_DCCM_OFFSET, \ + .dccm_len = IWL9000_DCCM_LEN, \ + .dccm2_offset = IWL9000_DCCM2_OFFSET, \ + .dccm2_len = IWL9000_DCCM2_LEN, \ + .smem_offset = IWL9000_SMEM_OFFSET, \ + .smem_len = IWL9000_SMEM_LEN, \ + .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, \ + .thermal_params = &iwl9000_tt_params, \ + .apmg_not_supported = true, \ + .num_rbds = 512, \ + .vht_mu_mimo_supported = true, \ + .mac_addr_from_csr = 0x380, \ + .nvm_type = IWL_NVM_EXT, \ + .dbgc_supported = true, \ + .min_umac_error_event_table = 0x800000, \ + .d3_debug_data_base_addr = 0x401000, \ + .d3_debug_data_length = 92 * 1024, \ + .ht_params = &iwl9000_ht_params, \ + .nvm_ver = IWL9000_NVM_VERSION, \ + .mon_smem_regs = { \ + .write_ptr = { \ + .addr = LDBG_M2S_BUF_WPTR, \ + .mask = LDBG_M2S_BUF_WPTR_VAL_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = LDBG_M2S_BUF_WRAP_CNT, \ + .mask = LDBG_M2S_BUF_WRAP_CNT_VAL_MSK, \ + }, \ + }, \ + .mon_dram_regs = { \ + .write_ptr = { \ + .addr = MON_BUFF_WRPTR_VER2, \ + .mask = 0xffffffff, \ + }, \ + .cycle_cnt = { \ + .addr = MON_BUFF_CYCLE_CNT_VER2, \ + .mask = 0xffffffff, \ + }, \ + } + +const struct iwl_cfg_trans_params iwl9000_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_9000, + .base_params = &iwl9000_base_params, + .mq_rx_supported = true, + .rf_id = true, +}; + +const struct iwl_cfg_trans_params iwl9560_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_9000, + .base_params = &iwl9000_base_params, + .mq_rx_supported = true, + .rf_id = true, + .integrated = true, + .xtal_latency = 650, +}; + +const struct iwl_cfg_trans_params iwl9560_long_latency_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_9000, + .base_params = &iwl9000_base_params, + .mq_rx_supported = true, + .rf_id = true, + .integrated = true, + .xtal_latency = 2820, +}; + +const struct iwl_cfg_trans_params iwl9560_shared_clk_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_9000, + .base_params = &iwl9000_base_params, + .mq_rx_supported = true, + .rf_id = true, + .integrated = true, + .xtal_latency = 670, + .extra_phy_cfg_flags = FW_PHY_CFG_SHARED_CLK +}; + +const char iwl9162_name[] = "Intel(R) Wireless-AC 9162"; +const char iwl9260_name[] = "Intel(R) Wireless-AC 9260"; +const char iwl9260_1_name[] = "Intel(R) Wireless-AC 9260-1"; +const char iwl9270_name[] = "Intel(R) Wireless-AC 9270"; +const char iwl9461_name[] = "Intel(R) Wireless-AC 9461"; +const char iwl9462_name[] = "Intel(R) Wireless-AC 9462"; +const char iwl9560_name[] = "Intel(R) Wireless-AC 9560"; +const char iwl9162_160_name[] = "Intel(R) Wireless-AC 9162 160MHz"; +const char iwl9260_160_name[] = "Intel(R) Wireless-AC 9260 160MHz"; +const char iwl9270_160_name[] = "Intel(R) Wireless-AC 9270 160MHz"; +const char iwl9461_160_name[] = "Intel(R) Wireless-AC 9461 160MHz"; +const char iwl9462_160_name[] = "Intel(R) Wireless-AC 9462 160MHz"; +const char iwl9560_160_name[] = "Intel(R) Wireless-AC 9560 160MHz"; + +const char iwl9260_killer_1550_name[] = + "Killer (R) Wireless-AC 1550 Wireless Network Adapter (9260NGW) 160MHz"; +const char iwl9560_killer_1550i_name[] = + "Killer (R) Wireless-AC 1550i Wireless Network Adapter (9560NGW)"; +const char iwl9560_killer_1550i_160_name[] = + "Killer(R) Wireless-AC 1550i Wireless Network Adapter (9560NGW) 160MHz"; +const char iwl9560_killer_1550s_name[] = + "Killer (R) Wireless-AC 1550s Wireless Network Adapter (9560NGW)"; +const char iwl9560_killer_1550s_160_name[] = + "Killer(R) Wireless-AC 1550s Wireless Network Adapter (9560D2W) 160MHz"; + +const struct iwl_cfg iwl9260_2ac_cfg = { + .fw_name_pre = IWL9260_FW_PRE, + IWL_DEVICE_9000, +}; + +const struct iwl_cfg iwl9560_2ac_cfg_soc = { + .fw_name_pre = IWL9000_FW_PRE, + IWL_DEVICE_9000, +}; + +MODULE_FIRMWARE(IWL9000_MODULE_FIRMWARE(IWL9000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL9260_MODULE_FIRMWARE(IWL9000_UCODE_API_MAX)); diff --git a/sys/contrib/dev/iwlwifi/fw/acpi.h b/sys/contrib/dev/iwlwifi/fw/acpi.h new file mode 100644 index 00000000000..4aaa8a6b071 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/acpi.h @@ -0,0 +1,305 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#ifndef __iwl_fw_acpi__ +#define __iwl_fw_acpi__ + +#include +#include "fw/api/commands.h" +#include "fw/api/power.h" +#include "fw/api/phy.h" +#include "fw/api/nvm-reg.h" +#include "fw/img.h" +#include "iwl-trans.h" + + +#define ACPI_WRDS_METHOD "WRDS" +#define ACPI_EWRD_METHOD "EWRD" +#define ACPI_WGDS_METHOD "WGDS" +#define ACPI_WRDD_METHOD "WRDD" +#define ACPI_SPLC_METHOD "SPLC" +#define ACPI_ECKV_METHOD "ECKV" +#define ACPI_PPAG_METHOD "PPAG" +#define ACPI_WTAS_METHOD "WTAS" + +#define ACPI_WIFI_DOMAIN (0x07) + +#define ACPI_SAR_PROFILE_NUM 4 + +#define ACPI_NUM_GEO_PROFILES 3 +#define ACPI_NUM_GEO_PROFILES_REV3 8 +#define ACPI_GEO_PER_CHAIN_SIZE 3 + +#define ACPI_SAR_NUM_CHAINS_REV0 2 +#define ACPI_SAR_NUM_CHAINS_REV1 2 +#define ACPI_SAR_NUM_CHAINS_REV2 4 +#define ACPI_SAR_NUM_SUB_BANDS_REV0 5 +#define ACPI_SAR_NUM_SUB_BANDS_REV1 11 +#define ACPI_SAR_NUM_SUB_BANDS_REV2 11 + +#define ACPI_WRDS_WIFI_DATA_SIZE_REV0 (ACPI_SAR_NUM_CHAINS_REV0 * \ + ACPI_SAR_NUM_SUB_BANDS_REV0 + 2) +#define ACPI_WRDS_WIFI_DATA_SIZE_REV1 (ACPI_SAR_NUM_CHAINS_REV1 * \ + ACPI_SAR_NUM_SUB_BANDS_REV1 + 2) +#define ACPI_WRDS_WIFI_DATA_SIZE_REV2 (ACPI_SAR_NUM_CHAINS_REV2 * \ + ACPI_SAR_NUM_SUB_BANDS_REV2 + 2) +#define ACPI_EWRD_WIFI_DATA_SIZE_REV0 ((ACPI_SAR_PROFILE_NUM - 1) * \ + ACPI_SAR_NUM_CHAINS_REV0 * \ + ACPI_SAR_NUM_SUB_BANDS_REV0 + 3) +#define ACPI_EWRD_WIFI_DATA_SIZE_REV1 ((ACPI_SAR_PROFILE_NUM - 1) * \ + ACPI_SAR_NUM_CHAINS_REV1 * \ + ACPI_SAR_NUM_SUB_BANDS_REV1 + 3) +#define ACPI_EWRD_WIFI_DATA_SIZE_REV2 ((ACPI_SAR_PROFILE_NUM - 1) * \ + ACPI_SAR_NUM_CHAINS_REV2 * \ + ACPI_SAR_NUM_SUB_BANDS_REV2 + 3) + +/* revision 0 and 1 are identical, except for the semantics in the FW */ +#define ACPI_GEO_NUM_BANDS_REV0 2 +#define ACPI_GEO_NUM_BANDS_REV2 3 +#define ACPI_GEO_NUM_CHAINS 2 + +#define ACPI_WRDD_WIFI_DATA_SIZE 2 +#define ACPI_SPLC_WIFI_DATA_SIZE 2 +#define ACPI_ECKV_WIFI_DATA_SIZE 2 + +/* + * 1 type, 1 enabled, 1 block list size, 16 block list array + */ +#define APCI_WTAS_BLACK_LIST_MAX 16 +#define ACPI_WTAS_WIFI_DATA_SIZE (3 + APCI_WTAS_BLACK_LIST_MAX) + +#define ACPI_PPAG_WIFI_DATA_SIZE_V1 ((IWL_NUM_CHAIN_LIMITS * \ + IWL_NUM_SUB_BANDS_V1) + 2) +#define ACPI_PPAG_WIFI_DATA_SIZE_V2 ((IWL_NUM_CHAIN_LIMITS * \ + IWL_NUM_SUB_BANDS_V2) + 2) + +/* PPAG gain value bounds in 1/8 dBm */ +#define ACPI_PPAG_MIN_LB -16 +#define ACPI_PPAG_MAX_LB 24 +#define ACPI_PPAG_MIN_HB -16 +#define ACPI_PPAG_MAX_HB 40 + +/* + * The profile for revision 2 is a superset of revision 1, which is in + * turn a superset of revision 0. So we can store all revisions + * inside revision 2, which is what we represent here. + */ +struct iwl_sar_profile_chain { + u8 subbands[ACPI_SAR_NUM_SUB_BANDS_REV2]; +}; + +struct iwl_sar_profile { + bool enabled; + struct iwl_sar_profile_chain chains[ACPI_SAR_NUM_CHAINS_REV2]; +}; + +/* Same thing as with SAR, all revisions fit in revision 2 */ +struct iwl_geo_profile_band { + u8 max; + u8 chains[ACPI_GEO_NUM_CHAINS]; +}; + +struct iwl_geo_profile { + struct iwl_geo_profile_band bands[ACPI_GEO_NUM_BANDS_REV2]; +}; + +enum iwl_dsm_funcs_rev_0 { + DSM_FUNC_QUERY = 0, + DSM_FUNC_DISABLE_SRD = 1, + DSM_FUNC_ENABLE_INDONESIA_5G2 = 2, + DSM_FUNC_ENABLE_6E = 3, + DSM_FUNC_11AX_ENABLEMENT = 6, + DSM_FUNC_ENABLE_UNII4_CHAN = 7, + DSM_FUNC_ACTIVATE_CHANNEL = 8 +}; + +enum iwl_dsm_values_srd { + DSM_VALUE_SRD_ACTIVE, + DSM_VALUE_SRD_PASSIVE, + DSM_VALUE_SRD_DISABLE, + DSM_VALUE_SRD_MAX +}; + +enum iwl_dsm_values_indonesia { + DSM_VALUE_INDONESIA_DISABLE, + DSM_VALUE_INDONESIA_ENABLE, + DSM_VALUE_INDONESIA_RESERVED, + DSM_VALUE_INDONESIA_MAX +}; + +/* DSM RFI uses a different GUID, so need separate definitions */ + +#define DSM_RFI_FUNC_ENABLE 3 + +enum iwl_dsm_values_rfi { + DSM_VALUE_RFI_ENABLE, + DSM_VALUE_RFI_DISABLE, + DSM_VALUE_RFI_MAX +}; + +#ifdef CONFIG_ACPI + +struct iwl_fw_runtime; + +extern const guid_t iwl_guid; +extern const guid_t iwl_rfi_guid; + +void *iwl_acpi_get_object(struct device *dev, acpi_string method); + +int iwl_acpi_get_dsm_u8(struct device *dev, int rev, int func, + const guid_t *guid, u8 *value); + +int iwl_acpi_get_dsm_u32(struct device *dev, int rev, int func, + const guid_t *guid, u32 *value); + +union acpi_object *iwl_acpi_get_wifi_pkg_range(struct device *dev, + union acpi_object *data, + int min_data_size, + int max_data_size, + int *tbl_rev); +/** + * iwl_acpi_get_mcc - read MCC from ACPI, if available + * + * @dev: the struct device + * @mcc: output buffer (3 bytes) that will get the MCC + * + * This function tries to read the current MCC from ACPI if available. + */ +int iwl_acpi_get_mcc(struct device *dev, char *mcc); + +u64 iwl_acpi_get_pwr_limit(struct device *dev); + +/* + * iwl_acpi_get_eckv - read external clock validation from ACPI, if available + * + * @dev: the struct device + * @extl_clk: output var (2 bytes) that will get the clk indication. + * + * This function tries to read the external clock indication + * from ACPI if available. + */ +int iwl_acpi_get_eckv(struct device *dev, u32 *extl_clk); + +int iwl_sar_select_profile(struct iwl_fw_runtime *fwrt, + __le16 *per_chain, u32 n_tables, u32 n_subbands, + int prof_a, int prof_b); + +int iwl_sar_get_wrds_table(struct iwl_fw_runtime *fwrt); + +int iwl_sar_get_ewrd_table(struct iwl_fw_runtime *fwrt); + +int iwl_sar_get_wgds_table(struct iwl_fw_runtime *fwrt); + +bool iwl_sar_geo_support(struct iwl_fw_runtime *fwrt); + +int iwl_sar_geo_init(struct iwl_fw_runtime *fwrt, + struct iwl_per_chain_offset *table, + u32 n_bands, u32 n_profiles); + +int iwl_acpi_get_tas(struct iwl_fw_runtime *fwrt, __le32 *block_list_array, + int *block_list_size); + +__le32 iwl_acpi_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt); + +#else /* CONFIG_ACPI */ + +static inline void *iwl_acpi_get_object(struct device *dev, acpi_string method) +{ + return ERR_PTR(-ENOENT); +} + +static inline void *iwl_acpi_get_dsm_object(struct device *dev, int rev, + int func, union acpi_object *args) +{ + return ERR_PTR(-ENOENT); +} + +static inline int iwl_acpi_get_dsm_u8(struct device *dev, int rev, int func, + const guid_t *guid, u8 *value) +{ + return -ENOENT; +} + +static inline int iwl_acpi_get_dsm_u32(struct device *dev, int rev, int func, + const guid_t *guid, u32 *value) +{ + return -ENOENT; +} + +static inline union acpi_object * +iwl_acpi_get_wifi_pkg_range(struct device *dev, + union acpi_object *data, + int min_data_size, int max_data_size, + int *tbl_rev) +{ + return ERR_PTR(-ENOENT); +} + +static inline int iwl_acpi_get_mcc(struct device *dev, char *mcc) +{ + return -ENOENT; +} + +static inline u64 iwl_acpi_get_pwr_limit(struct device *dev) +{ + return 0; +} + +static inline int iwl_acpi_get_eckv(struct device *dev, u32 *extl_clk) +{ + return -ENOENT; +} + +static inline int iwl_sar_select_profile(struct iwl_fw_runtime *fwrt, + __le16 *per_chain, u32 n_tables, u32 n_subbands, + int prof_a, int prof_b) +{ + return -ENOENT; +} + +static inline int iwl_sar_get_wrds_table(struct iwl_fw_runtime *fwrt) +{ + return -ENOENT; +} + +static inline int iwl_sar_get_ewrd_table(struct iwl_fw_runtime *fwrt) +{ + return -ENOENT; +} + +static inline int iwl_sar_get_wgds_table(struct iwl_fw_runtime *fwrt) +{ + return 1; +} + +static inline bool iwl_sar_geo_support(struct iwl_fw_runtime *fwrt) +{ + return false; +} + +static inline int iwl_acpi_get_tas(struct iwl_fw_runtime *fwrt, + __le32 *block_list_array, + int *block_list_size) +{ + return -ENOENT; +} + +static inline __le32 iwl_acpi_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt) +{ + return 0; +} + +#endif /* CONFIG_ACPI */ + +static inline union acpi_object * +iwl_acpi_get_wifi_pkg(struct device *dev, + union acpi_object *data, + int data_size, int *tbl_rev) +{ + return iwl_acpi_get_wifi_pkg_range(dev, data, data_size, data_size, + tbl_rev); +} + +#endif /* __iwl_fw_acpi__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/alive.h b/sys/contrib/dev/iwlwifi/fw/api/alive.h new file mode 100644 index 00000000000..c840a97e6a6 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/alive.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018, 2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_alive_h__ +#define __iwl_fw_api_alive_h__ + +/* alive response is_valid values */ +#define ALIVE_RESP_UCODE_OK BIT(0) +#define ALIVE_RESP_RFKILL BIT(1) + +/* alive response ver_type values */ +enum { + FW_TYPE_HW = 0, + FW_TYPE_PROT = 1, + FW_TYPE_AP = 2, + FW_TYPE_WOWLAN = 3, + FW_TYPE_TIMING = 4, + FW_TYPE_WIPAN = 5 +}; + +/* alive response ver_subtype values */ +enum { + FW_SUBTYPE_FULL_FEATURE = 0, + FW_SUBTYPE_BOOTSRAP = 1, /* Not valid */ + FW_SUBTYPE_REDUCED = 2, + FW_SUBTYPE_ALIVE_ONLY = 3, + FW_SUBTYPE_WOWLAN = 4, + FW_SUBTYPE_AP_SUBTYPE = 5, + FW_SUBTYPE_WIPAN = 6, + FW_SUBTYPE_INITIALIZE = 9 +}; + +#define IWL_ALIVE_STATUS_ERR 0xDEAD +#define IWL_ALIVE_STATUS_OK 0xCAFE + +#define IWL_ALIVE_FLG_RFKILL BIT(0) + +struct iwl_lmac_debug_addrs { + __le32 error_event_table_ptr; /* SRAM address for error log */ + __le32 log_event_table_ptr; /* SRAM address for LMAC event log */ + __le32 cpu_register_ptr; + __le32 dbgm_config_ptr; + __le32 alive_counter_ptr; + __le32 scd_base_ptr; /* SRAM address for SCD */ + __le32 st_fwrd_addr; /* pointer to Store and forward */ + __le32 st_fwrd_size; +} __packed; /* UCODE_DEBUG_ADDRS_API_S_VER_2 */ + +struct iwl_lmac_alive { + __le32 ucode_major; + __le32 ucode_minor; + u8 ver_subtype; + u8 ver_type; + u8 mac; + u8 opt; + __le32 timestamp; + struct iwl_lmac_debug_addrs dbg_ptrs; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_3 */ + +struct iwl_umac_debug_addrs { + __le32 error_info_addr; /* SRAM address for UMAC error log */ + __le32 dbg_print_buff_addr; +} __packed; /* UMAC_DEBUG_ADDRS_API_S_VER_1 */ + +struct iwl_umac_alive { + __le32 umac_major; /* UMAC version: major */ + __le32 umac_minor; /* UMAC version: minor */ + struct iwl_umac_debug_addrs dbg_ptrs; +} __packed; /* UMAC_ALIVE_DATA_API_S_VER_2 */ + +struct iwl_sku_id { + __le32 data[3]; +} __packed; /* SKU_ID_API_S_VER_1 */ + +struct iwl_alive_ntf_v3 { + __le16 status; + __le16 flags; + struct iwl_lmac_alive lmac_data; + struct iwl_umac_alive umac_data; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_3 */ + +struct iwl_alive_ntf_v4 { + __le16 status; + __le16 flags; + struct iwl_lmac_alive lmac_data[2]; + struct iwl_umac_alive umac_data; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_4 */ + +struct iwl_alive_ntf_v5 { + __le16 status; + __le16 flags; + struct iwl_lmac_alive lmac_data[2]; + struct iwl_umac_alive umac_data; + struct iwl_sku_id sku_id; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_5 */ + +/** + * enum iwl_extended_cfg_flag - commands driver may send before + * finishing init flow + * @IWL_INIT_DEBUG_CFG: driver is going to send debug config command + * @IWL_INIT_NVM: driver is going to send NVM_ACCESS commands + * @IWL_INIT_PHY: driver is going to send PHY_DB commands + */ +enum iwl_extended_cfg_flags { + IWL_INIT_DEBUG_CFG, + IWL_INIT_NVM, + IWL_INIT_PHY, +}; + +/** + * struct iwl_extended_cfg_cmd - mark what commands ucode should wait for + * before finishing init flows + * @init_flags: values from iwl_extended_cfg_flags + */ +struct iwl_init_extended_cfg_cmd { + __le32 init_flags; +} __packed; /* INIT_EXTENDED_CFG_CMD_API_S_VER_1 */ + +/** + * struct iwl_radio_version_notif - information on the radio version + * ( RADIO_VERSION_NOTIFICATION = 0x68 ) + * @radio_flavor: radio flavor + * @radio_step: radio version step + * @radio_dash: radio version dash + */ +struct iwl_radio_version_notif { + __le32 radio_flavor; + __le32 radio_step; + __le32 radio_dash; +} __packed; /* RADIO_VERSION_NOTOFICATION_S_VER_1 */ + +enum iwl_card_state_flags { + CARD_ENABLED = 0x00, + HW_CARD_DISABLED = 0x01, + SW_CARD_DISABLED = 0x02, + CT_KILL_CARD_DISABLED = 0x04, + HALT_CARD_DISABLED = 0x08, + CARD_DISABLED_MSK = 0x0f, + CARD_IS_RX_ON = 0x10, +}; + +/** + * struct iwl_radio_version_notif - information on the card state + * ( CARD_STATE_NOTIFICATION = 0xa1 ) + * @flags: &enum iwl_card_state_flags + */ +struct iwl_card_state_notif { + __le32 flags; +} __packed; /* CARD_STATE_NTFY_API_S_VER_1 */ + +/** + * enum iwl_error_recovery_flags - flags for error recovery cmd + * @ERROR_RECOVERY_UPDATE_DB: update db from blob sent + * @ERROR_RECOVERY_END_OF_RECOVERY: end of recovery + */ +enum iwl_error_recovery_flags { + ERROR_RECOVERY_UPDATE_DB = BIT(0), + ERROR_RECOVERY_END_OF_RECOVERY = BIT(1), +}; + +/** + * struct iwl_fw_error_recovery_cmd - recovery cmd sent upon assert + * @flags: &enum iwl_error_recovery_flags + * @buf_size: db buffer size in bytes + */ +struct iwl_fw_error_recovery_cmd { + __le32 flags; + __le32 buf_size; +} __packed; /* ERROR_RECOVERY_CMD_HDR_API_S_VER_1 */ + +#endif /* __iwl_fw_api_alive_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/binding.h b/sys/contrib/dev/iwlwifi/fw/api/binding.h new file mode 100644 index 00000000000..29e2816e705 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/binding.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_binding_h__ +#define __iwl_fw_api_binding_h__ + +#include +#include + +#define MAX_MACS_IN_BINDING (3) +#define MAX_BINDINGS (4) + +/** + * struct iwl_binding_cmd_v1 - configuring bindings + * ( BINDING_CONTEXT_CMD = 0x2b ) + * @id_and_color: ID and color of the relevant Binding, + * &enum iwl_ctxt_id_and_color + * @action: action to perform, one of FW_CTXT_ACTION_* + * @macs: array of MAC id and colors which belong to the binding, + * &enum iwl_ctxt_id_and_color + * @phy: PHY id and color which belongs to the binding, + * &enum iwl_ctxt_id_and_color + */ +struct iwl_binding_cmd_v1 { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* BINDING_DATA_API_S_VER_1 */ + __le32 macs[MAX_MACS_IN_BINDING]; + __le32 phy; +} __packed; /* BINDING_CMD_API_S_VER_1 */ + +/** + * struct iwl_binding_cmd - configuring bindings + * ( BINDING_CONTEXT_CMD = 0x2b ) + * @id_and_color: ID and color of the relevant Binding, + * &enum iwl_ctxt_id_and_color + * @action: action to perform, one of FW_CTXT_ACTION_* + * @macs: array of MAC id and colors which belong to the binding + * &enum iwl_ctxt_id_and_color + * @phy: PHY id and color which belongs to the binding + * &enum iwl_ctxt_id_and_color + * @lmac_id: the lmac id the binding belongs to + */ +struct iwl_binding_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* BINDING_DATA_API_S_VER_1 */ + __le32 macs[MAX_MACS_IN_BINDING]; + __le32 phy; + __le32 lmac_id; +} __packed; /* BINDING_CMD_API_S_VER_2 */ + +#define IWL_BINDING_CMD_SIZE_V1 sizeof(struct iwl_binding_cmd_v1) +#define IWL_LMAC_24G_INDEX 0 +#define IWL_LMAC_5G_INDEX 1 + +static inline u32 iwl_mvm_get_lmac_id(const struct iwl_fw *fw, + enum nl80211_band band){ + if (!fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_CDB_SUPPORT) || + band == NL80211_BAND_2GHZ) + return IWL_LMAC_24G_INDEX; + return IWL_LMAC_5G_INDEX; +} + +/* The maximal number of fragments in the FW's schedule session */ +#define IWL_MVM_MAX_QUOTA 128 + +/** + * struct iwl_time_quota_data_v1 - configuration of time quota per binding + * @id_and_color: ID and color of the relevant Binding, + * &enum iwl_ctxt_id_and_color + * @quota: absolute time quota in TU. The scheduler will try to divide the + * remainig quota (after Time Events) according to this quota. + * @max_duration: max uninterrupted context duration in TU + */ +struct iwl_time_quota_data_v1 { + __le32 id_and_color; + __le32 quota; + __le32 max_duration; +} __packed; /* TIME_QUOTA_DATA_API_S_VER_1 */ + +/** + * struct iwl_time_quota_cmd - configuration of time quota between bindings + * ( TIME_QUOTA_CMD = 0x2c ) + * @quotas: allocations per binding + * Note: on non-CDB the fourth one is the auxilary mac and is + * essentially zero. + * On CDB the fourth one is a regular binding. + */ +struct iwl_time_quota_cmd_v1 { + struct iwl_time_quota_data_v1 quotas[MAX_BINDINGS]; +} __packed; /* TIME_QUOTA_ALLOCATION_CMD_API_S_VER_1 */ + +enum iwl_quota_low_latency { + IWL_QUOTA_LOW_LATENCY_NONE = 0, + IWL_QUOTA_LOW_LATENCY_TX = BIT(0), + IWL_QUOTA_LOW_LATENCY_RX = BIT(1), + IWL_QUOTA_LOW_LATENCY_TX_RX = + IWL_QUOTA_LOW_LATENCY_TX | IWL_QUOTA_LOW_LATENCY_RX, +}; + +/** + * struct iwl_time_quota_data - configuration of time quota per binding + * @id_and_color: ID and color of the relevant Binding. + * @quota: absolute time quota in TU. The scheduler will try to divide the + * remainig quota (after Time Events) according to this quota. + * @max_duration: max uninterrupted context duration in TU + * @low_latency: low latency status, &enum iwl_quota_low_latency + */ +struct iwl_time_quota_data { + __le32 id_and_color; + __le32 quota; + __le32 max_duration; + __le32 low_latency; +} __packed; /* TIME_QUOTA_DATA_API_S_VER_2 */ + +/** + * struct iwl_time_quota_cmd - configuration of time quota between bindings + * ( TIME_QUOTA_CMD = 0x2c ) + * Note: on non-CDB the fourth one is the auxilary mac and is essentially zero. + * On CDB the fourth one is a regular binding. + * + * @quotas: allocations per binding + */ +struct iwl_time_quota_cmd { + struct iwl_time_quota_data quotas[MAX_BINDINGS]; +} __packed; /* TIME_QUOTA_ALLOCATION_CMD_API_S_VER_2 */ + +#endif /* __iwl_fw_api_binding_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/cmdhdr.h b/sys/contrib/dev/iwlwifi/fw/api/cmdhdr.h new file mode 100644 index 00000000000..d130d4f8544 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/cmdhdr.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_cmdhdr_h__ +#define __iwl_fw_api_cmdhdr_h__ + +/** + * DOC: Host command section + * + * A host command is a command issued by the upper layer to the fw. There are + * several versions of fw that have several APIs. The transport layer is + * completely agnostic to these differences. + * The transport does provide helper functionality (i.e. SYNC / ASYNC mode), + */ +#define SEQ_TO_QUEUE(s) (((s) >> 8) & 0x1f) +#define QUEUE_TO_SEQ(q) (((q) & 0x1f) << 8) +#define SEQ_TO_INDEX(s) ((s) & 0xff) +#define INDEX_TO_SEQ(i) ((i) & 0xff) +#define SEQ_RX_FRAME cpu_to_le16(0x8000) + +/* + * those functions retrieve specific information from + * the id field in the iwl_host_cmd struct which contains + * the command id, the group id and the version of the command + * and vice versa +*/ +static inline u8 iwl_cmd_opcode(u32 cmdid) +{ + return cmdid & 0xFF; +} + +static inline u8 iwl_cmd_groupid(u32 cmdid) +{ + return ((cmdid & 0xFF00) >> 8); +} + +static inline u8 iwl_cmd_version(u32 cmdid) +{ + return ((cmdid & 0xFF0000) >> 16); +} + +static inline u32 iwl_cmd_id(u8 opcode, u8 groupid, u8 version) +{ + return opcode + (groupid << 8) + (version << 16); +} + +/* make u16 wide id out of u8 group and opcode */ +#define WIDE_ID(grp, opcode) (((grp) << 8) | (opcode)) +#define DEF_ID(opcode) ((1 << 8) | (opcode)) + +/* due to the conversion, this group is special; new groups + * should be defined in the appropriate fw-api header files + */ +#define IWL_ALWAYS_LONG_GROUP 1 + +/** + * struct iwl_cmd_header - (short) command header format + * + * This header format appears in the beginning of each command sent from the + * driver, and each response/notification received from uCode. + */ +struct iwl_cmd_header { + /** + * @cmd: Command ID: REPLY_RXON, etc. + */ + u8 cmd; + /** + * @group_id: group ID, for commands with groups + */ + u8 group_id; + /** + * @sequence: + * Sequence number for the command. + * + * The driver sets up the sequence number to values of its choosing. + * uCode does not use this value, but passes it back to the driver + * when sending the response to each driver-originated command, so + * the driver can match the response to the command. Since the values + * don't get used by uCode, the driver may set up an arbitrary format. + * + * There is one exception: uCode sets bit 15 when it originates + * the response/notification, i.e. when the response/notification + * is not a direct response to a command sent by the driver. For + * example, uCode issues REPLY_RX when it sends a received frame + * to the driver; it is not a direct response to any driver command. + * + * The Linux driver uses the following format: + * + * 0:7 tfd index - position within TX queue + * 8:12 TX queue id + * 13:14 reserved + * 15 unsolicited RX or uCode-originated notification + */ + __le16 sequence; +} __packed; + +/** + * struct iwl_cmd_header_wide + * + * This header format appears in the beginning of each command sent from the + * driver, and each response/notification received from uCode. + * this is the wide version that contains more information about the command + * like length, version and command type + * + * @cmd: command ID, like in &struct iwl_cmd_header + * @group_id: group ID, like in &struct iwl_cmd_header + * @sequence: sequence, like in &struct iwl_cmd_header + * @length: length of the command + * @reserved: reserved + * @version: command version + */ +struct iwl_cmd_header_wide { + u8 cmd; + u8 group_id; + __le16 sequence; + __le16 length; + u8 reserved; + u8 version; +} __packed; + +/** + * struct iwl_calib_res_notif_phy_db - Receive phy db chunk after calibrations + * @type: type of the result - mostly ignored + * @length: length of the data + * @data: data, length in @length + */ +struct iwl_calib_res_notif_phy_db { + __le16 type; + __le16 length; + u8 data[]; +} __packed; + +/** + * struct iwl_phy_db_cmd - configure operational ucode + * @type: type of the data + * @length: length of the data + * @data: data, length in @length + */ +struct iwl_phy_db_cmd { + __le16 type; + __le16 length; + u8 data[]; +} __packed; + +/** + * struct iwl_cmd_response - generic response struct for most commands + * @status: status of the command asked, changes for each one + */ +struct iwl_cmd_response { + __le32 status; +}; + +#endif /* __iwl_fw_api_cmdhdr_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/coex.h b/sys/contrib/dev/iwlwifi/fw/api/coex.h new file mode 100644 index 00000000000..3e81e936922 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/coex.h @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2013-2014, 2018-2019 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_coex_h__ +#define __iwl_fw_api_coex_h__ + +#include +#include + +#define BITS(nb) (BIT(nb) - 1) + +enum iwl_bt_coex_lut_type { + BT_COEX_TIGHT_LUT = 0, + BT_COEX_LOOSE_LUT, + BT_COEX_TX_DIS_LUT, + + BT_COEX_MAX_LUT, + BT_COEX_INVALID_LUT = 0xff, +}; /* BT_COEX_DECISION_LUT_INDEX_API_E_VER_1 */ + +#define BT_REDUCED_TX_POWER_BIT BIT(7) + +enum iwl_bt_coex_mode { + BT_COEX_DISABLE = 0x0, + BT_COEX_NW = 0x1, + BT_COEX_BT = 0x2, + BT_COEX_WIFI = 0x3, +}; /* BT_COEX_MODES_E */ + +enum iwl_bt_coex_enabled_modules { + BT_COEX_MPLUT_ENABLED = BIT(0), + BT_COEX_MPLUT_BOOST_ENABLED = BIT(1), + BT_COEX_SYNC2SCO_ENABLED = BIT(2), + BT_COEX_CORUN_ENABLED = BIT(3), + BT_COEX_HIGH_BAND_RET = BIT(4), +}; /* BT_COEX_MODULES_ENABLE_E_VER_1 */ + +/** + * struct iwl_bt_coex_cmd - bt coex configuration command + * @mode: &enum iwl_bt_coex_mode + * @enabled_modules: &enum iwl_bt_coex_enabled_modules + * + * The structure is used for the BT_COEX command. + */ +struct iwl_bt_coex_cmd { + __le32 mode; + __le32 enabled_modules; +} __packed; /* BT_COEX_CMD_API_S_VER_6 */ + +/** + * struct iwl_bt_coex_reduced_txp_update_cmd + * @reduced_txp: bit BT_REDUCED_TX_POWER_BIT to enable / disable, rest of the + * bits are the sta_id (value) + */ +struct iwl_bt_coex_reduced_txp_update_cmd { + __le32 reduced_txp; +} __packed; /* BT_COEX_UPDATE_REDUCED_TX_POWER_API_S_VER_1 */ + +/** + * struct iwl_bt_coex_ci_cmd - bt coex channel inhibition command + * @bt_primary_ci: primary channel inhibition bitmap + * @primary_ch_phy_id: primary channel PHY ID + * @bt_secondary_ci: secondary channel inhibition bitmap + * @secondary_ch_phy_id: secondary channel PHY ID + * + * Used for BT_COEX_CI command + */ +struct iwl_bt_coex_ci_cmd { + __le64 bt_primary_ci; + __le32 primary_ch_phy_id; + + __le64 bt_secondary_ci; + __le32 secondary_ch_phy_id; +} __packed; /* BT_CI_MSG_API_S_VER_2 */ + +#define BT_MBOX(n_dw, _msg, _pos, _nbits) \ + BT_MBOX##n_dw##_##_msg##_POS = (_pos), \ + BT_MBOX##n_dw##_##_msg = BITS(_nbits) << BT_MBOX##n_dw##_##_msg##_POS + +enum iwl_bt_mxbox_dw0 { + BT_MBOX(0, LE_SLAVE_LAT, 0, 3), + BT_MBOX(0, LE_PROF1, 3, 1), + BT_MBOX(0, LE_PROF2, 4, 1), + BT_MBOX(0, LE_PROF_OTHER, 5, 1), + BT_MBOX(0, CHL_SEQ_N, 8, 4), + BT_MBOX(0, INBAND_S, 13, 1), + BT_MBOX(0, LE_MIN_RSSI, 16, 4), + BT_MBOX(0, LE_SCAN, 20, 1), + BT_MBOX(0, LE_ADV, 21, 1), + BT_MBOX(0, LE_MAX_TX_POWER, 24, 4), + BT_MBOX(0, OPEN_CON_1, 28, 2), +}; + +enum iwl_bt_mxbox_dw1 { + BT_MBOX(1, BR_MAX_TX_POWER, 0, 4), + BT_MBOX(1, IP_SR, 4, 1), + BT_MBOX(1, LE_MSTR, 5, 1), + BT_MBOX(1, AGGR_TRFC_LD, 8, 6), + BT_MBOX(1, MSG_TYPE, 16, 3), + BT_MBOX(1, SSN, 19, 2), +}; + +enum iwl_bt_mxbox_dw2 { + BT_MBOX(2, SNIFF_ACT, 0, 3), + BT_MBOX(2, PAG, 3, 1), + BT_MBOX(2, INQUIRY, 4, 1), + BT_MBOX(2, CONN, 5, 1), + BT_MBOX(2, SNIFF_INTERVAL, 8, 5), + BT_MBOX(2, DISC, 13, 1), + BT_MBOX(2, SCO_TX_ACT, 16, 2), + BT_MBOX(2, SCO_RX_ACT, 18, 2), + BT_MBOX(2, ESCO_RE_TX, 20, 2), + BT_MBOX(2, SCO_DURATION, 24, 6), +}; + +enum iwl_bt_mxbox_dw3 { + BT_MBOX(3, SCO_STATE, 0, 1), + BT_MBOX(3, SNIFF_STATE, 1, 1), + BT_MBOX(3, A2DP_STATE, 2, 1), + BT_MBOX(3, ACL_STATE, 3, 1), + BT_MBOX(3, MSTR_STATE, 4, 1), + BT_MBOX(3, OBX_STATE, 5, 1), + BT_MBOX(3, A2DP_SRC, 6, 1), + BT_MBOX(3, OPEN_CON_2, 8, 2), + BT_MBOX(3, TRAFFIC_LOAD, 10, 2), + BT_MBOX(3, CHL_SEQN_LSB, 12, 1), + BT_MBOX(3, INBAND_P, 13, 1), + BT_MBOX(3, MSG_TYPE_2, 16, 3), + BT_MBOX(3, SSN_2, 19, 2), + BT_MBOX(3, UPDATE_REQUEST, 21, 1), +}; + +#define BT_MBOX_MSG(_notif, _num, _field) \ + ((le32_to_cpu((_notif)->mbox_msg[(_num)]) & BT_MBOX##_num##_##_field)\ + >> BT_MBOX##_num##_##_field##_POS) + +#define BT_MBOX_PRINT(_num, _field, _end) \ + pos += scnprintf(buf + pos, bufsz - pos, \ + "\t%s: %d%s", \ + #_field, \ + BT_MBOX_MSG(notif, _num, _field), \ + true ? "\n" : ", ") +enum iwl_bt_activity_grading { + BT_OFF = 0, + BT_ON_NO_CONNECTION = 1, + BT_LOW_TRAFFIC = 2, + BT_HIGH_TRAFFIC = 3, + BT_VERY_HIGH_TRAFFIC = 4, + + BT_MAX_AG, +}; /* BT_COEX_BT_ACTIVITY_GRADING_API_E_VER_1 */ + +enum iwl_bt_ci_compliance { + BT_CI_COMPLIANCE_NONE = 0, + BT_CI_COMPLIANCE_PRIMARY = 1, + BT_CI_COMPLIANCE_SECONDARY = 2, + BT_CI_COMPLIANCE_BOTH = 3, +}; /* BT_COEX_CI_COMPLIENCE_E_VER_1 */ + +/** + * struct iwl_bt_coex_profile_notif - notification about BT coex + * @mbox_msg: message from BT to WiFi + * @msg_idx: the index of the message + * @bt_ci_compliance: enum %iwl_bt_ci_compliance + * @primary_ch_lut: LUT used for primary channel &enum iwl_bt_coex_lut_type + * @secondary_ch_lut: LUT used for secondary channel &enum iwl_bt_coex_lut_type + * @bt_activity_grading: the activity of BT &enum iwl_bt_activity_grading + * @ttc_status: is TTC enabled - one bit per PHY + * @rrc_status: is RRC enabled - one bit per PHY + * @reserved: reserved + */ +struct iwl_bt_coex_profile_notif { + __le32 mbox_msg[4]; + __le32 msg_idx; + __le32 bt_ci_compliance; + + __le32 primary_ch_lut; + __le32 secondary_ch_lut; + __le32 bt_activity_grading; + u8 ttc_status; + u8 rrc_status; + __le16 reserved; +} __packed; /* BT_COEX_PROFILE_NTFY_API_S_VER_4 */ + +#endif /* __iwl_fw_api_coex_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/commands.h b/sys/contrib/dev/iwlwifi/fw/api/commands.h new file mode 100644 index 00000000000..ee6b5844a87 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/commands.h @@ -0,0 +1,617 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2020 Intel Corporation + */ +#ifndef __iwl_fw_api_commands_h__ +#define __iwl_fw_api_commands_h__ + +/** + * enum iwl_mvm_command_groups - command groups for the firmware + * @LEGACY_GROUP: legacy group, uses command IDs from &enum iwl_legacy_cmds + * @LONG_GROUP: legacy group with long header, also uses command IDs + * from &enum iwl_legacy_cmds + * @SYSTEM_GROUP: system group, uses command IDs from + * &enum iwl_system_subcmd_ids + * @MAC_CONF_GROUP: MAC configuration group, uses command IDs from + * &enum iwl_mac_conf_subcmd_ids + * @PHY_OPS_GROUP: PHY operations group, uses command IDs from + * &enum iwl_phy_ops_subcmd_ids + * @DATA_PATH_GROUP: data path group, uses command IDs from + * &enum iwl_data_path_subcmd_ids + * @NAN_GROUP: NAN group, uses command IDs from &enum iwl_nan_subcmd_ids + * @LOCATION_GROUP: location group, uses command IDs from + * &enum iwl_location_subcmd_ids + * @PROT_OFFLOAD_GROUP: protocol offload group, uses command IDs from + * &enum iwl_prot_offload_subcmd_ids + * @REGULATORY_AND_NVM_GROUP: regulatory/NVM group, uses command IDs from + * &enum iwl_regulatory_and_nvm_subcmd_ids + * @DEBUG_GROUP: Debug group, uses command IDs from &enum iwl_debug_cmds + */ +enum iwl_mvm_command_groups { + LEGACY_GROUP = 0x0, + LONG_GROUP = 0x1, + SYSTEM_GROUP = 0x2, + MAC_CONF_GROUP = 0x3, + PHY_OPS_GROUP = 0x4, + DATA_PATH_GROUP = 0x5, + NAN_GROUP = 0x7, + LOCATION_GROUP = 0x8, + PROT_OFFLOAD_GROUP = 0xb, + REGULATORY_AND_NVM_GROUP = 0xc, + DEBUG_GROUP = 0xf, +}; + +/** + * enum iwl_legacy_cmds - legacy group command IDs + */ +enum iwl_legacy_cmds { + /** + * @UCODE_ALIVE_NTFY: + * Alive data from the firmware, as described in + * &struct iwl_alive_ntf_v3 or &struct iwl_alive_ntf_v4 or + * &struct iwl_alive_ntf_v5. + */ + UCODE_ALIVE_NTFY = 0x1, + + /** + * @REPLY_ERROR: Cause an error in the firmware, for testing purposes. + */ + REPLY_ERROR = 0x2, + + /** + * @ECHO_CMD: Send data to the device to have it returned immediately. + */ + ECHO_CMD = 0x3, + + /** + * @INIT_COMPLETE_NOTIF: Notification that initialization is complete. + */ + INIT_COMPLETE_NOTIF = 0x4, + + /** + * @PHY_CONTEXT_CMD: + * Add/modify/remove a PHY context, using &struct iwl_phy_context_cmd. + */ + PHY_CONTEXT_CMD = 0x8, + + /** + * @DBG_CFG: Debug configuration command. + */ + DBG_CFG = 0x9, + + /** + * @SCAN_ITERATION_COMPLETE_UMAC: + * Firmware indicates a scan iteration completed, using + * &struct iwl_umac_scan_iter_complete_notif. + */ + SCAN_ITERATION_COMPLETE_UMAC = 0xb5, + + /** + * @SCAN_CFG_CMD: + * uses &struct iwl_scan_config_v1 or &struct iwl_scan_config + */ + SCAN_CFG_CMD = 0xc, + + /** + * @SCAN_REQ_UMAC: uses &struct iwl_scan_req_umac + */ + SCAN_REQ_UMAC = 0xd, + + /** + * @SCAN_ABORT_UMAC: uses &struct iwl_umac_scan_abort + */ + SCAN_ABORT_UMAC = 0xe, + + /** + * @SCAN_COMPLETE_UMAC: uses &struct iwl_umac_scan_complete + */ + SCAN_COMPLETE_UMAC = 0xf, + + /** + * @BA_WINDOW_STATUS_NOTIFICATION_ID: + * uses &struct iwl_ba_window_status_notif + */ + BA_WINDOW_STATUS_NOTIFICATION_ID = 0x13, + + /** + * @ADD_STA_KEY: + * &struct iwl_mvm_add_sta_key_cmd_v1 or + * &struct iwl_mvm_add_sta_key_cmd. + */ + ADD_STA_KEY = 0x17, + + /** + * @ADD_STA: + * &struct iwl_mvm_add_sta_cmd or &struct iwl_mvm_add_sta_cmd_v7. + */ + ADD_STA = 0x18, + + /** + * @REMOVE_STA: &struct iwl_mvm_rm_sta_cmd + */ + REMOVE_STA = 0x19, + + /** + * @FW_GET_ITEM_CMD: uses &struct iwl_fw_get_item_cmd + */ + FW_GET_ITEM_CMD = 0x1a, + + /** + * @TX_CMD: uses &struct iwl_tx_cmd or &struct iwl_tx_cmd_gen2 or + * &struct iwl_tx_cmd_gen3, + * response in &struct iwl_mvm_tx_resp or + * &struct iwl_mvm_tx_resp_v3 + */ + TX_CMD = 0x1c, + + /** + * @TXPATH_FLUSH: &struct iwl_tx_path_flush_cmd + */ + TXPATH_FLUSH = 0x1e, + + /** + * @MGMT_MCAST_KEY: + * &struct iwl_mvm_mgmt_mcast_key_cmd or + * &struct iwl_mvm_mgmt_mcast_key_cmd_v1 + */ + MGMT_MCAST_KEY = 0x1f, + + /* scheduler config */ + /** + * @SCD_QUEUE_CFG: &struct iwl_scd_txq_cfg_cmd for older hardware, + * &struct iwl_tx_queue_cfg_cmd with &struct iwl_tx_queue_cfg_rsp + * for newer (22000) hardware. + */ + SCD_QUEUE_CFG = 0x1d, + + /** + * @WEP_KEY: uses &struct iwl_mvm_wep_key_cmd + */ + WEP_KEY = 0x20, + + /** + * @SHARED_MEM_CFG: + * retrieve shared memory configuration - response in + * &struct iwl_shared_mem_cfg + */ + SHARED_MEM_CFG = 0x25, + + /** + * @TDLS_CHANNEL_SWITCH_CMD: uses &struct iwl_tdls_channel_switch_cmd + */ + TDLS_CHANNEL_SWITCH_CMD = 0x27, + + /** + * @TDLS_CHANNEL_SWITCH_NOTIFICATION: + * uses &struct iwl_tdls_channel_switch_notif + */ + TDLS_CHANNEL_SWITCH_NOTIFICATION = 0xaa, + + /** + * @TDLS_CONFIG_CMD: + * &struct iwl_tdls_config_cmd, response in &struct iwl_tdls_config_res + */ + TDLS_CONFIG_CMD = 0xa7, + + /** + * @MAC_CONTEXT_CMD: &struct iwl_mac_ctx_cmd + */ + MAC_CONTEXT_CMD = 0x28, + + /** + * @TIME_EVENT_CMD: + * &struct iwl_time_event_cmd, response in &struct iwl_time_event_resp + */ + TIME_EVENT_CMD = 0x29, /* both CMD and response */ + + /** + * @TIME_EVENT_NOTIFICATION: &struct iwl_time_event_notif + */ + TIME_EVENT_NOTIFICATION = 0x2a, + + /** + * @BINDING_CONTEXT_CMD: + * &struct iwl_binding_cmd or &struct iwl_binding_cmd_v1 + */ + BINDING_CONTEXT_CMD = 0x2b, + + /** + * @TIME_QUOTA_CMD: &struct iwl_time_quota_cmd + */ + TIME_QUOTA_CMD = 0x2c, + + /** + * @NON_QOS_TX_COUNTER_CMD: + * command is &struct iwl_nonqos_seq_query_cmd + */ + NON_QOS_TX_COUNTER_CMD = 0x2d, + + /** + * @LEDS_CMD: command is &struct iwl_led_cmd + */ + LEDS_CMD = 0x48, + + /** + * @LQ_CMD: using &struct iwl_lq_cmd + */ + LQ_CMD = 0x4e, + + /** + * @FW_PAGING_BLOCK_CMD: + * &struct iwl_fw_paging_cmd + */ + FW_PAGING_BLOCK_CMD = 0x4f, + + /** + * @SCAN_OFFLOAD_REQUEST_CMD: uses &struct iwl_scan_req_lmac + */ + SCAN_OFFLOAD_REQUEST_CMD = 0x51, + + /** + * @SCAN_OFFLOAD_ABORT_CMD: abort the scan - no further contents + */ + SCAN_OFFLOAD_ABORT_CMD = 0x52, + + /** + * @HOT_SPOT_CMD: uses &struct iwl_hs20_roc_req + */ + HOT_SPOT_CMD = 0x53, + + /** + * @SCAN_OFFLOAD_COMPLETE: + * notification, &struct iwl_periodic_scan_complete + */ + SCAN_OFFLOAD_COMPLETE = 0x6D, + + /** + * @SCAN_OFFLOAD_UPDATE_PROFILES_CMD: + * update scan offload (scheduled scan) profiles/blocklist/etc. + */ + SCAN_OFFLOAD_UPDATE_PROFILES_CMD = 0x6E, + + /** + * @MATCH_FOUND_NOTIFICATION: scan match found + */ + MATCH_FOUND_NOTIFICATION = 0xd9, + + /** + * @SCAN_ITERATION_COMPLETE: + * uses &struct iwl_lmac_scan_complete_notif + */ + SCAN_ITERATION_COMPLETE = 0xe7, + + /* Phy */ + /** + * @PHY_CONFIGURATION_CMD: &struct iwl_phy_cfg_cmd_v1 or &struct iwl_phy_cfg_cmd_v3 + */ + PHY_CONFIGURATION_CMD = 0x6a, + + /** + * @CALIB_RES_NOTIF_PHY_DB: &struct iwl_calib_res_notif_phy_db + */ + CALIB_RES_NOTIF_PHY_DB = 0x6b, + + /** + * @PHY_DB_CMD: &struct iwl_phy_db_cmd + */ + PHY_DB_CMD = 0x6c, + + /** + * @POWER_TABLE_CMD: &struct iwl_device_power_cmd + */ + POWER_TABLE_CMD = 0x77, + + /** + * @PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION: + * &struct iwl_uapsd_misbehaving_ap_notif + */ + PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION = 0x78, + + /** + * @LTR_CONFIG: &struct iwl_ltr_config_cmd + */ + LTR_CONFIG = 0xee, + + /** + * @REPLY_THERMAL_MNG_BACKOFF: + * Thermal throttling command + */ + REPLY_THERMAL_MNG_BACKOFF = 0x7e, + + /** + * @DC2DC_CONFIG_CMD: + * Set/Get DC2DC frequency tune + * Command is &struct iwl_dc2dc_config_cmd, + * response is &struct iwl_dc2dc_config_resp + */ + DC2DC_CONFIG_CMD = 0x83, + + /** + * @NVM_ACCESS_CMD: using &struct iwl_nvm_access_cmd + */ + NVM_ACCESS_CMD = 0x88, + + /** + * @BEACON_NOTIFICATION: &struct iwl_extended_beacon_notif + */ + BEACON_NOTIFICATION = 0x90, + + /** + * @BEACON_TEMPLATE_CMD: + * Uses one of &struct iwl_mac_beacon_cmd_v6, + * &struct iwl_mac_beacon_cmd_v7 or &struct iwl_mac_beacon_cmd + * depending on the device version. + */ + BEACON_TEMPLATE_CMD = 0x91, + /** + * @TX_ANT_CONFIGURATION_CMD: &struct iwl_tx_ant_cfg_cmd + */ + TX_ANT_CONFIGURATION_CMD = 0x98, + + /** + * @STATISTICS_CMD: + * one of &struct iwl_statistics_cmd, + * &struct iwl_notif_statistics_v11, + * &struct iwl_notif_statistics_v10, + * &struct iwl_notif_statistics, + * &struct iwl_statistics_operational_ntfy + */ + STATISTICS_CMD = 0x9c, + + /** + * @STATISTICS_NOTIFICATION: + * one of &struct iwl_notif_statistics_v10, + * &struct iwl_notif_statistics_v11, + * &struct iwl_notif_statistic, + * &struct iwl_statistics_operational_ntfy + */ + STATISTICS_NOTIFICATION = 0x9d, + + /** + * @EOSP_NOTIFICATION: + * Notify that a service period ended, + * &struct iwl_mvm_eosp_notification + */ + EOSP_NOTIFICATION = 0x9e, + + /** + * @REDUCE_TX_POWER_CMD: + * &struct iwl_dev_tx_power_cmd + */ + REDUCE_TX_POWER_CMD = 0x9f, + + /** + * @CARD_STATE_NOTIFICATION: + * Card state (RF/CT kill) notification, + * uses &struct iwl_card_state_notif + */ + CARD_STATE_NOTIFICATION = 0xa1, + + /** + * @MISSED_BEACONS_NOTIFICATION: &struct iwl_missed_beacons_notif + */ + MISSED_BEACONS_NOTIFICATION = 0xa2, + + /** + * @MAC_PM_POWER_TABLE: using &struct iwl_mac_power_cmd + */ + MAC_PM_POWER_TABLE = 0xa9, + + /** + * @MFUART_LOAD_NOTIFICATION: &struct iwl_mfuart_load_notif + */ + MFUART_LOAD_NOTIFICATION = 0xb1, + + /** + * @RSS_CONFIG_CMD: &struct iwl_rss_config_cmd + */ + RSS_CONFIG_CMD = 0xb3, + + /** + * @REPLY_RX_PHY_CMD: &struct iwl_rx_phy_info + */ + REPLY_RX_PHY_CMD = 0xc0, + + /** + * @REPLY_RX_MPDU_CMD: + * &struct iwl_rx_mpdu_res_start or &struct iwl_rx_mpdu_desc + */ + REPLY_RX_MPDU_CMD = 0xc1, + + /** + * @BAR_FRAME_RELEASE: Frame release from BAR notification, used for + * multi-TID BAR (previously, the BAR frame itself was reported + * instead). Uses &struct iwl_bar_frame_release. + */ + BAR_FRAME_RELEASE = 0xc2, + + /** + * @FRAME_RELEASE: + * Frame release (reorder helper) notification, uses + * &struct iwl_frame_release + */ + FRAME_RELEASE = 0xc3, + + /** + * @BA_NOTIF: + * BlockAck notification, uses &struct iwl_mvm_compressed_ba_notif + * or &struct iwl_mvm_ba_notif depending on the HW + */ + BA_NOTIF = 0xc5, + + /* Location Aware Regulatory */ + /** + * @MCC_UPDATE_CMD: using &struct iwl_mcc_update_cmd + */ + MCC_UPDATE_CMD = 0xc8, + + /** + * @MCC_CHUB_UPDATE_CMD: using &struct iwl_mcc_chub_notif + */ + MCC_CHUB_UPDATE_CMD = 0xc9, + + /** + * @MARKER_CMD: trace marker command, uses &struct iwl_mvm_marker + * with &struct iwl_mvm_marker_rsp + */ + MARKER_CMD = 0xcb, + + /** + * @BT_PROFILE_NOTIFICATION: &struct iwl_bt_coex_profile_notif + */ + BT_PROFILE_NOTIFICATION = 0xce, + + /** + * @BT_CONFIG: &struct iwl_bt_coex_cmd + */ + BT_CONFIG = 0x9b, + + /** + * @BT_COEX_UPDATE_REDUCED_TXP: + * &struct iwl_bt_coex_reduced_txp_update_cmd + */ + BT_COEX_UPDATE_REDUCED_TXP = 0x5c, + + /** + * @BT_COEX_CI: &struct iwl_bt_coex_ci_cmd + */ + BT_COEX_CI = 0x5d, + + /** + * @REPLY_SF_CFG_CMD: &struct iwl_sf_cfg_cmd + */ + REPLY_SF_CFG_CMD = 0xd1, + /** + * @REPLY_BEACON_FILTERING_CMD: &struct iwl_beacon_filter_cmd + */ + REPLY_BEACON_FILTERING_CMD = 0xd2, + + /** + * @DTS_MEASUREMENT_NOTIFICATION: + * &struct iwl_dts_measurement_notif_v1 or + * &struct iwl_dts_measurement_notif_v2 + */ + DTS_MEASUREMENT_NOTIFICATION = 0xdd, + + /** + * @LDBG_CONFIG_CMD: configure continuous trace recording + */ + LDBG_CONFIG_CMD = 0xf6, + + /** + * @DEBUG_LOG_MSG: Debugging log data from firmware + */ + DEBUG_LOG_MSG = 0xf7, + + /** + * @BCAST_FILTER_CMD: &struct iwl_bcast_filter_cmd + */ + BCAST_FILTER_CMD = 0xcf, + + /** + * @MCAST_FILTER_CMD: &struct iwl_mcast_filter_cmd + */ + MCAST_FILTER_CMD = 0xd0, + + /** + * @D3_CONFIG_CMD: &struct iwl_d3_manager_config + */ + D3_CONFIG_CMD = 0xd3, + + /** + * @PROT_OFFLOAD_CONFIG_CMD: Depending on firmware, uses one of + * &struct iwl_proto_offload_cmd_v1, &struct iwl_proto_offload_cmd_v2, + * &struct iwl_proto_offload_cmd_v3_small, + * &struct iwl_proto_offload_cmd_v3_large + */ + PROT_OFFLOAD_CONFIG_CMD = 0xd4, + + /** + * @OFFLOADS_QUERY_CMD: + * No data in command, response in &struct iwl_wowlan_status + */ + OFFLOADS_QUERY_CMD = 0xd5, + + /** + * @D0I3_END_CMD: End D0i3/D3 state, no command data + */ + D0I3_END_CMD = 0xed, + + /** + * @WOWLAN_PATTERNS: &struct iwl_wowlan_patterns_cmd + */ + WOWLAN_PATTERNS = 0xe0, + + /** + * @WOWLAN_CONFIGURATION: &struct iwl_wowlan_config_cmd + */ + WOWLAN_CONFIGURATION = 0xe1, + + /** + * @WOWLAN_TSC_RSC_PARAM: &struct iwl_wowlan_rsc_tsc_params_cmd_v4, + * &struct iwl_wowlan_rsc_tsc_params_cmd + */ + WOWLAN_TSC_RSC_PARAM = 0xe2, + + /** + * @WOWLAN_TKIP_PARAM: &struct iwl_wowlan_tkip_params_cmd + */ + WOWLAN_TKIP_PARAM = 0xe3, + + /** + * @WOWLAN_KEK_KCK_MATERIAL: &struct iwl_wowlan_kek_kck_material_cmd + */ + WOWLAN_KEK_KCK_MATERIAL = 0xe4, + + /** + * @WOWLAN_GET_STATUSES: response in &struct iwl_wowlan_status + */ + WOWLAN_GET_STATUSES = 0xe5, + + /** + * @SCAN_OFFLOAD_PROFILES_QUERY_CMD: + * No command data, response is &struct iwl_scan_offload_profiles_query + */ + SCAN_OFFLOAD_PROFILES_QUERY_CMD = 0x56, +}; + +/** + * enum iwl_system_subcmd_ids - system group command IDs + */ +enum iwl_system_subcmd_ids { + /** + * @SHARED_MEM_CFG_CMD: + * response in &struct iwl_shared_mem_cfg or + * &struct iwl_shared_mem_cfg_v2 + */ + SHARED_MEM_CFG_CMD = 0x0, + + /** + * @SOC_CONFIGURATION_CMD: &struct iwl_soc_configuration_cmd + */ + SOC_CONFIGURATION_CMD = 0x01, + + /** + * @INIT_EXTENDED_CFG_CMD: &struct iwl_init_extended_cfg_cmd + */ + INIT_EXTENDED_CFG_CMD = 0x03, + + /** + * @FW_ERROR_RECOVERY_CMD: &struct iwl_fw_error_recovery_cmd + */ + FW_ERROR_RECOVERY_CMD = 0x7, + + /** + * @RFI_CONFIG_CMD: &struct iwl_rfi_config_cmd + */ + RFI_CONFIG_CMD = 0xb, + + /** + * @RFI_GET_FREQ_TABLE_CMD: &struct iwl_rfi_config_cmd + */ + RFI_GET_FREQ_TABLE_CMD = 0xc, +}; + +#endif /* __iwl_fw_api_commands_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/config.h b/sys/contrib/dev/iwlwifi/fw/api/config.h new file mode 100644 index 00000000000..1ab92f62c41 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/config.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2019 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_config_h__ +#define __iwl_fw_api_config_h__ + +/* + * struct iwl_dqa_enable_cmd + * @cmd_queue: the TXQ number of the command queue + */ +struct iwl_dqa_enable_cmd { + __le32 cmd_queue; +} __packed; /* DQA_CONTROL_CMD_API_S_VER_1 */ + +/* + * struct iwl_tx_ant_cfg_cmd + * @valid: valid antenna configuration + */ +struct iwl_tx_ant_cfg_cmd { + __le32 valid; +} __packed; + +/** + * struct iwl_calib_ctrl - Calibration control struct. + * Sent as part of the phy configuration command. + * @flow_trigger: bitmap for which calibrations to perform according to + * flow triggers, using &enum iwl_calib_cfg + * @event_trigger: bitmap for which calibrations to perform according to + * event triggers, using &enum iwl_calib_cfg + */ +struct iwl_calib_ctrl { + __le32 flow_trigger; + __le32 event_trigger; +} __packed; + +/* This enum defines the bitmap of various calibrations to enable in both + * init ucode and runtime ucode through CALIBRATION_CFG_CMD. + */ +enum iwl_calib_cfg { + IWL_CALIB_CFG_XTAL_IDX = BIT(0), + IWL_CALIB_CFG_TEMPERATURE_IDX = BIT(1), + IWL_CALIB_CFG_VOLTAGE_READ_IDX = BIT(2), + IWL_CALIB_CFG_PAPD_IDX = BIT(3), + IWL_CALIB_CFG_TX_PWR_IDX = BIT(4), + IWL_CALIB_CFG_DC_IDX = BIT(5), + IWL_CALIB_CFG_BB_FILTER_IDX = BIT(6), + IWL_CALIB_CFG_LO_LEAKAGE_IDX = BIT(7), + IWL_CALIB_CFG_TX_IQ_IDX = BIT(8), + IWL_CALIB_CFG_TX_IQ_SKEW_IDX = BIT(9), + IWL_CALIB_CFG_RX_IQ_IDX = BIT(10), + IWL_CALIB_CFG_RX_IQ_SKEW_IDX = BIT(11), + IWL_CALIB_CFG_SENSITIVITY_IDX = BIT(12), + IWL_CALIB_CFG_CHAIN_NOISE_IDX = BIT(13), + IWL_CALIB_CFG_DISCONNECTED_ANT_IDX = BIT(14), + IWL_CALIB_CFG_ANT_COUPLING_IDX = BIT(15), + IWL_CALIB_CFG_DAC_IDX = BIT(16), + IWL_CALIB_CFG_ABS_IDX = BIT(17), + IWL_CALIB_CFG_AGC_IDX = BIT(18), +}; + +/** + * struct iwl_phy_specific_cfg - specific PHY filter configuration + * + * Sent as part of the phy configuration command (v3) to configure specific FW + * defined PHY filters that can be applied to each antenna. + * + * @filter_cfg_chain_a: filter config id for LMAC1 chain A + * @filter_cfg_chain_b: filter config id for LMAC1 chain B + * @filter_cfg_chain_c: filter config id for LMAC2 chain A + * @filter_cfg_chain_d: filter config id for LMAC2 chain B + * values: 0 - no filter; 0xffffffff - reserved; otherwise - filter id + */ +struct iwl_phy_specific_cfg { + __le32 filter_cfg_chain_a; + __le32 filter_cfg_chain_b; + __le32 filter_cfg_chain_c; + __le32 filter_cfg_chain_d; +} __packed; /* PHY_SPECIFIC_CONFIGURATION_API_VER_1*/ + +/** + * struct iwl_phy_cfg_cmd - Phy configuration command + * + * @phy_cfg: PHY configuration value, uses &enum iwl_fw_phy_cfg + * @calib_control: calibration control data + */ +struct iwl_phy_cfg_cmd_v1 { + __le32 phy_cfg; + struct iwl_calib_ctrl calib_control; +} __packed; + +/** + * struct iwl_phy_cfg_cmd_v3 - Phy configuration command (v3) + * + * @phy_cfg: PHY configuration value, uses &enum iwl_fw_phy_cfg + * @calib_control: calibration control data + * @phy_specific_cfg: configure predefined PHY filters + */ +struct iwl_phy_cfg_cmd_v3 { + __le32 phy_cfg; + struct iwl_calib_ctrl calib_control; + struct iwl_phy_specific_cfg phy_specific_cfg; +} __packed; /* PHY_CONFIGURATION_CMD_API_S_VER_3 */ + +/* + * enum iwl_dc2dc_config_id - flag ids + * + * Ids of dc2dc configuration flags + */ +enum iwl_dc2dc_config_id { + DCDC_LOW_POWER_MODE_MSK_SET = 0x1, /* not used */ + DCDC_FREQ_TUNE_SET = 0x2, +}; /* MARKER_ID_API_E_VER_1 */ + +/** + * struct iwl_dc2dc_config_cmd - configure dc2dc values + * + * (DC2DC_CONFIG_CMD = 0x83) + * + * Set/Get & configure dc2dc values. + * The command always returns the current dc2dc values. + * + * @flags: set/get dc2dc + * @enable_low_power_mode: not used. + * @dc2dc_freq_tune0: frequency divider - digital domain + * @dc2dc_freq_tune1: frequency divider - analog domain + */ +struct iwl_dc2dc_config_cmd { + __le32 flags; + __le32 enable_low_power_mode; /* not used */ + __le32 dc2dc_freq_tune0; + __le32 dc2dc_freq_tune1; +} __packed; /* DC2DC_CONFIG_CMD_API_S_VER_1 */ + +/** + * struct iwl_dc2dc_config_resp - response for iwl_dc2dc_config_cmd + * + * Current dc2dc values returned by the FW. + * + * @dc2dc_freq_tune0: frequency divider - digital domain + * @dc2dc_freq_tune1: frequency divider - analog domain + */ +struct iwl_dc2dc_config_resp { + __le32 dc2dc_freq_tune0; + __le32 dc2dc_freq_tune1; +} __packed; /* DC2DC_CONFIG_RESP_API_S_VER_1 */ + +#endif /* __iwl_fw_api_config_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/context.h b/sys/contrib/dev/iwlwifi/fw/api/context.h new file mode 100644 index 00000000000..105ba7170c3 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/context.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_context_h__ +#define __iwl_fw_api_context_h__ + +/** + * enum iwl_ctxt_id_and_color - ID and color fields in context dword + * @FW_CTXT_ID_POS: position of the ID + * @FW_CTXT_ID_MSK: mask of the ID + * @FW_CTXT_COLOR_POS: position of the color + * @FW_CTXT_COLOR_MSK: mask of the color + * @FW_CTXT_INVALID: value used to indicate unused/invalid + */ +enum iwl_ctxt_id_and_color { + FW_CTXT_ID_POS = 0, + FW_CTXT_ID_MSK = 0xff << FW_CTXT_ID_POS, + FW_CTXT_COLOR_POS = 8, + FW_CTXT_COLOR_MSK = 0xff << FW_CTXT_COLOR_POS, + FW_CTXT_INVALID = 0xffffffff, +}; + +#define FW_CMD_ID_AND_COLOR(_id, _color) (((_id) << FW_CTXT_ID_POS) |\ + ((_color) << FW_CTXT_COLOR_POS)) + +/* Possible actions on PHYs, MACs and Bindings */ +enum iwl_ctxt_action { + FW_CTXT_ACTION_STUB = 0, + FW_CTXT_ACTION_ADD, + FW_CTXT_ACTION_MODIFY, + FW_CTXT_ACTION_REMOVE, + FW_CTXT_ACTION_NUM +}; /* COMMON_CONTEXT_ACTION_API_E_VER_1 */ + +#endif /* __iwl_fw_api_context_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/d3.h b/sys/contrib/dev/iwlwifi/fw/api/d3.h new file mode 100644 index 00000000000..1503119ea91 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/d3.h @@ -0,0 +1,698 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_d3_h__ +#define __iwl_fw_api_d3_h__ +#include + +/** + * enum iwl_d0i3_flags - d0i3 flags + * @IWL_D0I3_RESET_REQUIRE: FW require reset upon resume + */ +enum iwl_d0i3_flags { + IWL_D0I3_RESET_REQUIRE = BIT(0), +}; + +/** + * enum iwl_d3_wakeup_flags - D3 manager wakeup flags + * @IWL_WAKEUP_D3_CONFIG_FW_ERROR: wake up on firmware sysassert + */ +enum iwl_d3_wakeup_flags { + IWL_WAKEUP_D3_CONFIG_FW_ERROR = BIT(0), +}; /* D3_MANAGER_WAKEUP_CONFIG_API_E_VER_3 */ + +/** + * struct iwl_d3_manager_config - D3 manager configuration command + * @min_sleep_time: minimum sleep time (in usec) + * @wakeup_flags: wakeup flags, see &enum iwl_d3_wakeup_flags + * @wakeup_host_timer: force wakeup after this many seconds + * + * The structure is used for the D3_CONFIG_CMD command. + */ +struct iwl_d3_manager_config { + __le32 min_sleep_time; + __le32 wakeup_flags; + __le32 wakeup_host_timer; +} __packed; /* D3_MANAGER_CONFIG_CMD_S_VER_4 */ + + +/* TODO: OFFLOADS_QUERY_API_S_VER_1 */ + +/** + * enum iwl_d3_proto_offloads - enabled protocol offloads + * @IWL_D3_PROTO_OFFLOAD_ARP: ARP data is enabled + * @IWL_D3_PROTO_OFFLOAD_NS: NS (Neighbor Solicitation) is enabled + * @IWL_D3_PROTO_IPV4_VALID: IPv4 data is valid + * @IWL_D3_PROTO_IPV6_VALID: IPv6 data is valid + */ +enum iwl_proto_offloads { + IWL_D3_PROTO_OFFLOAD_ARP = BIT(0), + IWL_D3_PROTO_OFFLOAD_NS = BIT(1), + IWL_D3_PROTO_IPV4_VALID = BIT(2), + IWL_D3_PROTO_IPV6_VALID = BIT(3), +}; + +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V1 2 +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V2 6 +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L 12 +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3S 4 +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX 12 + +#define IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L 4 +#define IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3S 2 + +/** + * struct iwl_proto_offload_cmd_common - ARP/NS offload common part + * @enabled: enable flags + * @remote_ipv4_addr: remote address to answer to (or zero if all) + * @host_ipv4_addr: our IPv4 address to respond to queries for + * @arp_mac_addr: our MAC address for ARP responses + * @reserved: unused + */ +struct iwl_proto_offload_cmd_common { + __le32 enabled; + __be32 remote_ipv4_addr; + __be32 host_ipv4_addr; + u8 arp_mac_addr[ETH_ALEN]; + __le16 reserved; +} __packed; + +/** + * struct iwl_proto_offload_cmd_v1 - ARP/NS offload configuration + * @common: common/IPv4 configuration + * @remote_ipv6_addr: remote address to answer to (or zero if all) + * @solicited_node_ipv6_addr: broken -- solicited node address exists + * for each target address + * @target_ipv6_addr: our target addresses + * @ndp_mac_addr: neighbor solicitation response MAC address + * @reserved2: reserved + */ +struct iwl_proto_offload_cmd_v1 { + struct iwl_proto_offload_cmd_common common; + u8 remote_ipv6_addr[16]; + u8 solicited_node_ipv6_addr[16]; + u8 target_ipv6_addr[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V1][16]; + u8 ndp_mac_addr[ETH_ALEN]; + __le16 reserved2; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_1 */ + +/** + * struct iwl_proto_offload_cmd_v2 - ARP/NS offload configuration + * @common: common/IPv4 configuration + * @remote_ipv6_addr: remote address to answer to (or zero if all) + * @solicited_node_ipv6_addr: broken -- solicited node address exists + * for each target address + * @target_ipv6_addr: our target addresses + * @ndp_mac_addr: neighbor solicitation response MAC address + * @num_valid_ipv6_addrs: number of valid IPv6 addresses + * @reserved2: reserved + */ +struct iwl_proto_offload_cmd_v2 { + struct iwl_proto_offload_cmd_common common; + u8 remote_ipv6_addr[16]; + u8 solicited_node_ipv6_addr[16]; + u8 target_ipv6_addr[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V2][16]; + u8 ndp_mac_addr[ETH_ALEN]; + u8 num_valid_ipv6_addrs; + u8 reserved2[3]; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_2 */ + +struct iwl_ns_config { + struct in6_addr source_ipv6_addr; + struct in6_addr dest_ipv6_addr; + u8 target_mac_addr[ETH_ALEN]; + __le16 reserved; +} __packed; /* NS_OFFLOAD_CONFIG */ + +struct iwl_targ_addr { + struct in6_addr addr; + __le32 config_num; +} __packed; /* TARGET_IPV6_ADDRESS */ + +/** + * struct iwl_proto_offload_cmd_v3_small - ARP/NS offload configuration + * @common: common/IPv4 configuration + * @num_valid_ipv6_addrs: number of valid IPv6 addresses + * @targ_addrs: target IPv6 addresses + * @ns_config: NS offload configurations + */ +struct iwl_proto_offload_cmd_v3_small { + struct iwl_proto_offload_cmd_common common; + __le32 num_valid_ipv6_addrs; + struct iwl_targ_addr targ_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3S]; + struct iwl_ns_config ns_config[IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3S]; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_3 */ + +/** + * struct iwl_proto_offload_cmd_v3_large - ARP/NS offload configuration + * @common: common/IPv4 configuration + * @num_valid_ipv6_addrs: number of valid IPv6 addresses + * @targ_addrs: target IPv6 addresses + * @ns_config: NS offload configurations + */ +struct iwl_proto_offload_cmd_v3_large { + struct iwl_proto_offload_cmd_common common; + __le32 num_valid_ipv6_addrs; + struct iwl_targ_addr targ_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L]; + struct iwl_ns_config ns_config[IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L]; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_3 */ + +/** + * struct iwl_proto_offload_cmd_v4 - ARP/NS offload configuration + * @sta_id: station id + * @common: common/IPv4 configuration + * @num_valid_ipv6_addrs: number of valid IPv6 addresses + * @targ_addrs: target IPv6 addresses + * @ns_config: NS offload configurations + */ +struct iwl_proto_offload_cmd_v4 { + __le32 sta_id; + struct iwl_proto_offload_cmd_common common; + __le32 num_valid_ipv6_addrs; + struct iwl_targ_addr targ_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L]; + struct iwl_ns_config ns_config[IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L]; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_4 */ + +/* + * WOWLAN_PATTERNS + */ +#define IWL_WOWLAN_MIN_PATTERN_LEN 16 +#define IWL_WOWLAN_MAX_PATTERN_LEN 128 + +struct iwl_wowlan_pattern_v1 { + u8 mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8]; + u8 pattern[IWL_WOWLAN_MAX_PATTERN_LEN]; + u8 mask_size; + u8 pattern_size; + __le16 reserved; +} __packed; /* WOWLAN_PATTERN_API_S_VER_1 */ + +#define IWL_WOWLAN_MAX_PATTERNS 20 + +/** + * struct iwl_wowlan_patterns_cmd - WoWLAN wakeup patterns + */ +struct iwl_wowlan_patterns_cmd_v1 { + /** + * @n_patterns: number of patterns + */ + __le32 n_patterns; + + /** + * @patterns: the patterns, array length in @n_patterns + */ + struct iwl_wowlan_pattern_v1 patterns[]; +} __packed; /* WOWLAN_PATTERN_ARRAY_API_S_VER_1 */ + +#define IPV4_ADDR_SIZE 4 +#define IPV6_ADDR_SIZE 16 + +enum iwl_wowlan_pattern_type { + WOWLAN_PATTERN_TYPE_BITMASK, + WOWLAN_PATTERN_TYPE_IPV4_TCP_SYN, + WOWLAN_PATTERN_TYPE_IPV6_TCP_SYN, + WOWLAN_PATTERN_TYPE_IPV4_TCP_SYN_WILDCARD, + WOWLAN_PATTERN_TYPE_IPV6_TCP_SYN_WILDCARD, +}; /* WOWLAN_PATTERN_TYPE_API_E_VER_1 */ + +/** + * struct iwl_wowlan_ipv4_tcp_syn - WoWLAN IPv4 TCP SYN pattern data + */ +struct iwl_wowlan_ipv4_tcp_syn { + /** + * @src_addr: source IP address to match + */ + u8 src_addr[IPV4_ADDR_SIZE]; + + /** + * @dst_addr: destination IP address to match + */ + u8 dst_addr[IPV4_ADDR_SIZE]; + + /** + * @src_port: source TCP port to match + */ + __le16 src_port; + + /** + * @dst_port: destination TCP port to match + */ + __le16 dst_port; +} __packed; /* WOWLAN_IPV4_TCP_SYN_API_S_VER_1 */ + +/** + * struct iwl_wowlan_ipv6_tcp_syn - WoWLAN Ipv6 TCP SYN pattern data + */ +struct iwl_wowlan_ipv6_tcp_syn { + /** + * @src_addr: source IP address to match + */ + u8 src_addr[IPV6_ADDR_SIZE]; + + /** + * @dst_addr: destination IP address to match + */ + u8 dst_addr[IPV6_ADDR_SIZE]; + + /** + * @src_port: source TCP port to match + */ + __le16 src_port; + + /** + * @dst_port: destination TCP port to match + */ + __le16 dst_port; +} __packed; /* WOWLAN_IPV6_TCP_SYN_API_S_VER_1 */ + +/** + * union iwl_wowlan_pattern_data - Data for the different pattern types + * + * If wildcard addresses/ports are to be used, the union can be left + * undefined. + */ +union iwl_wowlan_pattern_data { + /** + * @bitmask: bitmask pattern data + */ + struct iwl_wowlan_pattern_v1 bitmask; + + /** + * @ipv4_tcp_syn: IPv4 TCP SYN pattern data + */ + struct iwl_wowlan_ipv4_tcp_syn ipv4_tcp_syn; + + /** + * @ipv6_tcp_syn: IPv6 TCP SYN pattern data + */ + struct iwl_wowlan_ipv6_tcp_syn ipv6_tcp_syn; +}; /* WOWLAN_PATTERN_API_U_VER_1 */ + +/** + * struct iwl_wowlan_pattern_v2 - Pattern entry for the WoWLAN wakeup patterns + */ +struct iwl_wowlan_pattern_v2 { + /** + * @pattern_type: defines the struct type to be used in the union + */ + u8 pattern_type; + + /** + * @reserved: reserved for alignment + */ + u8 reserved[3]; + + /** + * @u: the union containing the match data, or undefined for + * wildcard matches + */ + union iwl_wowlan_pattern_data u; +} __packed; /* WOWLAN_PATTERN_API_S_VER_2 */ + +/** + * struct iwl_wowlan_patterns_cmd - WoWLAN wakeup patterns command + */ +struct iwl_wowlan_patterns_cmd { + /** + * @n_patterns: number of patterns + */ + u8 n_patterns; + + /** + * @n_patterns: sta_id + */ + u8 sta_id; + + /** + * @reserved: reserved for alignment + */ + __le16 reserved; + + /** + * @patterns: the patterns, array length in @n_patterns + */ + struct iwl_wowlan_pattern_v2 patterns[]; +} __packed; /* WOWLAN_PATTERN_ARRAY_API_S_VER_3 */ + +enum iwl_wowlan_wakeup_filters { + IWL_WOWLAN_WAKEUP_MAGIC_PACKET = BIT(0), + IWL_WOWLAN_WAKEUP_PATTERN_MATCH = BIT(1), + IWL_WOWLAN_WAKEUP_BEACON_MISS = BIT(2), + IWL_WOWLAN_WAKEUP_LINK_CHANGE = BIT(3), + IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL = BIT(4), + IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ = BIT(5), + IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE = BIT(6), + IWL_WOWLAN_WAKEUP_ENABLE_NET_DETECT = BIT(7), + IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT = BIT(8), + IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS = BIT(9), + IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE = BIT(10), + IWL_WOWLAN_WAKEUP_REMOTE_TCP_EXTERNAL = BIT(11), + IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET = BIT(12), + IWL_WOWLAN_WAKEUP_IOAC_MAGIC_PACKET = BIT(13), + IWL_WOWLAN_WAKEUP_HOST_TIMER = BIT(14), + IWL_WOWLAN_WAKEUP_RX_FRAME = BIT(15), + IWL_WOWLAN_WAKEUP_BCN_FILTERING = BIT(16), +}; /* WOWLAN_WAKEUP_FILTER_API_E_VER_4 */ + +enum iwl_wowlan_flags { + IS_11W_ASSOC = BIT(0), + ENABLE_L3_FILTERING = BIT(1), + ENABLE_NBNS_FILTERING = BIT(2), + ENABLE_DHCP_FILTERING = BIT(3), + ENABLE_STORE_BEACON = BIT(4), +}; + +/** + * struct iwl_wowlan_config_cmd - WoWLAN configuration (versions 5 and 6) + * @wakeup_filter: filter from &enum iwl_wowlan_wakeup_filters + * @non_qos_seq: non-QoS sequence counter to use next. + * Reserved if the struct has version >= 6. + * @qos_seq: QoS sequence counters to use next + * @wowlan_ba_teardown_tids: bitmap of BA sessions to tear down + * @is_11n_connection: indicates HT connection + * @offloading_tid: TID reserved for firmware use + * @flags: extra flags, see &enum iwl_wowlan_flags + * @sta_id: station ID for wowlan. + * @reserved: reserved + */ +struct iwl_wowlan_config_cmd { + __le32 wakeup_filter; + __le16 non_qos_seq; + __le16 qos_seq[8]; + u8 wowlan_ba_teardown_tids; + u8 is_11n_connection; + u8 offloading_tid; + u8 flags; + u8 sta_id; + u8 reserved; +} __packed; /* WOWLAN_CONFIG_API_S_VER_5 */ + +#define IWL_NUM_RSC 16 +#define WOWLAN_KEY_MAX_SIZE 32 +#define WOWLAN_GTK_KEYS_NUM 2 +#define WOWLAN_IGTK_KEYS_NUM 2 + +/* + * WOWLAN_TSC_RSC_PARAMS + */ +struct tkip_sc { + __le16 iv16; + __le16 pad; + __le32 iv32; +} __packed; /* TKIP_SC_API_U_VER_1 */ + +struct iwl_tkip_rsc_tsc { + struct tkip_sc unicast_rsc[IWL_NUM_RSC]; + struct tkip_sc multicast_rsc[IWL_NUM_RSC]; + struct tkip_sc tsc; +} __packed; /* TKIP_TSC_RSC_API_S_VER_1 */ + +struct aes_sc { + __le64 pn; +} __packed; /* TKIP_AES_SC_API_U_VER_1 */ + +struct iwl_aes_rsc_tsc { + struct aes_sc unicast_rsc[IWL_NUM_RSC]; + struct aes_sc multicast_rsc[IWL_NUM_RSC]; + struct aes_sc tsc; +} __packed; /* AES_TSC_RSC_API_S_VER_1 */ + +union iwl_all_tsc_rsc { + struct iwl_tkip_rsc_tsc tkip; + struct iwl_aes_rsc_tsc aes; +}; /* ALL_TSC_RSC_API_S_VER_2 */ + +struct iwl_wowlan_rsc_tsc_params_cmd_ver_2 { + union iwl_all_tsc_rsc all_tsc_rsc; +} __packed; /* ALL_TSC_RSC_API_S_VER_2 */ + +struct iwl_wowlan_rsc_tsc_params_cmd_v4 { + struct iwl_wowlan_rsc_tsc_params_cmd_ver_2 params; + __le32 sta_id; +} __packed; /* ALL_TSC_RSC_API_S_VER_4 */ + +struct iwl_wowlan_rsc_tsc_params_cmd { + __le64 ucast_rsc[IWL_MAX_TID_COUNT]; + __le64 mcast_rsc[WOWLAN_GTK_KEYS_NUM][IWL_MAX_TID_COUNT]; + __le32 sta_id; +#define IWL_MCAST_KEY_MAP_INVALID 0xff + u8 mcast_key_id_map[4]; +} __packed; /* ALL_TSC_RSC_API_S_VER_5 */ + +#define IWL_MIC_KEY_SIZE 8 +struct iwl_mic_keys { + u8 tx[IWL_MIC_KEY_SIZE]; + u8 rx_unicast[IWL_MIC_KEY_SIZE]; + u8 rx_mcast[IWL_MIC_KEY_SIZE]; +} __packed; /* MIC_KEYS_API_S_VER_1 */ + +#define IWL_P1K_SIZE 5 +struct iwl_p1k_cache { + __le16 p1k[IWL_P1K_SIZE]; +} __packed; + +#define IWL_NUM_RX_P1K_CACHE 2 + +struct iwl_wowlan_tkip_params_cmd_ver_1 { + struct iwl_mic_keys mic_keys; + struct iwl_p1k_cache tx; + struct iwl_p1k_cache rx_uni[IWL_NUM_RX_P1K_CACHE]; + struct iwl_p1k_cache rx_multi[IWL_NUM_RX_P1K_CACHE]; +} __packed; /* WOWLAN_TKIP_SETTING_API_S_VER_1 */ + +struct iwl_wowlan_tkip_params_cmd { + struct iwl_mic_keys mic_keys; + struct iwl_p1k_cache tx; + struct iwl_p1k_cache rx_uni[IWL_NUM_RX_P1K_CACHE]; + struct iwl_p1k_cache rx_multi[IWL_NUM_RX_P1K_CACHE]; + u8 reversed[2]; + __le32 sta_id; +} __packed; /* WOWLAN_TKIP_SETTING_API_S_VER_2 */ + +#define IWL_KCK_MAX_SIZE 32 +#define IWL_KEK_MAX_SIZE 32 + +struct iwl_wowlan_kek_kck_material_cmd_v2 { + u8 kck[IWL_KCK_MAX_SIZE]; + u8 kek[IWL_KEK_MAX_SIZE]; + __le16 kck_len; + __le16 kek_len; + __le64 replay_ctr; +} __packed; /* KEK_KCK_MATERIAL_API_S_VER_2 */ + +struct iwl_wowlan_kek_kck_material_cmd_v3 { + u8 kck[IWL_KCK_MAX_SIZE]; + u8 kek[IWL_KEK_MAX_SIZE]; + __le16 kck_len; + __le16 kek_len; + __le64 replay_ctr; + __le32 akm; + __le32 gtk_cipher; + __le32 igtk_cipher; + __le32 bigtk_cipher; +} __packed; /* KEK_KCK_MATERIAL_API_S_VER_3 */ + +struct iwl_wowlan_kek_kck_material_cmd_v4 { + __le32 sta_id; + u8 kck[IWL_KCK_MAX_SIZE]; + u8 kek[IWL_KEK_MAX_SIZE]; + __le16 kck_len; + __le16 kek_len; + __le64 replay_ctr; + __le32 akm; + __le32 gtk_cipher; + __le32 igtk_cipher; + __le32 bigtk_cipher; +} __packed; /* KEK_KCK_MATERIAL_API_S_VER_4 */ + +struct iwl_wowlan_get_status_cmd { + __le32 sta_id; +} __packed; /* WOWLAN_GET_STATUSES_CMD_API_S_VER_1 */ + +#define RF_KILL_INDICATOR_FOR_WOWLAN 0x87 + +enum iwl_wowlan_rekey_status { + IWL_WOWLAN_REKEY_POST_REKEY = 0, + IWL_WOWLAN_REKEY_WHILE_REKEY = 1, +}; /* WOWLAN_REKEY_STATUS_API_E_VER_1 */ + +enum iwl_wowlan_wakeup_reason { + IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS = 0, + IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET = BIT(0), + IWL_WOWLAN_WAKEUP_BY_PATTERN = BIT(1), + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON = BIT(2), + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH = BIT(3), + IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE = BIT(4), + IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED = BIT(5), + IWL_WOWLAN_WAKEUP_BY_UCODE_ERROR = BIT(6), + IWL_WOWLAN_WAKEUP_BY_EAPOL_REQUEST = BIT(7), + IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE = BIT(8), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS = BIT(9), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE = BIT(10), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_TCP_EXTERNAL = BIT(11), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET = BIT(12), + IWL_WOWLAN_WAKEUP_BY_IOAC_MAGIC_PACKET = BIT(13), + IWL_WOWLAN_WAKEUP_BY_D3_WAKEUP_HOST_TIMER = BIT(14), + IWL_WOWLAN_WAKEUP_BY_RXFRAME_FILTERED_IN = BIT(15), + IWL_WOWLAN_WAKEUP_BY_BEACON_FILTERED_IN = BIT(16), + IWL_WAKEUP_BY_11W_UNPROTECTED_DEAUTH_OR_DISASSOC = BIT(17), + IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN = BIT(18), + IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN_WILDCARD = BIT(19), + IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN = BIT(20), + IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN_WILDCARD = BIT(21), +}; /* WOWLAN_WAKE_UP_REASON_API_E_VER_2 */ + +struct iwl_wowlan_gtk_status_v1 { + u8 key_index; + u8 reserved[3]; + u8 decrypt_key[16]; + u8 tkip_mic_key[8]; + struct iwl_wowlan_rsc_tsc_params_cmd_ver_2 rsc; +} __packed; /* WOWLAN_GTK_MATERIAL_VER_1 */ + +/** + * struct iwl_wowlan_gtk_status - GTK status + * @key: GTK material + * @key_len: GTK legth, if set to 0, the key is not available + * @key_flags: information about the key: + * bits[0:1]: key index assigned by the AP + * bits[2:6]: GTK index of the key in the internal DB + * bit[7]: Set iff this is the currently used GTK + * @reserved: padding + * @tkip_mic_key: TKIP RX MIC key + * @rsc: TSC RSC counters + */ +struct iwl_wowlan_gtk_status { + u8 key[WOWLAN_KEY_MAX_SIZE]; + u8 key_len; + u8 key_flags; + u8 reserved[2]; + u8 tkip_mic_key[8]; + struct iwl_wowlan_rsc_tsc_params_cmd_ver_2 rsc; +} __packed; /* WOWLAN_GTK_MATERIAL_VER_2 */ + +#define IWL_WOWLAN_GTK_IDX_MASK (BIT(0) | BIT(1)) + +/** + * struct iwl_wowlan_igtk_status - IGTK status + * @key: IGTK material + * @ipn: the IGTK packet number (replay counter) + * @key_len: IGTK length, if set to 0, the key is not available + * @key_flags: information about the key: + * bits[0]: key index assigned by the AP (0: index 4, 1: index 5) + * bits[1:5]: IGTK index of the key in the internal DB + * bit[6]: Set iff this is the currently used IGTK + */ +struct iwl_wowlan_igtk_status { + u8 key[WOWLAN_KEY_MAX_SIZE]; + u8 ipn[6]; + u8 key_len; + u8 key_flags; +} __packed; /* WOWLAN_IGTK_MATERIAL_VER_1 */ + +/** + * struct iwl_wowlan_status_v6 - WoWLAN status + * @gtk: GTK data + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched pattern + * @non_qos_seq_ctr: non-QoS sequence counter to use next + * @qos_seq_ctr: QoS sequence counters to use next + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @num_of_gtk_rekeys: number of GTK rekeys + * @transmitted_ndps: number of transmitted neighbor discovery packets + * @received_beacons: number of received beacons + * @wake_packet_length: wakeup packet length + * @wake_packet_bufsize: wakeup packet buffer size + * @wake_packet: wakeup packet + */ +struct iwl_wowlan_status_v6 { + struct iwl_wowlan_gtk_status_v1 gtk; + __le64 replay_ctr; + __le16 pattern_number; + __le16 non_qos_seq_ctr; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + __le32 wake_packet_length; + __le32 wake_packet_bufsize; + u8 wake_packet[]; /* can be truncated from _length to _bufsize */ +} __packed; /* WOWLAN_STATUSES_API_S_VER_6 */ + +/** + * struct iwl_wowlan_status_v7 - WoWLAN status + * @gtk: GTK data + * @igtk: IGTK data + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched pattern + * @non_qos_seq_ctr: non-QoS sequence counter to use next + * @qos_seq_ctr: QoS sequence counters to use next + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @num_of_gtk_rekeys: number of GTK rekeys + * @transmitted_ndps: number of transmitted neighbor discovery packets + * @received_beacons: number of received beacons + * @wake_packet_length: wakeup packet length + * @wake_packet_bufsize: wakeup packet buffer size + * @wake_packet: wakeup packet + */ +struct iwl_wowlan_status_v7 { + struct iwl_wowlan_gtk_status gtk[WOWLAN_GTK_KEYS_NUM]; + struct iwl_wowlan_igtk_status igtk[WOWLAN_IGTK_KEYS_NUM]; + __le64 replay_ctr; + __le16 pattern_number; + __le16 non_qos_seq_ctr; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + __le32 wake_packet_length; + __le32 wake_packet_bufsize; + u8 wake_packet[]; /* can be truncated from _length to _bufsize */ +} __packed; /* WOWLAN_STATUSES_API_S_VER_7 */ + +/** + * struct iwl_wowlan_status_v9 - WoWLAN status (versions 9 and 10) + * @gtk: GTK data + * @igtk: IGTK data + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched pattern + * @non_qos_seq_ctr: non-QoS sequence counter to use next. + * Reserved if the struct has version >= 10. + * @qos_seq_ctr: QoS sequence counters to use next + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @num_of_gtk_rekeys: number of GTK rekeys + * @transmitted_ndps: number of transmitted neighbor discovery packets + * @received_beacons: number of received beacons + * @wake_packet_length: wakeup packet length + * @wake_packet_bufsize: wakeup packet buffer size + * @tid_tear_down: bit mask of tids whose BA sessions were closed + * in suspend state + * @reserved: unused + * @wake_packet: wakeup packet + */ +struct iwl_wowlan_status_v9 { + struct iwl_wowlan_gtk_status gtk[WOWLAN_GTK_KEYS_NUM]; + struct iwl_wowlan_igtk_status igtk[WOWLAN_IGTK_KEYS_NUM]; + __le64 replay_ctr; + __le16 pattern_number; + __le16 non_qos_seq_ctr; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + __le32 wake_packet_length; + __le32 wake_packet_bufsize; + u8 tid_tear_down; + u8 reserved[3]; + u8 wake_packet[]; /* can be truncated from _length to _bufsize */ +} __packed; /* WOWLAN_STATUSES_RSP_API_S_VER_9 */ + +/* TODO: NetDetect API */ + +#endif /* __iwl_fw_api_d3_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/datapath.h b/sys/contrib/dev/iwlwifi/fw/api/datapath.h new file mode 100644 index 00000000000..985b0dc5b52 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/datapath.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_datapath_h__ +#define __iwl_fw_api_datapath_h__ + +/** + * enum iwl_data_path_subcmd_ids - data path group commands + */ +enum iwl_data_path_subcmd_ids { + /** + * @DQA_ENABLE_CMD: &struct iwl_dqa_enable_cmd + */ + DQA_ENABLE_CMD = 0x0, + + /** + * @UPDATE_MU_GROUPS_CMD: &struct iwl_mu_group_mgmt_cmd + */ + UPDATE_MU_GROUPS_CMD = 0x1, + + /** + * @TRIGGER_RX_QUEUES_NOTIF_CMD: &struct iwl_rxq_sync_cmd + */ + TRIGGER_RX_QUEUES_NOTIF_CMD = 0x2, + + /** + * @STA_HE_CTXT_CMD: &struct iwl_he_sta_context_cmd + */ + STA_HE_CTXT_CMD = 0x7, + + /** + * @RFH_QUEUE_CONFIG_CMD: &struct iwl_rfh_queue_config + */ + RFH_QUEUE_CONFIG_CMD = 0xD, + + /** + * @TLC_MNG_CONFIG_CMD: &struct iwl_tlc_config_cmd + */ + TLC_MNG_CONFIG_CMD = 0xF, + + /** + * @HE_AIR_SNIFFER_CONFIG_CMD: &struct iwl_he_monitor_cmd + */ + HE_AIR_SNIFFER_CONFIG_CMD = 0x13, + + /** + * @CHEST_COLLECTOR_FILTER_CONFIG_CMD: Configure the CSI + * matrix collection, uses &struct iwl_channel_estimation_cfg + */ + CHEST_COLLECTOR_FILTER_CONFIG_CMD = 0x14, + + /** + * @MONITOR_NOTIF: Datapath monitoring notification, using + * &struct iwl_datapath_monitor_notif + */ + MONITOR_NOTIF = 0xF4, + + /** + * @RX_NO_DATA_NOTIF: &struct iwl_rx_no_data + */ + RX_NO_DATA_NOTIF = 0xF5, + + /** + * @THERMAL_DUAL_CHAIN_DISABLE_REQ: firmware request for SMPS mode, + * &struct iwl_thermal_dual_chain_request + */ + THERMAL_DUAL_CHAIN_REQUEST = 0xF6, + + /** + * @TLC_MNG_UPDATE_NOTIF: &struct iwl_tlc_update_notif + */ + TLC_MNG_UPDATE_NOTIF = 0xF7, + + /** + * @STA_PM_NOTIF: &struct iwl_mvm_pm_state_notification + */ + STA_PM_NOTIF = 0xFD, + + /** + * @MU_GROUP_MGMT_NOTIF: &struct iwl_mu_group_mgmt_notif + */ + MU_GROUP_MGMT_NOTIF = 0xFE, + + /** + * @RX_QUEUES_NOTIFICATION: &struct iwl_rxq_sync_notification + */ + RX_QUEUES_NOTIFICATION = 0xFF, +}; + +/** + * struct iwl_mu_group_mgmt_cmd - VHT MU-MIMO group configuration + * + * @reserved: reserved + * @membership_status: a bitmap of MU groups + * @user_position:the position of station in a group. If the station is in the + * group then bits (group * 2) is the position -1 + */ +struct iwl_mu_group_mgmt_cmd { + __le32 reserved; + __le32 membership_status[2]; + __le32 user_position[4]; +} __packed; /* MU_GROUP_ID_MNG_TABLE_API_S_VER_1 */ + +/** + * struct iwl_mu_group_mgmt_notif - VHT MU-MIMO group id notification + * + * @membership_status: a bitmap of MU groups + * @user_position: the position of station in a group. If the station is in the + * group then bits (group * 2) is the position -1 + */ +struct iwl_mu_group_mgmt_notif { + __le32 membership_status[2]; + __le32 user_position[4]; +} __packed; /* MU_GROUP_MNG_NTFY_API_S_VER_1 */ + +enum iwl_channel_estimation_flags { + IWL_CHANNEL_ESTIMATION_ENABLE = BIT(0), + IWL_CHANNEL_ESTIMATION_TIMER = BIT(1), + IWL_CHANNEL_ESTIMATION_COUNTER = BIT(2), +}; + +/** + * struct iwl_channel_estimation_cfg - channel estimation reporting config + */ +struct iwl_channel_estimation_cfg { + /** + * @flags: flags, see &enum iwl_channel_estimation_flags + */ + __le32 flags; + /** + * @timer: if enabled via flags, automatically disable after this many + * microseconds + */ + __le32 timer; + /** + * @count: if enabled via flags, automatically disable after this many + * frames with channel estimation matrix were captured + */ + __le32 count; + /** + * @rate_n_flags_mask: only try to record the channel estimation matrix + * if the rate_n_flags value for the received frame (let's call + * that rx_rnf) matches the mask/value given here like this: + * (rx_rnf & rate_n_flags_mask) == rate_n_flags_val. + */ + __le32 rate_n_flags_mask; + /** + * @rate_n_flags_val: see @rate_n_flags_mask + */ + __le32 rate_n_flags_val; + /** + * @reserved: reserved (for alignment) + */ + __le32 reserved; + /** + * @frame_types: bitmap of frame types to capture, the received frame's + * subtype|type takes 6 bits in the frame and the corresponding bit + * in this field must be set to 1 to capture channel estimation for + * that frame type. Set to all-ones to enable capturing for all + * frame types. + */ + __le64 frame_types; +} __packed; /* CHEST_COLLECTOR_FILTER_CMD_API_S_VER_1 */ + +enum iwl_datapath_monitor_notif_type { + IWL_DP_MON_NOTIF_TYPE_EXT_CCA, +}; + +struct iwl_datapath_monitor_notif { + __le32 type; + u8 mac_id; + u8 reserved[3]; +} __packed; /* MONITOR_NTF_API_S_VER_1 */ + +/** + * enum iwl_thermal_dual_chain_req_events - firmware SMPS request event + * @THERMAL_DUAL_CHAIN_REQ_ENABLE: (re-)enable dual-chain operation + * (subject to other constraints) + * @THERMAL_DUAL_CHAIN_REQ_DISABLE: disable dual-chain operation + * (static SMPS) + */ +enum iwl_thermal_dual_chain_req_events { + THERMAL_DUAL_CHAIN_REQ_ENABLE, + THERMAL_DUAL_CHAIN_REQ_DISABLE, +}; /* THERMAL_DUAL_CHAIN_DISABLE_STATE_API_E_VER_1 */ + +/** + * struct iwl_thermal_dual_chain_request - SMPS request + * @event: the type of request, see &enum iwl_thermal_dual_chain_req_events + */ +struct iwl_thermal_dual_chain_request { + __le32 event; +} __packed; /* THERMAL_DUAL_CHAIN_DISABLE_REQ_NTFY_API_S_VER_1 */ + +#endif /* __iwl_fw_api_datapath_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/dbg-tlv.h b/sys/contrib/dev/iwlwifi/fw/api/dbg-tlv.h new file mode 100644 index 00000000000..3988f5fea33 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/dbg-tlv.h @@ -0,0 +1,468 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2018-2021 Intel Corporation + */ +#ifndef __iwl_fw_dbg_tlv_h__ +#define __iwl_fw_dbg_tlv_h__ + +#include + +#define IWL_FW_INI_HW_SMEM_REGION_ID 15 +#define IWL_FW_INI_MAX_REGION_ID 64 +#define IWL_FW_INI_MAX_NAME 32 +#define IWL_FW_INI_MAX_CFG_NAME 64 +#define IWL_FW_INI_DOMAIN_ALWAYS_ON 0 +#define IWL_FW_INI_REGION_V2_MASK 0x0000FFFF + +/** + * struct iwl_fw_ini_hcmd + * + * @id: the debug configuration command type for instance: 0xf6 / 0xf5 / DHC + * @group: the desired cmd group + * @reserved: to align to FW struct + * @data: all of the relevant command data to be sent + */ +struct iwl_fw_ini_hcmd { + u8 id; + u8 group; + __le16 reserved; + u8 data[0]; +} __packed; /* FW_DEBUG_TLV_HCMD_DATA_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_header - Common Header for all ini debug TLV's structures + * + * @version: TLV version + * @domain: domain of the TLV. One of &enum iwl_fw_ini_dbg_domain + */ +struct iwl_fw_ini_header { + __le32 version; + __le32 domain; + /* followed by the data */ +} __packed; /* FW_TLV_DEBUG_HEADER_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_dev_addr - Configuration to read device addresses + * + * @size: size of each memory chunk + * @offset: offset to add to the base address of each chunk + */ +struct iwl_fw_ini_region_dev_addr { + __le32 size; + __le32 offset; +} __packed; /* FW_TLV_DEBUG_DEVICE_ADDR_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_fifos - Configuration to read Tx/Rx fifos + * + * @fid: fifos ids array. Used to determine what fifos to collect + * @hdr_only: if non zero, collect only the registers + * @offset: offset to add to the registers addresses + */ +struct iwl_fw_ini_region_fifos { + __le32 fid[2]; + __le32 hdr_only; + __le32 offset; +} __packed; /* FW_TLV_DEBUG_REGION_FIFOS_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_err_table - error table region data + * + * Configuration to read Umac/Lmac error table + * + * @version: version of the error table + * @base_addr: base address of the error table + * @size: size of the error table + * @offset: offset to add to &base_addr + */ +struct iwl_fw_ini_region_err_table { + __le32 version; + __le32 base_addr; + __le32 size; + __le32 offset; +} __packed; /* FW_TLV_DEBUG_REGION_ERROR_TABLE_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_special_device_memory - special device memory + * + * Configuration to read a special memory + * + * @type: type of the special memory + * @version: version of the special memory + * @base_addr: base address of the error table + * @size: size of the error table + * @offset: offset to add to &base_addr + */ +struct iwl_fw_ini_region_special_device_memory { + __le16 type; + __le16 version; + __le32 base_addr; + __le32 size; + __le32 offset; +} __packed; /* FW_TLV_DEBUG_REGION_SPECIAL_DEVICE_ADDR_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_internal_buffer - internal buffer region data + * + * Configuration to read internal monitor buffer + * + * @alloc_id: allocation id one of &enum iwl_fw_ini_allocation_id + * @base_addr: internal buffer base address + * @size: size internal buffer size + */ +struct iwl_fw_ini_region_internal_buffer { + __le32 alloc_id; + __le32 base_addr; + __le32 size; +} __packed; /* FW_TLV_DEBUG_REGION_INTERNAL_BUFFER_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_tlv - region TLV + * + * Configures parameters for region data collection + * + * @hdr: debug header + * @id: region id. Max id is &IWL_FW_INI_MAX_REGION_ID + * @type: region type. One of &enum iwl_fw_ini_region_type + * @name: region name + * @dev_addr: device address configuration. Used by + * &IWL_FW_INI_REGION_DEVICE_MEMORY, &IWL_FW_INI_REGION_PERIPHERY_MAC, + * &IWL_FW_INI_REGION_PERIPHERY_PHY, &IWL_FW_INI_REGION_PERIPHERY_AUX, + * &IWL_FW_INI_REGION_PAGING, &IWL_FW_INI_REGION_CSR, + * &IWL_FW_INI_REGION_DRAM_IMR and &IWL_FW_INI_REGION_PCI_IOSF_CONFIG + * &IWL_FW_INI_REGION_DBGI_SRAM, &FW_TLV_DEBUG_REGION_TYPE_DBGI_SRAM, + * @fifos: fifos configuration. Used by &IWL_FW_INI_REGION_TXF and + * &IWL_FW_INI_REGION_RXF + * @err_table: error table configuration. Used by + * IWL_FW_INI_REGION_LMAC_ERROR_TABLE and + * IWL_FW_INI_REGION_UMAC_ERROR_TABLE + * @internal_buffer: internal monitor buffer configuration. Used by + * &IWL_FW_INI_REGION_INTERNAL_BUFFER + * @dram_alloc_id: dram allocation id. One of &enum iwl_fw_ini_allocation_id. + * Used by &IWL_FW_INI_REGION_DRAM_BUFFER + * @tlv_mask: tlv collection mask. Used by &IWL_FW_INI_REGION_TLV + * @addrs: array of addresses attached to the end of the region tlv + */ +struct iwl_fw_ini_region_tlv { + struct iwl_fw_ini_header hdr; + __le32 id; + __le32 type; + u8 name[IWL_FW_INI_MAX_NAME]; + union { + struct iwl_fw_ini_region_dev_addr dev_addr; + struct iwl_fw_ini_region_fifos fifos; + struct iwl_fw_ini_region_err_table err_table; + struct iwl_fw_ini_region_internal_buffer internal_buffer; + struct iwl_fw_ini_region_special_device_memory special_mem; + __le32 dram_alloc_id; + __le32 tlv_mask; + }; /* FW_TLV_DEBUG_REGION_CONF_PARAMS_API_U_VER_1 */ + __le32 addrs[]; +} __packed; /* FW_TLV_DEBUG_REGION_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_debug_info_tlv + * + * debug configuration name for a specific image + * + * @hdr: debug header + * @image_type: image type + * @debug_cfg_name: debug configuration name + */ +struct iwl_fw_ini_debug_info_tlv { + struct iwl_fw_ini_header hdr; + __le32 image_type; + u8 debug_cfg_name[IWL_FW_INI_MAX_CFG_NAME]; +} __packed; /* FW_TLV_DEBUG_INFO_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_allocation_tlv - Allocates DRAM buffers + * + * @hdr: debug header + * @alloc_id: allocation id. One of &enum iwl_fw_ini_allocation_id + * @buf_location: buffer location. One of &enum iwl_fw_ini_buffer_location + * @req_size: requested buffer size + * @max_frags_num: maximum number of fragments + * @min_size: minimum buffer size + */ +struct iwl_fw_ini_allocation_tlv { + struct iwl_fw_ini_header hdr; + __le32 alloc_id; + __le32 buf_location; + __le32 req_size; + __le32 max_frags_num; + __le32 min_size; +} __packed; /* FW_TLV_DEBUG_BUFFER_ALLOCATION_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_trigger_tlv - trigger TLV + * + * Trigger that upon firing, determines what regions to collect + * + * @hdr: debug header + * @time_point: time point. One of &enum iwl_fw_ini_time_point + * @trigger_reason: trigger reason + * @apply_policy: uses &enum iwl_fw_ini_trigger_apply_policy + * @dump_delay: delay from trigger fire to dump, in usec + * @occurrences: max trigger fire occurrences allowed + * @reserved: unused + * @ignore_consec: ignore consecutive triggers, in usec + * @reset_fw: if non zero, will reset and reload the FW + * @multi_dut: initiate debug dump data on several DUTs + * @regions_mask: mask of regions to collect + * @data: trigger data + */ +struct iwl_fw_ini_trigger_tlv { + struct iwl_fw_ini_header hdr; + __le32 time_point; + __le32 trigger_reason; + __le32 apply_policy; + __le32 dump_delay; + __le32 occurrences; + __le32 reserved; + __le32 ignore_consec; + __le32 reset_fw; + __le32 multi_dut; + __le64 regions_mask; + __le32 data[]; +} __packed; /* FW_TLV_DEBUG_TRIGGER_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_hcmd_tlv - Generic Host command pass through TLV + * + * @hdr: debug header + * @time_point: time point. One of &enum iwl_fw_ini_time_point + * @period_msec: interval at which the hcmd will be sent to the FW. + * Measured in msec (0 = one time command) + * @hcmd: a variable length host-command to be sent to apply the configuration + */ +struct iwl_fw_ini_hcmd_tlv { + struct iwl_fw_ini_header hdr; + __le32 time_point; + __le32 period_msec; + struct iwl_fw_ini_hcmd hcmd; +} __packed; /* FW_TLV_DEBUG_HCMD_API_S_VER_1 */ + +/** +* struct iwl_fw_ini_conf_tlv - preset configuration TLV +* +* @address: the base address +* @value: value to set at address + +*/ +struct iwl_fw_ini_addr_val { + __le32 address; + __le32 value; +} __packed; /* FW_TLV_DEBUG_ADDR_VALUE_VER_1 */ + +/** + * struct iwl_fw_ini_conf_tlv - configuration TLV to set register/memory. + * + * @hdr: debug header + * @time_point: time point to apply config. One of &enum iwl_fw_ini_time_point + * @set_type: write access type preset token for time point. + * one of &enum iwl_fw_ini_config_set_type + * @addr_offset: the offset to add to any item in address[0] field + * @addr_val: address value pair + */ +struct iwl_fw_ini_conf_set_tlv { + struct iwl_fw_ini_header hdr; + __le32 time_point; + __le32 set_type; + __le32 addr_offset; + struct iwl_fw_ini_addr_val addr_val[0]; +} __packed; /* FW_TLV_DEBUG_CONFIG_SET_API_S_VER_1 */ + +/** + * enum iwl_fw_ini_config_set_type + * + * @IWL_FW_INI_CONFIG_SET_TYPE_INVALID: invalid config set + * @IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_MAC: for PERIPHERY MAC configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_PHY: for PERIPHERY PHY configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_AUX: for PERIPHERY AUX configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_MEMORY: for DEVICE MEMORY configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_CSR: for CSR configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_DBGC_DRAM_ADDR: for DBGC_DRAM_ADDR configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_PERIPH_SCRATCH_HWM: for PERIPH SCRATCH HWM configuration + * @IWL_FW_INI_ALLOCATION_NUM: max number of configuration supported +*/ + +enum iwl_fw_ini_config_set_type { + IWL_FW_INI_CONFIG_SET_TYPE_INVALID = 0, + IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_MAC, + IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_PHY, + IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_AUX, + IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_MEMORY, + IWL_FW_INI_CONFIG_SET_TYPE_CSR, + IWL_FW_INI_CONFIG_SET_TYPE_DBGC_DRAM_ADDR, + IWL_FW_INI_CONFIG_SET_TYPE_PERIPH_SCRATCH_HWM, + IWL_FW_INI_CONFIG_SET_TYPE_MAX_NUM, +} __packed; + +/** + * enum iwl_fw_ini_allocation_id + * + * @IWL_FW_INI_ALLOCATION_INVALID: invalid + * @IWL_FW_INI_ALLOCATION_ID_DBGC1: allocation meant for DBGC1 configuration + * @IWL_FW_INI_ALLOCATION_ID_DBGC2: allocation meant for DBGC2 configuration + * @IWL_FW_INI_ALLOCATION_ID_DBGC3: allocation meant for DBGC3 configuration + * @IWL_FW_INI_ALLOCATION_NUM: number of allocation ids +*/ +enum iwl_fw_ini_allocation_id { + IWL_FW_INI_ALLOCATION_INVALID, + IWL_FW_INI_ALLOCATION_ID_DBGC1, + IWL_FW_INI_ALLOCATION_ID_DBGC2, + IWL_FW_INI_ALLOCATION_ID_DBGC3, + IWL_FW_INI_ALLOCATION_NUM, +}; /* FW_DEBUG_TLV_ALLOCATION_ID_E_VER_1 */ + +/** + * enum iwl_fw_ini_buffer_location + * + * @IWL_FW_INI_LOCATION_INVALID: invalid + * @IWL_FW_INI_LOCATION_SRAM_PATH: SRAM location + * @IWL_FW_INI_LOCATION_DRAM_PATH: DRAM location + * @IWL_FW_INI_LOCATION_NPK_PATH: NPK location + */ +enum iwl_fw_ini_buffer_location { + IWL_FW_INI_LOCATION_INVALID, + IWL_FW_INI_LOCATION_SRAM_PATH, + IWL_FW_INI_LOCATION_DRAM_PATH, + IWL_FW_INI_LOCATION_NPK_PATH, + IWL_FW_INI_LOCATION_NUM, +}; /* FW_DEBUG_TLV_BUFFER_LOCATION_E_VER_1 */ + +/** + * enum iwl_fw_ini_region_type + * + * @IWL_FW_INI_REGION_INVALID: invalid + * @IWL_FW_INI_REGION_TLV: uCode and debug TLVs + * @IWL_FW_INI_REGION_INTERNAL_BUFFER: monitor SMEM buffer + * @IWL_FW_INI_REGION_DRAM_BUFFER: monitor DRAM buffer + * @IWL_FW_INI_REGION_TXF: TX fifos + * @IWL_FW_INI_REGION_RXF: RX fifo + * @IWL_FW_INI_REGION_LMAC_ERROR_TABLE: lmac error table + * @IWL_FW_INI_REGION_UMAC_ERROR_TABLE: umac error table + * @IWL_FW_INI_REGION_RSP_OR_NOTIF: FW response or notification data + * @IWL_FW_INI_REGION_DEVICE_MEMORY: device internal memory + * @IWL_FW_INI_REGION_PERIPHERY_MAC: periphery registers of MAC + * @IWL_FW_INI_REGION_PERIPHERY_PHY: periphery registers of PHY + * @IWL_FW_INI_REGION_PERIPHERY_AUX: periphery registers of AUX + * @IWL_FW_INI_REGION_PAGING: paging memory + * @IWL_FW_INI_REGION_CSR: CSR registers + * @IWL_FW_INI_REGION_DRAM_IMR: IMR memory + * @IWL_FW_INI_REGION_PCI_IOSF_CONFIG: PCI/IOSF config + * @IWL_FW_INI_REGION_SPECIAL_DEVICE_MEMORY: special device memory + * @IWL_FW_INI_REGION_DBGI_SRAM: periphery registers of DBGI SRAM + * @IWL_FW_INI_REGION_NUM: number of region types + */ +enum iwl_fw_ini_region_type { + IWL_FW_INI_REGION_INVALID, + IWL_FW_INI_REGION_TLV, + IWL_FW_INI_REGION_INTERNAL_BUFFER, + IWL_FW_INI_REGION_DRAM_BUFFER, + IWL_FW_INI_REGION_TXF, + IWL_FW_INI_REGION_RXF, + IWL_FW_INI_REGION_LMAC_ERROR_TABLE, + IWL_FW_INI_REGION_UMAC_ERROR_TABLE, + IWL_FW_INI_REGION_RSP_OR_NOTIF, + IWL_FW_INI_REGION_DEVICE_MEMORY, + IWL_FW_INI_REGION_PERIPHERY_MAC, + IWL_FW_INI_REGION_PERIPHERY_PHY, + IWL_FW_INI_REGION_PERIPHERY_AUX, + IWL_FW_INI_REGION_PAGING, + IWL_FW_INI_REGION_CSR, + IWL_FW_INI_REGION_DRAM_IMR, + IWL_FW_INI_REGION_PCI_IOSF_CONFIG, + IWL_FW_INI_REGION_SPECIAL_DEVICE_MEMORY, + IWL_FW_INI_REGION_DBGI_SRAM, + IWL_FW_INI_REGION_NUM +}; /* FW_TLV_DEBUG_REGION_TYPE_API_E */ + +/** + * enum iwl_fw_ini_time_point + * + * Hard coded time points in which the driver can send hcmd or perform dump + * collection + * + * @IWL_FW_INI_TIME_POINT_EARLY: pre loading the FW + * @IWL_FW_INI_TIME_POINT_AFTER_ALIVE: first cmd from host after alive notif + * @IWL_FW_INI_TIME_POINT_POST_INIT: last cmd in series of init sequence + * @IWL_FW_INI_TIME_POINT_FW_ASSERT: FW assert + * @IWL_FW_INI_TIME_POINT_FW_HW_ERROR: FW HW error + * @IWL_FW_INI_TIME_POINT_FW_TFD_Q_HANG: TFD queue hang + * @IWL_FW_INI_TIME_POINT_FW_DHC_NOTIFICATION: DHC cmd response and notif + * @IWL_FW_INI_TIME_POINT_FW_RSP_OR_NOTIF: FW response or notification. + * data field holds id and group + * @IWL_FW_INI_TIME_POINT_USER_TRIGGER: user trigger time point + * @IWL_FW_INI_TIME_POINT_PERIODIC: periodic timepoint that fires in constant + * intervals. data field holds the interval time in msec + * @IWL_FW_INI_TIME_POINT_RESERVED: reserved + * @IWL_FW_INI_TIME_POINT_HOST_ASSERT: Unused + * @IWL_FW_INI_TIME_POINT_HOST_ALIVE_TIMEOUT: alive timeout + * @IWL_FW_INI_TIME_POINT_HOST_DEVICE_ENABLE: device enable + * @IWL_FW_INI_TIME_POINT_HOST_DEVICE_DISABLE: device disable + * @IWL_FW_INI_TIME_POINT_HOST_D3_START: D3 start + * @IWL_FW_INI_TIME_POINT_HOST_D3_END: D3 end + * @IWL_FW_INI_TIME_POINT_MISSED_BEACONS: missed beacons + * @IWL_FW_INI_TIME_POINT_ASSOC_FAILED: association failure + * @IWL_FW_INI_TIME_POINT_TX_FAILED: Tx frame failed + * @IWL_FW_INI_TIME_POINT_TX_WFD_ACTION_FRAME_FAILED: wifi direct action + * frame failed + * @IWL_FW_INI_TIME_POINT_TX_LATENCY_THRESHOLD: Tx latency threshold + * @IWL_FW_INI_TIME_POINT_HANG_OCCURRED: hang occurred + * @IWL_FW_INI_TIME_POINT_EAPOL_FAILED: EAPOL failed + * @IWL_FW_INI_TIME_POINT_FAKE_TX: fake Tx + * @IWL_FW_INI_TIME_POINT_DEASSOC: de association + * @IWL_FW_INI_TIME_POINT_NUM: number of time points + */ +enum iwl_fw_ini_time_point { + IWL_FW_INI_TIME_POINT_INVALID, + IWL_FW_INI_TIME_POINT_EARLY, + IWL_FW_INI_TIME_POINT_AFTER_ALIVE, + IWL_FW_INI_TIME_POINT_POST_INIT, + IWL_FW_INI_TIME_POINT_FW_ASSERT, + IWL_FW_INI_TIME_POINT_FW_HW_ERROR, + IWL_FW_INI_TIME_POINT_FW_TFD_Q_HANG, + IWL_FW_INI_TIME_POINT_FW_DHC_NOTIFICATION, + IWL_FW_INI_TIME_POINT_FW_RSP_OR_NOTIF, + IWL_FW_INI_TIME_POINT_USER_TRIGGER, + IWL_FW_INI_TIME_POINT_PERIODIC, + IWL_FW_INI_TIME_POINT_RESERVED, + IWL_FW_INI_TIME_POINT_HOST_ASSERT, + IWL_FW_INI_TIME_POINT_HOST_ALIVE_TIMEOUT, + IWL_FW_INI_TIME_POINT_HOST_DEVICE_ENABLE, + IWL_FW_INI_TIME_POINT_HOST_DEVICE_DISABLE, + IWL_FW_INI_TIME_POINT_HOST_D3_START, + IWL_FW_INI_TIME_POINT_HOST_D3_END, + IWL_FW_INI_TIME_POINT_MISSED_BEACONS, + IWL_FW_INI_TIME_POINT_ASSOC_FAILED, + IWL_FW_INI_TIME_POINT_TX_FAILED, + IWL_FW_INI_TIME_POINT_TX_WFD_ACTION_FRAME_FAILED, + IWL_FW_INI_TIME_POINT_TX_LATENCY_THRESHOLD, + IWL_FW_INI_TIME_POINT_HANG_OCCURRED, + IWL_FW_INI_TIME_POINT_EAPOL_FAILED, + IWL_FW_INI_TIME_POINT_FAKE_TX, + IWL_FW_INI_TIME_POINT_DEASSOC, + IWL_FW_INI_TIME_POINT_NUM, +}; /* FW_TLV_DEBUG_TIME_POINT_API_E */ + +/** + * enum iwl_fw_ini_trigger_apply_policy - Determines how to apply triggers + * + * @IWL_FW_INI_APPLY_POLICY_MATCH_TIME_POINT: match by time point + * @IWL_FW_INI_APPLY_POLICY_MATCH_DATA: match by trigger data + * @IWL_FW_INI_APPLY_POLICY_OVERRIDE_REGIONS: override regions mask. + * Append otherwise + * @IWL_FW_INI_APPLY_POLICY_OVERRIDE_CFG: override trigger configuration + * @IWL_FW_INI_APPLY_POLICY_OVERRIDE_DATA: override trigger data. + * Append otherwise + */ +enum iwl_fw_ini_trigger_apply_policy { + IWL_FW_INI_APPLY_POLICY_MATCH_TIME_POINT = BIT(0), + IWL_FW_INI_APPLY_POLICY_MATCH_DATA = BIT(1), + IWL_FW_INI_APPLY_POLICY_OVERRIDE_REGIONS = BIT(8), + IWL_FW_INI_APPLY_POLICY_OVERRIDE_CFG = BIT(9), + IWL_FW_INI_APPLY_POLICY_OVERRIDE_DATA = BIT(10), +}; +#endif diff --git a/sys/contrib/dev/iwlwifi/fw/api/debug.h b/sys/contrib/dev/iwlwifi/fw/api/debug.h new file mode 100644 index 00000000000..029ae64bf2b --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/debug.h @@ -0,0 +1,407 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_debug_h__ +#define __iwl_fw_api_debug_h__ + +/** + * enum iwl_debug_cmds - debug commands + */ +enum iwl_debug_cmds { + /** + * @LMAC_RD_WR: + * LMAC memory read/write, using &struct iwl_dbg_mem_access_cmd and + * &struct iwl_dbg_mem_access_rsp + */ + LMAC_RD_WR = 0x0, + /** + * @UMAC_RD_WR: + * UMAC memory read/write, using &struct iwl_dbg_mem_access_cmd and + * &struct iwl_dbg_mem_access_rsp + */ + UMAC_RD_WR = 0x1, + /** + * @HOST_EVENT_CFG: + * updates the enabled event severities + * &struct iwl_dbg_host_event_cfg_cmd + */ + HOST_EVENT_CFG = 0x3, + /** + * @DBGC_SUSPEND_RESUME: + * DBGC suspend/resume commad. Uses a single dword as data: + * 0 - resume DBGC recording + * 1 - suspend DBGC recording + */ + DBGC_SUSPEND_RESUME = 0x7, + /** + * @BUFFER_ALLOCATION: + * passes DRAM buffers to a DBGC + * &struct iwl_buf_alloc_cmd + */ + BUFFER_ALLOCATION = 0x8, + /** + * @MFU_ASSERT_DUMP_NTF: + * &struct iwl_mfu_assert_dump_notif + */ + MFU_ASSERT_DUMP_NTF = 0xFE, +}; + +/* Error response/notification */ +enum { + FW_ERR_UNKNOWN_CMD = 0x0, + FW_ERR_INVALID_CMD_PARAM = 0x1, + FW_ERR_SERVICE = 0x2, + FW_ERR_ARC_MEMORY = 0x3, + FW_ERR_ARC_CODE = 0x4, + FW_ERR_WATCH_DOG = 0x5, + FW_ERR_WEP_GRP_KEY_INDX = 0x10, + FW_ERR_WEP_KEY_SIZE = 0x11, + FW_ERR_OBSOLETE_FUNC = 0x12, + FW_ERR_UNEXPECTED = 0xFE, + FW_ERR_FATAL = 0xFF +}; + +/** enum iwl_dbg_suspend_resume_cmds - dbgc suspend resume operations + * dbgc suspend resume command operations + * @DBGC_RESUME_CMD: resume dbgc recording + * @DBGC_SUSPEND_CMD: stop dbgc recording + */ +enum iwl_dbg_suspend_resume_cmds { + DBGC_RESUME_CMD, + DBGC_SUSPEND_CMD, +}; + +/** + * struct iwl_error_resp - FW error indication + * ( REPLY_ERROR = 0x2 ) + * @error_type: one of FW_ERR_* + * @cmd_id: the command ID for which the error occurred + * @reserved1: reserved + * @bad_cmd_seq_num: sequence number of the erroneous command + * @error_service: which service created the error, applicable only if + * error_type = 2, otherwise 0 + * @timestamp: TSF in usecs. + */ +struct iwl_error_resp { + __le32 error_type; + u8 cmd_id; + u8 reserved1; + __le16 bad_cmd_seq_num; + __le32 error_service; + __le64 timestamp; +} __packed; + +#define TX_FIFO_MAX_NUM_9000 8 +#define TX_FIFO_MAX_NUM 15 +#define RX_FIFO_MAX_NUM 2 +#define TX_FIFO_INTERNAL_MAX_NUM 6 + +/** + * struct iwl_shared_mem_cfg_v2 - Shared memory configuration information + * + * @shared_mem_addr: shared memory addr (pre 8000 HW set to 0x0 as MARBH is not + * accessible) + * @shared_mem_size: shared memory size + * @sample_buff_addr: internal sample (mon/adc) buff addr (pre 8000 HW set to + * 0x0 as accessible only via DBGM RDAT) + * @sample_buff_size: internal sample buff size + * @txfifo_addr: start addr of TXF0 (excluding the context table 0.5KB), (pre + * 8000 HW set to 0x0 as not accessible) + * @txfifo_size: size of TXF0 ... TXF7 + * @rxfifo_size: RXF1, RXF2 sizes. If there is no RXF2, it'll have a value of 0 + * @page_buff_addr: used by UMAC and performance debug (page miss analysis), + * when paging is not supported this should be 0 + * @page_buff_size: size of %page_buff_addr + * @rxfifo_addr: Start address of rxFifo + * @internal_txfifo_addr: start address of internalFifo + * @internal_txfifo_size: internal fifos' size + * + * NOTE: on firmware that don't have IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG + * set, the last 3 members don't exist. + */ +struct iwl_shared_mem_cfg_v2 { + __le32 shared_mem_addr; + __le32 shared_mem_size; + __le32 sample_buff_addr; + __le32 sample_buff_size; + __le32 txfifo_addr; + __le32 txfifo_size[TX_FIFO_MAX_NUM_9000]; + __le32 rxfifo_size[RX_FIFO_MAX_NUM]; + __le32 page_buff_addr; + __le32 page_buff_size; + __le32 rxfifo_addr; + __le32 internal_txfifo_addr; + __le32 internal_txfifo_size[TX_FIFO_INTERNAL_MAX_NUM]; +} __packed; /* SHARED_MEM_ALLOC_API_S_VER_2 */ + +/** + * struct iwl_shared_mem_lmac_cfg - LMAC shared memory configuration + * + * @txfifo_addr: start addr of TXF0 (excluding the context table 0.5KB) + * @txfifo_size: size of TX FIFOs + * @rxfifo1_addr: RXF1 addr + * @rxfifo1_size: RXF1 size + */ +struct iwl_shared_mem_lmac_cfg { + __le32 txfifo_addr; + __le32 txfifo_size[TX_FIFO_MAX_NUM]; + __le32 rxfifo1_addr; + __le32 rxfifo1_size; + +} __packed; /* SHARED_MEM_ALLOC_LMAC_API_S_VER_1 */ + +/** + * struct iwl_shared_mem_cfg - Shared memory configuration information + * + * @shared_mem_addr: shared memory address + * @shared_mem_size: shared memory size + * @sample_buff_addr: internal sample (mon/adc) buff addr + * @sample_buff_size: internal sample buff size + * @rxfifo2_addr: start addr of RXF2 + * @rxfifo2_size: size of RXF2 + * @page_buff_addr: used by UMAC and performance debug (page miss analysis), + * when paging is not supported this should be 0 + * @page_buff_size: size of %page_buff_addr + * @lmac_num: number of LMACs (1 or 2) + * @lmac_smem: per - LMAC smem data + * @rxfifo2_control_addr: start addr of RXF2C + * @rxfifo2_control_size: size of RXF2C + */ +struct iwl_shared_mem_cfg { + __le32 shared_mem_addr; + __le32 shared_mem_size; + __le32 sample_buff_addr; + __le32 sample_buff_size; + __le32 rxfifo2_addr; + __le32 rxfifo2_size; + __le32 page_buff_addr; + __le32 page_buff_size; + __le32 lmac_num; + struct iwl_shared_mem_lmac_cfg lmac_smem[3]; + __le32 rxfifo2_control_addr; + __le32 rxfifo2_control_size; +} __packed; /* SHARED_MEM_ALLOC_API_S_VER_4 */ + +/** + * struct iwl_mfuart_load_notif_v1 - mfuart image version & status + * ( MFUART_LOAD_NOTIFICATION = 0xb1 ) + * @installed_ver: installed image version + * @external_ver: external image version + * @status: MFUART loading status + * @duration: MFUART loading time +*/ +struct iwl_mfuart_load_notif_v1 { + __le32 installed_ver; + __le32 external_ver; + __le32 status; + __le32 duration; +} __packed; /* MFU_LOADER_NTFY_API_S_VER_1 */ + +/** + * struct iwl_mfuart_load_notif - mfuart image version & status + * ( MFUART_LOAD_NOTIFICATION = 0xb1 ) + * @installed_ver: installed image version + * @external_ver: external image version + * @status: MFUART loading status + * @duration: MFUART loading time + * @image_size: MFUART image size in bytes +*/ +struct iwl_mfuart_load_notif { + __le32 installed_ver; + __le32 external_ver; + __le32 status; + __le32 duration; + /* image size valid only in v2 of the command */ + __le32 image_size; +} __packed; /* MFU_LOADER_NTFY_API_S_VER_2 */ + +/** + * struct iwl_mfu_assert_dump_notif - mfuart dump logs + * ( MFU_ASSERT_DUMP_NTF = 0xfe ) + * @assert_id: mfuart assert id that cause the notif + * @curr_reset_num: number of asserts since uptime + * @index_num: current chunk id + * @parts_num: total number of chunks + * @data_size: number of data bytes sent + * @data: data buffer + */ +struct iwl_mfu_assert_dump_notif { + __le32 assert_id; + __le32 curr_reset_num; + __le16 index_num; + __le16 parts_num; + __le32 data_size; + __le32 data[0]; +} __packed; /* MFU_DUMP_ASSERT_API_S_VER_1 */ + +/** + * enum iwl_mvm_marker_id - marker ids + * + * The ids for different type of markers to insert into the usniffer logs + * + * @MARKER_ID_TX_FRAME_LATENCY: TX latency marker + * @MARKER_ID_SYNC_CLOCK: sync FW time and systime + */ +enum iwl_mvm_marker_id { + MARKER_ID_TX_FRAME_LATENCY = 1, + MARKER_ID_SYNC_CLOCK = 2, +}; /* MARKER_ID_API_E_VER_2 */ + +/** + * struct iwl_mvm_marker - mark info into the usniffer logs + * + * (MARKER_CMD = 0xcb) + * + * Mark the UTC time stamp into the usniffer logs together with additional + * metadata, so the usniffer output can be parsed. + * In the command response the ucode will return the GP2 time. + * + * @dw_len: The amount of dwords following this byte including this byte. + * @marker_id: A unique marker id (iwl_mvm_marker_id). + * @reserved: reserved. + * @timestamp: in milliseconds since 1970-01-01 00:00:00 UTC + * @metadata: additional meta data that will be written to the unsiffer log + */ +struct iwl_mvm_marker { + u8 dw_len; + u8 marker_id; + __le16 reserved; + __le64 timestamp; + __le32 metadata[0]; +} __packed; /* MARKER_API_S_VER_1 */ + +/** + * struct iwl_mvm_marker_rsp - Response to marker cmd + * + * @gp2: The gp2 clock value in the FW + */ +struct iwl_mvm_marker_rsp { + __le32 gp2; +} __packed; + +/* Operation types for the debug mem access */ +enum { + DEBUG_MEM_OP_READ = 0, + DEBUG_MEM_OP_WRITE = 1, + DEBUG_MEM_OP_WRITE_BYTES = 2, +}; + +#define DEBUG_MEM_MAX_SIZE_DWORDS 32 + +/** + * struct iwl_dbg_mem_access_cmd - Request the device to read/write memory + * @op: DEBUG_MEM_OP_* + * @addr: address to read/write from/to + * @len: in dwords, to read/write + * @data: for write opeations, contains the source buffer + */ +struct iwl_dbg_mem_access_cmd { + __le32 op; + __le32 addr; + __le32 len; + __le32 data[]; +} __packed; /* DEBUG_(U|L)MAC_RD_WR_CMD_API_S_VER_1 */ + +/* Status responses for the debug mem access */ +enum { + DEBUG_MEM_STATUS_SUCCESS = 0x0, + DEBUG_MEM_STATUS_FAILED = 0x1, + DEBUG_MEM_STATUS_LOCKED = 0x2, + DEBUG_MEM_STATUS_HIDDEN = 0x3, + DEBUG_MEM_STATUS_LENGTH = 0x4, +}; + +/** + * struct iwl_dbg_mem_access_rsp - Response to debug mem commands + * @status: DEBUG_MEM_STATUS_* + * @len: read dwords (0 for write operations) + * @data: contains the read DWs + */ +struct iwl_dbg_mem_access_rsp { + __le32 status; + __le32 len; + __le32 data[]; +} __packed; /* DEBUG_(U|L)MAC_RD_WR_RSP_API_S_VER_1 */ + +/** + * struct iwl_dbg_suspend_resume_cmd - dbgc suspend resume command + * @operation: suspend or resume operation, uses + * &enum iwl_dbg_suspend_resume_cmds + */ +struct iwl_dbg_suspend_resume_cmd { + __le32 operation; +} __packed; + +#define BUF_ALLOC_MAX_NUM_FRAGS 16 + +/** + * struct iwl_buf_alloc_frag - a DBGC fragment + * @addr: base address of the fragment + * @size: size of the fragment + */ +struct iwl_buf_alloc_frag { + __le64 addr; + __le32 size; +} __packed; /* FRAGMENT_STRUCTURE_API_S_VER_1 */ + +/** + * struct iwl_buf_alloc_cmd - buffer allocation command + * @alloc_id: &enum iwl_fw_ini_allocation_id + * @buf_location: &enum iwl_fw_ini_buffer_location + * @num_frags: number of fragments + * @frags: fragments array + */ +struct iwl_buf_alloc_cmd { + __le32 alloc_id; + __le32 buf_location; + __le32 num_frags; + struct iwl_buf_alloc_frag frags[BUF_ALLOC_MAX_NUM_FRAGS]; +} __packed; /* BUFFER_ALLOCATION_CMD_API_S_VER_2 */ + +#define DRAM_INFO_FIRST_MAGIC_WORD 0x76543210 +#define DRAM_INFO_SECOND_MAGIC_WORD 0x89ABCDEF + +/** + * struct iwL_dram_info - DRAM fragments allocation struct + * + * Driver will fill in the first 1K(+) of the pointed DRAM fragment + * + * @first_word: magic word value + * @second_word: magic word value + * @framfrags: DRAM fragmentaion detail +*/ +struct iwl_dram_info { + __le32 first_word; + __le32 second_word; + struct iwl_buf_alloc_cmd dram_frags[IWL_FW_INI_ALLOCATION_NUM - 1]; +} __packed; /* INIT_DRAM_FRAGS_ALLOCATIONS_S_VER_1 */ + +/** + * struct iwl_dbgc1_info - DBGC1 address and size + * + * Driver will fill the dbcg1 address and size at address based on config TLV. + * + * @first_word: all 0 set as identifier + * @dbgc1_add_lsb: LSB bits of DBGC1 physical address + * @dbgc1_add_msb: MSB bits of DBGC1 physical address + * @dbgc1_size: DBGC1 size +*/ +struct iwl_dbgc1_info { + __le32 first_word; + __le32 dbgc1_add_lsb; + __le32 dbgc1_add_msb; + __le32 dbgc1_size; +} __packed; /* INIT_DRAM_FRAGS_ALLOCATIONS_S_VER_1 */ + +/** + * struct iwl_dbg_host_event_cfg_cmd + * @enabled_severities: enabled severities + */ +struct iwl_dbg_host_event_cfg_cmd { + __le32 enabled_severities; +} __packed; /* DEBUG_HOST_EVENT_CFG_CMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_debug_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/filter.h b/sys/contrib/dev/iwlwifi/fw/api/filter.h new file mode 100644 index 00000000000..dd62a63956b --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/filter.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_filter_h__ +#define __iwl_fw_api_filter_h__ + +#include "fw/api/mac.h" + +#define MAX_PORT_ID_NUM 2 +#define MAX_MCAST_FILTERING_ADDRESSES 256 + +/** + * struct iwl_mcast_filter_cmd - configure multicast filter. + * @filter_own: Set 1 to filter out multicast packets sent by station itself + * @port_id: Multicast MAC addresses array specifier. This is a strange way + * to identify network interface adopted in host-device IF. + * It is used by FW as index in array of addresses. This array has + * MAX_PORT_ID_NUM members. + * @count: Number of MAC addresses in the array + * @pass_all: Set 1 to pass all multicast packets. + * @bssid: current association BSSID. + * @reserved: reserved + * @addr_list: Place holder for array of MAC addresses. + * IMPORTANT: add padding if necessary to ensure DWORD alignment. + */ +struct iwl_mcast_filter_cmd { + u8 filter_own; + u8 port_id; + u8 count; + u8 pass_all; + u8 bssid[6]; + u8 reserved[2]; + u8 addr_list[0]; +} __packed; /* MCAST_FILTERING_CMD_API_S_VER_1 */ + +#define MAX_BCAST_FILTERS 8 +#define MAX_BCAST_FILTER_ATTRS 2 + +/** + * enum iwl_mvm_bcast_filter_attr_offset - written by fw for each Rx packet + * @BCAST_FILTER_OFFSET_PAYLOAD_START: offset is from payload start. + * @BCAST_FILTER_OFFSET_IP_END: offset is from ip header end (i.e. + * start of ip payload). + */ +enum iwl_mvm_bcast_filter_attr_offset { + BCAST_FILTER_OFFSET_PAYLOAD_START = 0, + BCAST_FILTER_OFFSET_IP_END = 1, +}; + +/** + * struct iwl_fw_bcast_filter_attr - broadcast filter attribute + * @offset_type: &enum iwl_mvm_bcast_filter_attr_offset. + * @offset: starting offset of this pattern. + * @reserved1: reserved + * @val: value to match - big endian (MSB is the first + * byte to match from offset pos). + * @mask: mask to match (big endian). + */ +struct iwl_fw_bcast_filter_attr { + u8 offset_type; + u8 offset; + __le16 reserved1; + __be32 val; + __be32 mask; +} __packed; /* BCAST_FILTER_ATT_S_VER_1 */ + +/** + * enum iwl_mvm_bcast_filter_frame_type - filter frame type + * @BCAST_FILTER_FRAME_TYPE_ALL: consider all frames. + * @BCAST_FILTER_FRAME_TYPE_IPV4: consider only ipv4 frames + */ +enum iwl_mvm_bcast_filter_frame_type { + BCAST_FILTER_FRAME_TYPE_ALL = 0, + BCAST_FILTER_FRAME_TYPE_IPV4 = 1, +}; + +/** + * struct iwl_fw_bcast_filter - broadcast filter + * @discard: discard frame (1) or let it pass (0). + * @frame_type: &enum iwl_mvm_bcast_filter_frame_type. + * @reserved1: reserved + * @num_attrs: number of valid attributes in this filter. + * @attrs: attributes of this filter. a filter is considered matched + * only when all its attributes are matched (i.e. AND relationship) + */ +struct iwl_fw_bcast_filter { + u8 discard; + u8 frame_type; + u8 num_attrs; + u8 reserved1; + struct iwl_fw_bcast_filter_attr attrs[MAX_BCAST_FILTER_ATTRS]; +} __packed; /* BCAST_FILTER_S_VER_1 */ + +/** + * struct iwl_fw_bcast_mac - per-mac broadcast filtering configuration. + * @default_discard: default action for this mac (discard (1) / pass (0)). + * @reserved1: reserved + * @attached_filters: bitmap of relevant filters for this mac. + */ +struct iwl_fw_bcast_mac { + u8 default_discard; + u8 reserved1; + __le16 attached_filters; +} __packed; /* BCAST_MAC_CONTEXT_S_VER_1 */ + +/** + * struct iwl_bcast_filter_cmd - broadcast filtering configuration + * @disable: enable (0) / disable (1) + * @max_bcast_filters: max number of filters (MAX_BCAST_FILTERS) + * @max_macs: max number of macs (NUM_MAC_INDEX_DRIVER) + * @reserved1: reserved + * @filters: broadcast filters + * @macs: broadcast filtering configuration per-mac + */ +struct iwl_bcast_filter_cmd { + u8 disable; + u8 max_bcast_filters; + u8 max_macs; + u8 reserved1; + struct iwl_fw_bcast_filter filters[MAX_BCAST_FILTERS]; + struct iwl_fw_bcast_mac macs[NUM_MAC_INDEX_DRIVER]; +} __packed; /* BCAST_FILTERING_HCMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_filter_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/led.h b/sys/contrib/dev/iwlwifi/fw/api/led.h new file mode 100644 index 00000000000..475bb4640ea --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/led.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_led_h__ +#define __iwl_fw_api_led_h__ + +/** + * struct iwl_led_cmd - LED switching command + * + * @status: LED status (on/off) + */ +struct iwl_led_cmd { + __le32 status; +} __packed; /* LEDS_CMD_API_S_VER_2 */ + +#endif /* __iwl_fw_api_led_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/location.h b/sys/contrib/dev/iwlwifi/fw/api/location.h new file mode 100644 index 00000000000..12af94e166e --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/location.h @@ -0,0 +1,1694 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#ifndef __iwl_fw_api_location_h__ +#define __iwl_fw_api_location_h__ + +/** + * enum iwl_location_subcmd_ids - location group command IDs + */ +enum iwl_location_subcmd_ids { + /** + * @TOF_RANGE_REQ_CMD: TOF ranging request, + * uses one of &struct iwl_tof_range_req_cmd_v5, + * &struct iwl_tof_range_req_cmd_v7, + * &struct iwl_tof_range_req_cmd_v8, + * &struct iwl_tof_range_req_cmd_v9, + * &struct iwl_tof_range_req_cmd_v11, + * &struct iwl_tof_range_req_cmd_v7 + */ + TOF_RANGE_REQ_CMD = 0x0, + /** + * @TOF_CONFIG_CMD: TOF configuration, uses &struct iwl_tof_config_cmd + */ + TOF_CONFIG_CMD = 0x1, + /** + * @TOF_RANGE_ABORT_CMD: abort ongoing ranging, uses + * &struct iwl_tof_range_abort_cmd + */ + TOF_RANGE_ABORT_CMD = 0x2, + /** + * @TOF_RANGE_REQ_EXT_CMD: TOF extended ranging config, + * uses &struct iwl_tof_range_req_ext_cmd + */ + TOF_RANGE_REQ_EXT_CMD = 0x3, + /** + * @TOF_RESPONDER_CONFIG_CMD: FTM responder configuration, + * uses &struct iwl_tof_responder_config_cmd + */ + TOF_RESPONDER_CONFIG_CMD = 0x4, + /** + * @TOF_RESPONDER_DYN_CONFIG_CMD: FTM dynamic configuration, + * uses &struct iwl_tof_responder_dyn_config_cmd + */ + TOF_RESPONDER_DYN_CONFIG_CMD = 0x5, + /** + * @CSI_HEADER_NOTIFICATION: CSI header + */ + CSI_HEADER_NOTIFICATION = 0xFA, + /** + * @CSI_CHUNKS_NOTIFICATION: CSI chunk, + * uses &struct iwl_csi_chunk_notification + */ + CSI_CHUNKS_NOTIFICATION = 0xFB, + /** + * @TOF_LC_NOTIF: used for LCI/civic location, contains just + * the action frame + */ + TOF_LC_NOTIF = 0xFC, + /** + * @TOF_RESPONDER_STATS: FTM responder statistics notification, + * uses &struct iwl_ftm_responder_stats + */ + TOF_RESPONDER_STATS = 0xFD, + /** + * @TOF_MCSI_DEBUG_NOTIF: MCSI debug notification, uses + * &struct iwl_tof_mcsi_notif + */ + TOF_MCSI_DEBUG_NOTIF = 0xFE, + /** + * @TOF_RANGE_RESPONSE_NOTIF: ranging response, using + * &struct iwl_tof_range_rsp_ntfy + */ + TOF_RANGE_RESPONSE_NOTIF = 0xFF, +}; + +/** + * struct iwl_tof_config_cmd - ToF configuration + * @tof_disabled: indicates if ToF is disabled (or not) + * @one_sided_disabled: indicates if one-sided is disabled (or not) + * @is_debug_mode: indiciates if debug mode is active + * @is_buf_required: indicates if channel estimation buffer is required + */ +struct iwl_tof_config_cmd { + u8 tof_disabled; + u8 one_sided_disabled; + u8 is_debug_mode; + u8 is_buf_required; +} __packed; + +/** + * enum iwl_tof_bandwidth - values for iwl_tof_range_req_ap_entry.bandwidth + * @IWL_TOF_BW_20_LEGACY: 20 MHz non-HT + * @IWL_TOF_BW_20_HT: 20 MHz HT + * @IWL_TOF_BW_40: 40 MHz + * @IWL_TOF_BW_80: 80 MHz + * @IWL_TOF_BW_160: 160 MHz + * @IWL_TOF_BW_NUM: number of tof bandwidths + */ +enum iwl_tof_bandwidth { + IWL_TOF_BW_20_LEGACY, + IWL_TOF_BW_20_HT, + IWL_TOF_BW_40, + IWL_TOF_BW_80, + IWL_TOF_BW_160, + IWL_TOF_BW_NUM, +}; /* LOCAT_BW_TYPE_E */ + +/* + * enum iwl_tof_algo_type - Algorithym type for range measurement request + */ +enum iwl_tof_algo_type { + IWL_TOF_ALGO_TYPE_MAX_LIKE = 0, + IWL_TOF_ALGO_TYPE_LINEAR_REG = 1, + IWL_TOF_ALGO_TYPE_FFT = 2, + + /* Keep last */ + IWL_TOF_ALGO_TYPE_INVALID, +}; /* ALGO_TYPE_E */ + +/* + * enum iwl_tof_mcsi_ntfy - Enable/Disable MCSI notifications + */ +enum iwl_tof_mcsi_enable { + IWL_TOF_MCSI_DISABLED = 0, + IWL_TOF_MCSI_ENABLED = 1, +}; /* MCSI_ENABLE_E */ + +/** + * enum iwl_tof_responder_cmd_valid_field - valid fields in the responder cfg + * @IWL_TOF_RESPONDER_CMD_VALID_CHAN_INFO: channel info is valid + * @IWL_TOF_RESPONDER_CMD_VALID_TOA_OFFSET: ToA offset is valid + * @IWL_TOF_RESPONDER_CMD_VALID_COMMON_CALIB: common calibration mode is valid + * @IWL_TOF_RESPONDER_CMD_VALID_SPECIFIC_CALIB: spefici calibration mode is + * valid + * @IWL_TOF_RESPONDER_CMD_VALID_BSSID: BSSID is valid + * @IWL_TOF_RESPONDER_CMD_VALID_TX_ANT: TX antenna is valid + * @IWL_TOF_RESPONDER_CMD_VALID_ALGO_TYPE: algorithm type is valid + * @IWL_TOF_RESPONDER_CMD_VALID_NON_ASAP_SUPPORT: non-ASAP support is valid + * @IWL_TOF_RESPONDER_CMD_VALID_STATISTICS_REPORT_SUPPORT: statistics report + * support is valid + * @IWL_TOF_RESPONDER_CMD_VALID_MCSI_NOTIF_SUPPORT: MCSI notification support + * is valid + * @IWL_TOF_RESPONDER_CMD_VALID_FAST_ALGO_SUPPORT: fast algorithm support + * is valid + * @IWL_TOF_RESPONDER_CMD_VALID_RETRY_ON_ALGO_FAIL: retry on algorithm failure + * is valid + * @IWL_TOF_RESPONDER_CMD_VALID_STA_ID: station ID is valid + * @IWL_TOF_RESPONDER_CMD_VALID_NDP_SUPPORT: enable/disable NDP ranging support + * is valid + * @IWL_TOF_RESPONDER_CMD_VALID_NDP_PARAMS: NDP parameters are valid + * @IWL_TOF_RESPONDER_CMD_VALID_LMR_FEEDBACK: LMR feedback support is valid + * @IWL_TOF_RESPONDER_CMD_VALID_SESSION_ID: session id flag is valid + * @IWL_TOF_RESPONDER_CMD_VALID_BSS_COLOR: the bss_color field is valid + * @IWL_TOF_RESPONDER_CMD_VALID_MIN_MAX_TIME_BETWEEN_MSR: the + * min_time_between_msr and max_time_between_msr fields are valid + */ +enum iwl_tof_responder_cmd_valid_field { + IWL_TOF_RESPONDER_CMD_VALID_CHAN_INFO = BIT(0), + IWL_TOF_RESPONDER_CMD_VALID_TOA_OFFSET = BIT(1), + IWL_TOF_RESPONDER_CMD_VALID_COMMON_CALIB = BIT(2), + IWL_TOF_RESPONDER_CMD_VALID_SPECIFIC_CALIB = BIT(3), + IWL_TOF_RESPONDER_CMD_VALID_BSSID = BIT(4), + IWL_TOF_RESPONDER_CMD_VALID_TX_ANT = BIT(5), + IWL_TOF_RESPONDER_CMD_VALID_ALGO_TYPE = BIT(6), + IWL_TOF_RESPONDER_CMD_VALID_NON_ASAP_SUPPORT = BIT(7), + IWL_TOF_RESPONDER_CMD_VALID_STATISTICS_REPORT_SUPPORT = BIT(8), + IWL_TOF_RESPONDER_CMD_VALID_MCSI_NOTIF_SUPPORT = BIT(9), + IWL_TOF_RESPONDER_CMD_VALID_FAST_ALGO_SUPPORT = BIT(10), + IWL_TOF_RESPONDER_CMD_VALID_RETRY_ON_ALGO_FAIL = BIT(11), + IWL_TOF_RESPONDER_CMD_VALID_STA_ID = BIT(12), + IWL_TOF_RESPONDER_CMD_VALID_NDP_SUPPORT = BIT(22), + IWL_TOF_RESPONDER_CMD_VALID_NDP_PARAMS = BIT(23), + IWL_TOF_RESPONDER_CMD_VALID_LMR_FEEDBACK = BIT(24), + IWL_TOF_RESPONDER_CMD_VALID_SESSION_ID = BIT(25), + IWL_TOF_RESPONDER_CMD_VALID_BSS_COLOR = BIT(26), + IWL_TOF_RESPONDER_CMD_VALID_MIN_MAX_TIME_BETWEEN_MSR = BIT(27), +}; + +/** + * enum iwl_tof_responder_cfg_flags - responder configuration flags + * @IWL_TOF_RESPONDER_FLAGS_NON_ASAP_SUPPORT: non-ASAP support + * @IWL_TOF_RESPONDER_FLAGS_REPORT_STATISTICS: report statistics + * @IWL_TOF_RESPONDER_FLAGS_REPORT_MCSI: report MCSI + * @IWL_TOF_RESPONDER_FLAGS_ALGO_TYPE: algorithm type + * @IWL_TOF_RESPONDER_FLAGS_TOA_OFFSET_MODE: ToA offset mode + * @IWL_TOF_RESPONDER_FLAGS_COMMON_CALIB_MODE: common calibration mode + * @IWL_TOF_RESPONDER_FLAGS_SPECIFIC_CALIB_MODE: specific calibration mode + * @IWL_TOF_RESPONDER_FLAGS_FAST_ALGO_SUPPORT: fast algorithm support + * @IWL_TOF_RESPONDER_FLAGS_RETRY_ON_ALGO_FAIL: retry on algorithm fail + * @IWL_TOF_RESPONDER_FLAGS_FTM_TX_ANT: TX antenna mask + * @IWL_TOF_RESPONDER_FLAGS_NDP_SUPPORT: support NDP ranging + * @IWL_TOF_RESPONDER_FLAGS_LMR_FEEDBACK: request for LMR feedback if the + * initiator supports it + * @IWL_TOF_RESPONDER_FLAGS_SESSION_ID: send the session id in the initial FTM + * frame. + */ +enum iwl_tof_responder_cfg_flags { + IWL_TOF_RESPONDER_FLAGS_NON_ASAP_SUPPORT = BIT(0), + IWL_TOF_RESPONDER_FLAGS_REPORT_STATISTICS = BIT(1), + IWL_TOF_RESPONDER_FLAGS_REPORT_MCSI = BIT(2), + IWL_TOF_RESPONDER_FLAGS_ALGO_TYPE = BIT(3) | BIT(4) | BIT(5), + IWL_TOF_RESPONDER_FLAGS_TOA_OFFSET_MODE = BIT(6), + IWL_TOF_RESPONDER_FLAGS_COMMON_CALIB_MODE = BIT(7), + IWL_TOF_RESPONDER_FLAGS_SPECIFIC_CALIB_MODE = BIT(8), + IWL_TOF_RESPONDER_FLAGS_FAST_ALGO_SUPPORT = BIT(9), + IWL_TOF_RESPONDER_FLAGS_RETRY_ON_ALGO_FAIL = BIT(10), + IWL_TOF_RESPONDER_FLAGS_FTM_TX_ANT = RATE_MCS_ANT_AB_MSK, + IWL_TOF_RESPONDER_FLAGS_NDP_SUPPORT = BIT(24), + IWL_TOF_RESPONDER_FLAGS_LMR_FEEDBACK = BIT(25), + IWL_TOF_RESPONDER_FLAGS_SESSION_ID = BIT(27), +}; + +/** + * struct iwl_tof_responder_config_cmd_v6 - ToF AP mode (for debug) + * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field + * @responder_cfg_flags: &iwl_tof_responder_cfg_flags + * @bandwidth: current AP Bandwidth: &enum iwl_tof_bandwidth + * @rate: current AP rate + * @channel_num: current AP Channel + * @ctrl_ch_position: coding of the control channel position relative to + * the center frequency, see iwl_mvm_get_ctrl_pos() + * @sta_id: index of the AP STA when in AP mode + * @reserved1: reserved + * @toa_offset: Artificial addition [pSec] for the ToA - to be used for debug + * purposes, simulating station movement by adding various values + * to this field + * @common_calib: XVT: common calibration value + * @specific_calib: XVT: specific calibration value + * @bssid: Current AP BSSID + * @reserved2: reserved + */ +struct iwl_tof_responder_config_cmd_v6 { + __le32 cmd_valid_fields; + __le32 responder_cfg_flags; + u8 bandwidth; + u8 rate; + u8 channel_num; + u8 ctrl_ch_position; + u8 sta_id; + u8 reserved1; + __le16 toa_offset; + __le16 common_calib; + __le16 specific_calib; + u8 bssid[ETH_ALEN]; + __le16 reserved2; +} __packed; /* TOF_RESPONDER_CONFIG_CMD_API_S_VER_6 */ + +/** + * struct iwl_tof_responder_config_cmd_v7 - ToF AP mode (for debug) + * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field + * @responder_cfg_flags: &iwl_tof_responder_cfg_flags + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @rate: current AP rate + * @channel_num: current AP Channel + * @ctrl_ch_position: coding of the control channel position relative to + * the center frequency, see iwl_mvm_get_ctrl_pos() + * @sta_id: index of the AP STA when in AP mode + * @reserved1: reserved + * @toa_offset: Artificial addition [pSec] for the ToA - to be used for debug + * purposes, simulating station movement by adding various values + * to this field + * @common_calib: XVT: common calibration value + * @specific_calib: XVT: specific calibration value + * @bssid: Current AP BSSID + * @reserved2: reserved + */ +struct iwl_tof_responder_config_cmd_v7 { + __le32 cmd_valid_fields; + __le32 responder_cfg_flags; + u8 format_bw; + u8 rate; + u8 channel_num; + u8 ctrl_ch_position; + u8 sta_id; + u8 reserved1; + __le16 toa_offset; + __le16 common_calib; + __le16 specific_calib; + u8 bssid[ETH_ALEN]; + __le16 reserved2; +} __packed; /* TOF_RESPONDER_CONFIG_CMD_API_S_VER_7 */ + +#define IWL_RESPONDER_STS_POS 3 +#define IWL_RESPONDER_TOTAL_LTF_POS 6 + +/** + * struct iwl_tof_responder_config_cmd_v8 - ToF AP mode (for debug) + * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field + * @responder_cfg_flags: &iwl_tof_responder_cfg_flags + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @rate: current AP rate + * @channel_num: current AP Channel + * @ctrl_ch_position: coding of the control channel position relative to + * the center frequency, see iwl_mvm_get_ctrl_pos() + * @sta_id: index of the AP STA when in AP mode + * @reserved1: reserved + * @toa_offset: Artificial addition [pSec] for the ToA - to be used for debug + * purposes, simulating station movement by adding various values + * to this field + * @common_calib: XVT: common calibration value + * @specific_calib: XVT: specific calibration value + * @bssid: Current AP BSSID + * @r2i_ndp_params: parameters for R2I NDP. + * bits 0 - 2: max number of LTF repetitions + * bits 3 - 5: max number of spatial streams (supported values are < 2) + * bits 6 - 7: max number of total LTFs see + * &enum ieee80211_range_params_max_total_ltf + * @i2r_ndp_params: parameters for I2R NDP. + * bits 0 - 2: max number of LTF repetitions + * bits 3 - 5: max number of spatial streams + * bits 6 - 7: max number of total LTFs see + * &enum ieee80211_range_params_max_total_ltf + */ +struct iwl_tof_responder_config_cmd_v8 { + __le32 cmd_valid_fields; + __le32 responder_cfg_flags; + u8 format_bw; + u8 rate; + u8 channel_num; + u8 ctrl_ch_position; + u8 sta_id; + u8 reserved1; + __le16 toa_offset; + __le16 common_calib; + __le16 specific_calib; + u8 bssid[ETH_ALEN]; + u8 r2i_ndp_params; + u8 i2r_ndp_params; +} __packed; /* TOF_RESPONDER_CONFIG_CMD_API_S_VER_8 */ + +/** + * struct iwl_tof_responder_config_cmd_v9 - ToF AP mode (for debug) + * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field + * @responder_cfg_flags: &iwl_tof_responder_cfg_flags + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @bss_color: current AP bss_color + * @channel_num: current AP Channel + * @ctrl_ch_position: coding of the control channel position relative to + * the center frequency, see iwl_mvm_get_ctrl_pos() + * @sta_id: index of the AP STA when in AP mode + * @reserved1: reserved + * @toa_offset: Artificial addition [pSec] for the ToA - to be used for debug + * purposes, simulating station movement by adding various values + * to this field + * @common_calib: XVT: common calibration value + * @specific_calib: XVT: specific calibration value + * @bssid: Current AP BSSID + * @r2i_ndp_params: parameters for R2I NDP. + * bits 0 - 2: max number of LTF repetitions + * bits 3 - 5: max number of spatial streams (supported values are < 2) + * bits 6 - 7: max number of total LTFs see + * &enum ieee80211_range_params_max_total_ltf + * @i2r_ndp_params: parameters for I2R NDP. + * bits 0 - 2: max number of LTF repetitions + * bits 3 - 5: max number of spatial streams + * bits 6 - 7: max number of total LTFs see + * &enum ieee80211_range_params_max_total_ltf + * @min_time_between_msr: for non trigger based NDP ranging, minimum time + * between measurements in milliseconds. + * @max_time_between_msr: for non trigger based NDP ranging, maximum time + * between measurements in milliseconds. + */ +struct iwl_tof_responder_config_cmd_v9 { + __le32 cmd_valid_fields; + __le32 responder_cfg_flags; + u8 format_bw; + u8 bss_color; + u8 channel_num; + u8 ctrl_ch_position; + u8 sta_id; + u8 reserved1; + __le16 toa_offset; + __le16 common_calib; + __le16 specific_calib; + u8 bssid[ETH_ALEN]; + u8 r2i_ndp_params; + u8 i2r_ndp_params; + __le16 min_time_between_msr; + __le16 max_time_between_msr; +} __packed; /* TOF_RESPONDER_CONFIG_CMD_API_S_VER_8 */ + +#define IWL_LCI_CIVIC_IE_MAX_SIZE 400 + +/** + * struct iwl_tof_responder_dyn_config_cmd - Dynamic responder settings + * @lci_len: The length of the 1st (LCI) part in the @lci_civic buffer + * @civic_len: The length of the 2nd (CIVIC) part in the @lci_civic buffer + * @lci_civic: The LCI/CIVIC buffer. LCI data (if exists) comes first, then, if + * needed, 0-padding such that the next part is dword-aligned, then CIVIC + * data (if exists) follows, and then 0-padding again to complete a + * 4-multiple long buffer. + */ +struct iwl_tof_responder_dyn_config_cmd_v2 { + __le32 lci_len; + __le32 civic_len; + u8 lci_civic[]; +} __packed; /* TOF_RESPONDER_DYN_CONFIG_CMD_API_S_VER_2 */ + +#define IWL_LCI_MAX_SIZE 160 +#define IWL_CIVIC_MAX_SIZE 160 +#define HLTK_11AZ_LEN 32 + +/** + * enum iwl_responder_dyn_cfg_valid_flags - valid flags for dyn_config_cmd + * @IWL_RESPONDER_DYN_CFG_VALID_LCI: LCI data is valid + * @IWL_RESPONDER_DYN_CFG_VALID_CIVIC: Civic data is valid + * @IWL_RESPONDER_DYN_CFG_VALID_PASN_STA: the pasn_addr, HLTK and cipher fields + * are valid. + */ +enum iwl_responder_dyn_cfg_valid_flags { + IWL_RESPONDER_DYN_CFG_VALID_LCI = BIT(0), + IWL_RESPONDER_DYN_CFG_VALID_CIVIC = BIT(1), + IWL_RESPONDER_DYN_CFG_VALID_PASN_STA = BIT(2), +}; + +/** + * struct iwl_tof_responder_dyn_config_cmd - Dynamic responder settings + * @cipher: The negotiated cipher. see &enum iwl_location_cipher. + * @valid_flags: flags indicating which fields in the command are valid. see + * &enum iwl_responder_dyn_cfg_valid_flags. + * @lci_len: length of the LCI data in bytes + * @civic_len: length of the Civic data in bytes + * @lci_buf: the LCI buffer + * @civic_buf: the Civic buffer + * @hltk_buf: HLTK for secure LTF bits generation for the specified station + * @addr: mac address of the station for which to use the HLTK + * @reserved: for alignment + */ +struct iwl_tof_responder_dyn_config_cmd { + u8 cipher; + u8 valid_flags; + u8 lci_len; + u8 civic_len; + u8 lci_buf[IWL_LCI_MAX_SIZE]; + u8 civic_buf[IWL_LCI_MAX_SIZE]; + u8 hltk_buf[HLTK_11AZ_LEN]; + u8 addr[ETH_ALEN]; + u8 reserved[2]; +} __packed; /* TOF_RESPONDER_DYN_CONFIG_CMD_API_S_VER_3 */ + +/** + * struct iwl_tof_range_req_ext_cmd - extended range req for WLS + * @tsf_timer_offset_msec: the recommended time offset (mSec) from the AP's TSF + * @reserved: reserved + * @min_delta_ftm: Minimal time between two consecutive measurements, + * in units of 100us. 0 means no preference by station + * @ftm_format_and_bw20M: FTM Channel Spacing/Format for 20MHz: recommended + * value be sent to the AP + * @ftm_format_and_bw40M: FTM Channel Spacing/Format for 40MHz: recommended + * value to be sent to the AP + * @ftm_format_and_bw80M: FTM Channel Spacing/Format for 80MHz: recommended + * value to be sent to the AP + */ +struct iwl_tof_range_req_ext_cmd { + __le16 tsf_timer_offset_msec; + __le16 reserved; + u8 min_delta_ftm; + u8 ftm_format_and_bw20M; + u8 ftm_format_and_bw40M; + u8 ftm_format_and_bw80M; +} __packed; + +/** + * enum iwl_tof_location_query - values for query bitmap + * @IWL_TOF_LOC_LCI: query LCI + * @IWL_TOF_LOC_CIVIC: query civic + */ +enum iwl_tof_location_query { + IWL_TOF_LOC_LCI = 0x01, + IWL_TOF_LOC_CIVIC = 0x02, +}; + + /** + * struct iwl_tof_range_req_ap_entry_v2 - AP configuration parameters + * @channel_num: Current AP Channel + * @bandwidth: Current AP Bandwidth. One of iwl_tof_bandwidth. + * @tsf_delta_direction: TSF relatively to the subject AP + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @bssid: AP's BSSID + * @measure_type: Measurement type: 0 - two sided, 1 - One sided + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of the + * number of measurement iterations (min 2^0 = 1, max 2^14) + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts = 0 + * @samples_per_burst: 2-sided: the number of FTMs pairs in single Burst (1-31); + * 1-sided: how many rts/cts pairs should be used per burst. + * @retries_per_sample: Max number of retries that the LMAC should send + * in case of no replies by the AP. + * @tsf_delta: TSF Delta in units of microseconds. + * The difference between the AP TSF and the device local clock. + * @location_req: Location Request Bit[0] LCI should be sent in the FTMR; + * Bit[1] Civic should be sent in the FTMR + * @asap_mode: 0 - non asap mode, 1 - asap mode (not relevant for one sided) + * @enable_dyn_ack: Enable Dynamic ACK BW. + * 0: Initiator interact with regular AP; + * 1: Initiator interact with Responder machine: need to send the + * Initiator Acks with HT 40MHz / 80MHz, since the Responder should + * use it for its ch est measurement (this flag will be set when we + * configure the opposite machine to be Responder). + * @rssi: Last received value + * legal values: -128-0 (0x7f). above 0x0 indicating an invalid value. + * @algo_type: &enum iwl_tof_algo_type + * @notify_mcsi: &enum iwl_tof_mcsi_ntfy. + * @reserved: For alignment and future use + */ +struct iwl_tof_range_req_ap_entry_v2 { + u8 channel_num; + u8 bandwidth; + u8 tsf_delta_direction; + u8 ctrl_ch_position; + u8 bssid[ETH_ALEN]; + u8 measure_type; + u8 num_of_bursts; + __le16 burst_period; + u8 samples_per_burst; + u8 retries_per_sample; + __le32 tsf_delta; + u8 location_req; + u8 asap_mode; + u8 enable_dyn_ack; + s8 rssi; + u8 algo_type; + u8 notify_mcsi; + __le16 reserved; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_2 */ + +/** + * enum iwl_initiator_ap_flags - per responder FTM configuration flags + * @IWL_INITIATOR_AP_FLAGS_ASAP: Request for ASAP measurement. + * @IWL_INITIATOR_AP_FLAGS_LCI_REQUEST: Request for LCI information + * @IWL_INITIATOR_AP_FLAGS_CIVIC_REQUEST: Request for CIVIC information + * @IWL_INITIATOR_AP_FLAGS_DYN_ACK: Send HT/VHT ack for FTM frames. If not set, + * 20Mhz dup acks will be sent. + * @IWL_INITIATOR_AP_FLAGS_ALGO_LR: Use LR algo type for rtt calculation. + * Default algo type is ML. + * @IWL_INITIATOR_AP_FLAGS_ALGO_FFT: Use FFT algo type for rtt calculation. + * Default algo type is ML. + * @IWL_INITIATOR_AP_FLAGS_MCSI_REPORT: Send the MCSI for each FTM frame to the + * driver. + * @IWL_INITIATOR_AP_FLAGS_NON_TB: Use non trigger based flow + * @IWL_INITIATOR_AP_FLAGS_TB: Use trigger based flow + * @IWL_INITIATOR_AP_FLAGS_SECURED: request secure LTF measurement + * @IWL_INITIATOR_AP_FLAGS_LMR_FEEDBACK: Send LMR feedback + * @IWL_INITIATOR_AP_FLAGS_USE_CALIB: Use calibration values from the request + * instead of fw internal values. + * @IWL_INITIATOR_AP_FLAGS_PMF: request to protect the negotiation and LMR + * frames with protected management frames. + * @IWL_INITIATOR_AP_FLAGS_TERMINATE_ON_LMR_FEEDBACK: terminate the session if + * the responder asked for LMR feedback although the initiator did not set + * the LMR feedback bit in the FTM request. If not set, the initiator will + * continue with the session and will provide the LMR feedback. + */ +enum iwl_initiator_ap_flags { + IWL_INITIATOR_AP_FLAGS_ASAP = BIT(1), + IWL_INITIATOR_AP_FLAGS_LCI_REQUEST = BIT(2), + IWL_INITIATOR_AP_FLAGS_CIVIC_REQUEST = BIT(3), + IWL_INITIATOR_AP_FLAGS_DYN_ACK = BIT(4), + IWL_INITIATOR_AP_FLAGS_ALGO_LR = BIT(5), + IWL_INITIATOR_AP_FLAGS_ALGO_FFT = BIT(6), + IWL_INITIATOR_AP_FLAGS_MCSI_REPORT = BIT(8), + IWL_INITIATOR_AP_FLAGS_NON_TB = BIT(9), + IWL_INITIATOR_AP_FLAGS_TB = BIT(10), + IWL_INITIATOR_AP_FLAGS_SECURED = BIT(11), + IWL_INITIATOR_AP_FLAGS_LMR_FEEDBACK = BIT(12), + IWL_INITIATOR_AP_FLAGS_USE_CALIB = BIT(13), + IWL_INITIATOR_AP_FLAGS_PMF = BIT(14), + IWL_INITIATOR_AP_FLAGS_TERMINATE_ON_LMR_FEEDBACK = BIT(15), +}; + +/** + * struct iwl_tof_range_req_ap_entry_v3 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @bandwidth: AP bandwidth. One of iwl_tof_bandwidth. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @reserved: For alignment and future use + * @tsf_delta: not in use + */ +struct iwl_tof_range_req_ap_entry_v3 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 bandwidth; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + __le16 reserved; + __le32 tsf_delta; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_3 */ + +/** + * enum iwl_location_frame_format - location frame formats + * @IWL_LOCATION_FRAME_FORMAT_LEGACY: legacy + * @IWL_LOCATION_FRAME_FORMAT_HT: HT + * @IWL_LOCATION_FRAME_FORMAT_VHT: VHT + * @IWL_LOCATION_FRAME_FORMAT_HE: HE + */ +enum iwl_location_frame_format { + IWL_LOCATION_FRAME_FORMAT_LEGACY, + IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FRAME_FORMAT_VHT, + IWL_LOCATION_FRAME_FORMAT_HE, +}; + +/** + * enum iwl_location_bw - location bandwidth selection + * @IWL_LOCATION_BW_20MHZ: 20MHz + * @IWL_LOCATION_BW_40MHZ: 40MHz + * @IWL_LOCATION_BW_80MHZ: 80MHz + */ +enum iwl_location_bw { + IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_BW_40MHZ, + IWL_LOCATION_BW_80MHZ, + IWL_LOCATION_BW_160MHZ, +}; + +#define TK_11AZ_LEN 32 + +#define LOCATION_BW_POS 4 + +/** + * struct iwl_tof_range_req_ap_entry_v4 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @reserved: For alignment and future use + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + */ +struct iwl_tof_range_req_ap_entry_v4 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + __le16 reserved; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_4 */ + +/** + * enum iwl_location_cipher - location cipher selection + * @IWL_LOCATION_CIPHER_CCMP_128: CCMP 128 + * @IWL_LOCATION_CIPHER_GCMP_128: GCMP 128 + * @IWL_LOCATION_CIPHER_GCMP_256: GCMP 256 + * @IWL_LOCATION_CIPHER_INVALID: security is not used. + * @IWL_LOCATION_CIPHER_MAX: maximum value for this enum. + */ +enum iwl_location_cipher { + IWL_LOCATION_CIPHER_CCMP_128, + IWL_LOCATION_CIPHER_GCMP_128, + IWL_LOCATION_CIPHER_GCMP_256, + IWL_LOCATION_CIPHER_INVALID, + IWL_LOCATION_CIPHER_MAX, +}; + +/** + * struct iwl_tof_range_req_ap_entry_v6 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @sta_id: the station id of the AP. Only relevant when associated to the AP, + * otherwise should be set to &IWL_MVM_INVALID_STA. + * @cipher: pairwise cipher suite for secured measurement. + * &enum iwl_location_cipher. + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + * @calib: An array of calibration values per FTM rx bandwidth. + * If &IWL_INITIATOR_AP_FLAGS_USE_CALIB is set, the fw will use the + * calibration value that corresponds to the rx bandwidth of the FTM + * frame. + * @beacon_interval: beacon interval of the AP in TUs. Only required if + * &IWL_INITIATOR_AP_FLAGS_TB is set. + */ +struct iwl_tof_range_req_ap_entry_v6 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + u8 sta_id; + u8 cipher; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + __le16 calib[IWL_TOF_BW_NUM]; + __le16 beacon_interval; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_6 */ + +/** + * struct iwl_tof_range_req_ap_entry_v7 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @sta_id: the station id of the AP. Only relevant when associated to the AP, + * otherwise should be set to &IWL_MVM_INVALID_STA. + * @cipher: pairwise cipher suite for secured measurement. + * &enum iwl_location_cipher. + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + * @calib: An array of calibration values per FTM rx bandwidth. + * If &IWL_INITIATOR_AP_FLAGS_USE_CALIB is set, the fw will use the + * calibration value that corresponds to the rx bandwidth of the FTM + * frame. + * @beacon_interval: beacon interval of the AP in TUs. Only required if + * &IWL_INITIATOR_AP_FLAGS_TB is set. + * @rx_pn: the next expected PN for protected management frames Rx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @tx_pn: the next PN to use for protected management frames Tx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + */ +struct iwl_tof_range_req_ap_entry_v7 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + u8 sta_id; + u8 cipher; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + __le16 calib[IWL_TOF_BW_NUM]; + __le16 beacon_interval; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_7 */ + +#define IWL_LOCATION_MAX_STS_POS 3 + +/** + * struct iwl_tof_range_req_ap_entry_v8 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @sta_id: the station id of the AP. Only relevant when associated to the AP, + * otherwise should be set to &IWL_MVM_INVALID_STA. + * @cipher: pairwise cipher suite for secured measurement. + * &enum iwl_location_cipher. + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + * @calib: An array of calibration values per FTM rx bandwidth. + * If &IWL_INITIATOR_AP_FLAGS_USE_CALIB is set, the fw will use the + * calibration value that corresponds to the rx bandwidth of the FTM + * frame. + * @beacon_interval: beacon interval of the AP in TUs. Only required if + * &IWL_INITIATOR_AP_FLAGS_TB is set. + * @rx_pn: the next expected PN for protected management frames Rx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @tx_pn: the next PN to use for protected management frames Tx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @r2i_ndp_params: parameters for R2I NDP ranging negotiation. + * bits 0 - 2: max LTF repetitions + * bits 3 - 5: max number of spatial streams + * bits 6 - 7: reserved + * @i2r_ndp_params: parameters for I2R NDP ranging negotiation. + * bits 0 - 2: max LTF repetitions + * bits 3 - 5: max number of spatial streams (supported values are < 2) + * bits 6 - 7: reserved + * @r2i_max_total_ltf: R2I Max Total LTFs for NDP ranging negotiation. + * One of &enum ieee80211_range_params_max_total_ltf. + * @i2r_max_total_ltf: I2R Max Total LTFs for NDP ranging negotiation. + * One of &enum ieee80211_range_params_max_total_ltf. + */ +struct iwl_tof_range_req_ap_entry_v8 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + u8 sta_id; + u8 cipher; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + __le16 calib[IWL_TOF_BW_NUM]; + __le16 beacon_interval; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; + u8 r2i_ndp_params; + u8 i2r_ndp_params; + u8 r2i_max_total_ltf; + u8 i2r_max_total_ltf; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_8 */ + +/** + * struct iwl_tof_range_req_ap_entry_v9 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: For EDCA based ranging: Recommended value to be sent to the + * AP. Measurement periodicity In units of 100ms. ignored if + * num_of_bursts_exp = 0. + * For non trigger based NDP ranging, the maximum time between + * measurements in units of milliseconds. + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @sta_id: the station id of the AP. Only relevant when associated to the AP, + * otherwise should be set to &IWL_MVM_INVALID_STA. + * @cipher: pairwise cipher suite for secured measurement. + * &enum iwl_location_cipher. + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + * @calib: An array of calibration values per FTM rx bandwidth. + * If &IWL_INITIATOR_AP_FLAGS_USE_CALIB is set, the fw will use the + * calibration value that corresponds to the rx bandwidth of the FTM + * frame. + * @beacon_interval: beacon interval of the AP in TUs. Only required if + * &IWL_INITIATOR_AP_FLAGS_TB is set. + * @bss_color: the BSS color of the responder. Only valid if + * &IWL_INITIATOR_AP_FLAGS_TB or &IWL_INITIATOR_AP_FLAGS_NON_TB is set. + * @rx_pn: the next expected PN for protected management frames Rx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @tx_pn: the next PN to use for protected management frames Tx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @r2i_ndp_params: parameters for R2I NDP ranging negotiation. + * bits 0 - 2: max LTF repetitions + * bits 3 - 5: max number of spatial streams + * bits 6 - 7: reserved + * @i2r_ndp_params: parameters for I2R NDP ranging negotiation. + * bits 0 - 2: max LTF repetitions + * bits 3 - 5: max number of spatial streams (supported values are < 2) + * bits 6 - 7: reserved + * @r2i_max_total_ltf: R2I Max Total LTFs for NDP ranging negotiation. + * One of &enum ieee80211_range_params_max_total_ltf. + * @i2r_max_total_ltf: I2R Max Total LTFs for NDP ranging negotiation. + * One of &enum ieee80211_range_params_max_total_ltf. + * @bss_color: the BSS color of the responder. Only valid if + * &IWL_INITIATOR_AP_FLAGS_NON_TB or &IWL_INITIATOR_AP_FLAGS_TB is set. + * @band: 0 for 5.2 GHz, 1 for 2.4 GHz, 2 for 6GHz + * @min_time_between_msr: For non trigger based NDP ranging, the minimum time + * between measurements in units of milliseconds + */ +struct iwl_tof_range_req_ap_entry_v9 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + u8 sta_id; + u8 cipher; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + __le16 calib[IWL_TOF_BW_NUM]; + u16 beacon_interval; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; + u8 r2i_ndp_params; + u8 i2r_ndp_params; + u8 r2i_max_total_ltf; + u8 i2r_max_total_ltf; + u8 bss_color; + u8 band; + __le16 min_time_between_msr; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_9 */ + +/** + * enum iwl_tof_response_mode + * @IWL_MVM_TOF_RESPONSE_ASAP: report each AP measurement separately as soon as + * possible (not supported for this release) + * @IWL_MVM_TOF_RESPONSE_TIMEOUT: report all AP measurements as a batch upon + * timeout expiration + * @IWL_MVM_TOF_RESPONSE_COMPLETE: report all AP measurements as a batch at the + * earlier of: measurements completion / timeout + * expiration. + */ +enum iwl_tof_response_mode { + IWL_MVM_TOF_RESPONSE_ASAP, + IWL_MVM_TOF_RESPONSE_TIMEOUT, + IWL_MVM_TOF_RESPONSE_COMPLETE, +}; + +/** + * enum iwl_tof_initiator_flags + * + * @IWL_TOF_INITIATOR_FLAGS_FAST_ALGO_DISABLED: disable fast algo, meaning run + * the algo on ant A+B, instead of only one of them. + * @IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_A: open RX antenna A for FTMs RX + * @IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_B: open RX antenna B for FTMs RX + * @IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_C: open RX antenna C for FTMs RX + * @IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_A: use antenna A fo TX ACKs during FTM + * @IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_B: use antenna B fo TX ACKs during FTM + * @IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_C: use antenna C fo TX ACKs during FTM + * @IWL_TOF_INITIATOR_FLAGS_MACADDR_RANDOM: use random mac address for FTM + * @IWL_TOF_INITIATOR_FLAGS_SPECIFIC_CALIB: use the specific calib value from + * the range request command + * @IWL_TOF_INITIATOR_FLAGS_COMMON_CALIB: use the common calib value from the + * ragne request command + * @IWL_TOF_INITIATOR_FLAGS_NON_ASAP_SUPPORT: support non-asap measurements + */ +enum iwl_tof_initiator_flags { + IWL_TOF_INITIATOR_FLAGS_FAST_ALGO_DISABLED = BIT(0), + IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_A = BIT(1), + IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_B = BIT(2), + IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_C = BIT(3), + IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_A = BIT(4), + IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_B = BIT(5), + IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_C = BIT(6), + IWL_TOF_INITIATOR_FLAGS_MACADDR_RANDOM = BIT(7), + IWL_TOF_INITIATOR_FLAGS_SPECIFIC_CALIB = BIT(15), + IWL_TOF_INITIATOR_FLAGS_COMMON_CALIB = BIT(16), + IWL_TOF_INITIATOR_FLAGS_NON_ASAP_SUPPORT = BIT(20), +}; /* LOCATION_RANGE_REQ_CMD_API_S_VER_5 */ + +#define IWL_MVM_TOF_MAX_APS 5 +#define IWL_MVM_TOF_MAX_TWO_SIDED_APS 5 + +/** + * struct iwl_tof_range_req_cmd_v5 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @initiator: 0- NW initiated, 1 - Client Initiated + * @one_sided_los_disable: '0'- run ML-Algo for both ToF/OneSided, + * '1' - run ML-Algo for ToF only + * @req_timeout: Requested timeout of the response in units of 100ms. + * This is equivalent to the session time configured to the + * LMAC in Initiator Request + * @report_policy: Supported partially for this release: For current release - + * the range report will be uploaded as a batch when ready or + * when the session is done (successfully / partially). + * one of iwl_tof_response_mode. + * @reserved0: reserved + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @macaddr_random: '0' Use default source MAC address (i.e. p2_p), + * '1' Use MAC Address randomization according to the below + * @range_req_bssid: ranging request BSSID + * @macaddr_template: MAC address template to use for non-randomized bits + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @ftm_rx_chains: Rx chain to open to receive Responder's FTMs (XVT) + * @ftm_tx_chains: Tx chain to send the ack to the Responder FTM (XVT) + * @common_calib: The common calib value to inject to this measurement calc + * @specific_calib: The specific calib value to inject to this measurement calc + * @ap: per-AP request data + */ +struct iwl_tof_range_req_cmd_v5 { + __le32 initiator_flags; + u8 request_id; + u8 initiator; + u8 one_sided_los_disable; + u8 req_timeout; + u8 report_policy; + u8 reserved0; + u8 num_of_ap; + u8 macaddr_random; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 ftm_rx_chains; + u8 ftm_tx_chains; + __le16 common_calib; + __le16 specific_calib; + struct iwl_tof_range_req_ap_entry_v2 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; +/* LOCATION_RANGE_REQ_CMD_API_S_VER_5 */ + +/** + * struct iwl_tof_range_req_cmd_v7 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @common_calib: The common calib value to inject to this measurement calc + * @specific_calib: The specific calib value to inject to this measurement calc + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v7 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + __le16 common_calib; + __le16 specific_calib; + struct iwl_tof_range_req_ap_entry_v3 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_7 */ + +/** + * struct iwl_tof_range_req_cmd_v8 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @common_calib: The common calib value to inject to this measurement calc + * @specific_calib: The specific calib value to inject to this measurement calc + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v8 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + __le16 common_calib; + __le16 specific_calib; + struct iwl_tof_range_req_ap_entry_v4 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_8 */ + +/** + * struct iwl_tof_range_req_cmd_v9 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v9 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + struct iwl_tof_range_req_ap_entry_v6 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_9 */ + +/** + * struct iwl_tof_range_req_cmd_v11 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v11 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + struct iwl_tof_range_req_ap_entry_v7 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_11 */ + +/** + * struct iwl_tof_range_req_cmd_v12 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v12 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + struct iwl_tof_range_req_ap_entry_v8 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_12 */ + +/** + * struct iwl_tof_range_req_cmd_v13 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v9. + */ +struct iwl_tof_range_req_cmd_v13 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + struct iwl_tof_range_req_ap_entry_v9 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_13 */ + +/* + * enum iwl_tof_range_request_status - status of the sent request + * @IWL_TOF_RANGE_REQUEST_STATUS_SUCCESSFUL - FW successfully received the + * request + * @IWL_TOF_RANGE_REQUEST_STATUS_BUSY - FW is busy with a previous request, the + * sent request will not be handled + */ +enum iwl_tof_range_request_status { + IWL_TOF_RANGE_REQUEST_STATUS_SUCCESS, + IWL_TOF_RANGE_REQUEST_STATUS_BUSY, +}; + +/** + * enum iwl_tof_entry_status + * + * @IWL_TOF_ENTRY_SUCCESS: successful measurement. + * @IWL_TOF_ENTRY_GENERAL_FAILURE: General failure. + * @IWL_TOF_ENTRY_NO_RESPONSE: Responder didn't reply to the request. + * @IWL_TOF_ENTRY_REQUEST_REJECTED: Responder rejected the request. + * @IWL_TOF_ENTRY_NOT_SCHEDULED: Time event was scheduled but not called yet. + * @IWL_TOF_ENTRY_TIMING_MEASURE_TIMEOUT: Time event triggered but no + * measurement was completed. + * @IWL_TOF_ENTRY_TARGET_DIFF_CH_CANNOT_CHANGE: No range due inability to switch + * from the primary channel. + * @IWL_TOF_ENTRY_RANGE_NOT_SUPPORTED: Device doesn't support FTM. + * @IWL_TOF_ENTRY_REQUEST_ABORT_UNKNOWN_REASON: Request aborted due to unknown + * reason. + * @IWL_TOF_ENTRY_LOCATION_INVALID_T1_T4_TIME_STAMP: Failure due to invalid + * T1/T4. + * @IWL_TOF_ENTRY_11MC_PROTOCOL_FAILURE: Failure due to invalid FTM frame + * structure. + * @IWL_TOF_ENTRY_REQUEST_CANNOT_SCHED: Request cannot be scheduled. + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE: Responder cannot serve the + * initiator for some period, period supplied in @refusal_period. + * @IWL_TOF_ENTRY_BAD_REQUEST_ARGS: Bad request arguments. + * @IWL_TOF_ENTRY_WIFI_NOT_ENABLED: Wifi not enabled. + * @IWL_TOF_ENTRY_RESPONDER_OVERRIDE_PARAMS: Responder override the original + * parameters within the current session. + */ +enum iwl_tof_entry_status { + IWL_TOF_ENTRY_SUCCESS = 0, + IWL_TOF_ENTRY_GENERAL_FAILURE = 1, + IWL_TOF_ENTRY_NO_RESPONSE = 2, + IWL_TOF_ENTRY_REQUEST_REJECTED = 3, + IWL_TOF_ENTRY_NOT_SCHEDULED = 4, + IWL_TOF_ENTRY_TIMING_MEASURE_TIMEOUT = 5, + IWL_TOF_ENTRY_TARGET_DIFF_CH_CANNOT_CHANGE = 6, + IWL_TOF_ENTRY_RANGE_NOT_SUPPORTED = 7, + IWL_TOF_ENTRY_REQUEST_ABORT_UNKNOWN_REASON = 8, + IWL_TOF_ENTRY_LOCATION_INVALID_T1_T4_TIME_STAMP = 9, + IWL_TOF_ENTRY_11MC_PROTOCOL_FAILURE = 10, + IWL_TOF_ENTRY_REQUEST_CANNOT_SCHED = 11, + IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE = 12, + IWL_TOF_ENTRY_BAD_REQUEST_ARGS = 13, + IWL_TOF_ENTRY_WIFI_NOT_ENABLED = 14, + IWL_TOF_ENTRY_RESPONDER_OVERRIDE_PARAMS = 15, +}; /* LOCATION_RANGE_RSP_AP_ENTRY_NTFY_API_S_VER_2 */ + +/** + * struct iwl_tof_range_rsp_ap_entry_ntfy_v3 - AP parameters (response) + * @bssid: BSSID of the AP + * @measure_status: current APs measurement status, one of + * &enum iwl_tof_entry_status. + * @measure_bw: Current AP Bandwidth: 0 20MHz, 1 40MHz, 2 80MHz + * @rtt: The Round Trip Time that took for the last measurement for + * current AP [pSec] + * @rtt_variance: The Variance of the RTT values measured for current AP + * @rtt_spread: The Difference between the maximum and the minimum RTT + * values measured for current AP in the current session [pSec] + * @rssi: RSSI as uploaded in the Channel Estimation notification + * @rssi_spread: The Difference between the maximum and the minimum RSSI values + * measured for current AP in the current session + * @reserved: reserved + * @refusal_period: refusal period in case of + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE [sec] + * @range: Measured range [cm] + * @range_variance: Measured range variance [cm] + * @timestamp: The GP2 Clock [usec] where Channel Estimation notification was + * uploaded by the LMAC + * @t2t3_initiator: as calculated from the algo in the initiator + * @t1t4_responder: as calculated from the algo in the responder + * @common_calib: Calib val that was used in for this AP measurement + * @specific_calib: val that was used in for this AP measurement + * @papd_calib_output: The result of the tof papd calibration that was injected + * into the algorithm. + */ +struct iwl_tof_range_rsp_ap_entry_ntfy_v3 { + u8 bssid[ETH_ALEN]; + u8 measure_status; + u8 measure_bw; + __le32 rtt; + __le32 rtt_variance; + __le32 rtt_spread; + s8 rssi; + u8 rssi_spread; + u8 reserved; + u8 refusal_period; + __le32 range; + __le32 range_variance; + __le32 timestamp; + __le32 t2t3_initiator; + __le32 t1t4_responder; + __le16 common_calib; + __le16 specific_calib; + __le32 papd_calib_output; +} __packed; /* LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_3 */ + +/** + * struct iwl_tof_range_rsp_ap_entry_ntfy_v4 - AP parameters (response) + * @bssid: BSSID of the AP + * @measure_status: current APs measurement status, one of + * &enum iwl_tof_entry_status. + * @measure_bw: Current AP Bandwidth: 0 20MHz, 1 40MHz, 2 80MHz + * @rtt: The Round Trip Time that took for the last measurement for + * current AP [pSec] + * @rtt_variance: The Variance of the RTT values measured for current AP + * @rtt_spread: The Difference between the maximum and the minimum RTT + * values measured for current AP in the current session [pSec] + * @rssi: RSSI as uploaded in the Channel Estimation notification + * @rssi_spread: The Difference between the maximum and the minimum RSSI values + * measured for current AP in the current session + * @last_burst: 1 if no more FTM sessions are scheduled for this responder + * @refusal_period: refusal period in case of + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE [sec] + * @timestamp: The GP2 Clock [usec] where Channel Estimation notification was + * uploaded by the LMAC + * @start_tsf: measurement start time in TSF of the mac specified in the range + * request + * @rx_rate_n_flags: rate and flags of the last FTM frame received from this + * responder + * @tx_rate_n_flags: rate and flags of the last ack sent to this responder + * @t2t3_initiator: as calculated from the algo in the initiator + * @t1t4_responder: as calculated from the algo in the responder + * @common_calib: Calib val that was used in for this AP measurement + * @specific_calib: val that was used in for this AP measurement + * @papd_calib_output: The result of the tof papd calibration that was injected + * into the algorithm. + */ +struct iwl_tof_range_rsp_ap_entry_ntfy_v4 { + u8 bssid[ETH_ALEN]; + u8 measure_status; + u8 measure_bw; + __le32 rtt; + __le32 rtt_variance; + __le32 rtt_spread; + s8 rssi; + u8 rssi_spread; + u8 last_burst; + u8 refusal_period; + __le32 timestamp; + __le32 start_tsf; + __le32 rx_rate_n_flags; + __le32 tx_rate_n_flags; + __le32 t2t3_initiator; + __le32 t1t4_responder; + __le16 common_calib; + __le16 specific_calib; + __le32 papd_calib_output; +} __packed; /* LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_4 */ + +/** + * struct iwl_tof_range_rsp_ap_entry_ntfy_v5 - AP parameters (response) + * @bssid: BSSID of the AP + * @measure_status: current APs measurement status, one of + * &enum iwl_tof_entry_status. + * @measure_bw: Current AP Bandwidth: 0 20MHz, 1 40MHz, 2 80MHz + * @rtt: The Round Trip Time that took for the last measurement for + * current AP [pSec] + * @rtt_variance: The Variance of the RTT values measured for current AP + * @rtt_spread: The Difference between the maximum and the minimum RTT + * values measured for current AP in the current session [pSec] + * @rssi: RSSI as uploaded in the Channel Estimation notification + * @rssi_spread: The Difference between the maximum and the minimum RSSI values + * measured for current AP in the current session + * @last_burst: 1 if no more FTM sessions are scheduled for this responder + * @refusal_period: refusal period in case of + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE [sec] + * @timestamp: The GP2 Clock [usec] where Channel Estimation notification was + * uploaded by the LMAC + * @start_tsf: measurement start time in TSF of the mac specified in the range + * request + * @rx_rate_n_flags: rate and flags of the last FTM frame received from this + * responder + * @tx_rate_n_flags: rate and flags of the last ack sent to this responder + * @t2t3_initiator: as calculated from the algo in the initiator + * @t1t4_responder: as calculated from the algo in the responder + * @common_calib: Calib val that was used in for this AP measurement + * @specific_calib: val that was used in for this AP measurement + * @papd_calib_output: The result of the tof papd calibration that was injected + * into the algorithm. + * @rttConfidence: a value between 0 - 31 that represents the rtt accuracy. + * @reserved: for alignment + */ +struct iwl_tof_range_rsp_ap_entry_ntfy_v5 { + u8 bssid[ETH_ALEN]; + u8 measure_status; + u8 measure_bw; + __le32 rtt; + __le32 rtt_variance; + __le32 rtt_spread; + s8 rssi; + u8 rssi_spread; + u8 last_burst; + u8 refusal_period; + __le32 timestamp; + __le32 start_tsf; + __le32 rx_rate_n_flags; + __le32 tx_rate_n_flags; + __le32 t2t3_initiator; + __le32 t1t4_responder; + __le16 common_calib; + __le16 specific_calib; + __le32 papd_calib_output; + u8 rttConfidence; + u8 reserved[3]; +} __packed; /* LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_5 */ + +/** + * struct iwl_tof_range_rsp_ap_entry_ntfy_v6 - AP parameters (response) + * @bssid: BSSID of the AP + * @measure_status: current APs measurement status, one of + * &enum iwl_tof_entry_status. + * @measure_bw: Current AP Bandwidth: 0 20MHz, 1 40MHz, 2 80MHz + * @rtt: The Round Trip Time that took for the last measurement for + * current AP [pSec] + * @rtt_variance: The Variance of the RTT values measured for current AP + * @rtt_spread: The Difference between the maximum and the minimum RTT + * values measured for current AP in the current session [pSec] + * @rssi: RSSI as uploaded in the Channel Estimation notification + * @rssi_spread: The Difference between the maximum and the minimum RSSI values + * measured for current AP in the current session + * @last_burst: 1 if no more FTM sessions are scheduled for this responder + * @refusal_period: refusal period in case of + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE [sec] + * @timestamp: The GP2 Clock [usec] where Channel Estimation notification was + * uploaded by the LMAC + * @start_tsf: measurement start time in TSF of the mac specified in the range + * request + * @rx_rate_n_flags: rate and flags of the last FTM frame received from this + * responder + * @tx_rate_n_flags: rate and flags of the last ack sent to this responder + * @t2t3_initiator: as calculated from the algo in the initiator + * @t1t4_responder: as calculated from the algo in the responder + * @common_calib: Calib val that was used in for this AP measurement + * @specific_calib: val that was used in for this AP measurement + * @papd_calib_output: The result of the tof papd calibration that was injected + * into the algorithm. + * @rttConfidence: a value between 0 - 31 that represents the rtt accuracy. + * @reserved: for alignment + * @rx_pn: the last PN used for this responder Rx in case PMF is configured in + * LE byte order. + * @tx_pn: the last PN used for this responder Tx in case PMF is configured in + * LE byte order. + */ +struct iwl_tof_range_rsp_ap_entry_ntfy_v6 { + u8 bssid[ETH_ALEN]; + u8 measure_status; + u8 measure_bw; + __le32 rtt; + __le32 rtt_variance; + __le32 rtt_spread; + s8 rssi; + u8 rssi_spread; + u8 last_burst; + u8 refusal_period; + __le32 timestamp; + __le32 start_tsf; + __le32 rx_rate_n_flags; + __le32 tx_rate_n_flags; + __le32 t2t3_initiator; + __le32 t1t4_responder; + __le16 common_calib; + __le16 specific_calib; + __le32 papd_calib_output; + u8 rttConfidence; + u8 reserved[3]; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; +} __packed; /* LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_6, + LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_7 */ + + +/** + * enum iwl_tof_response_status - tof response status + * + * @IWL_TOF_RESPONSE_SUCCESS: successful range. + * @IWL_TOF_RESPONSE_TIMEOUT: request aborted due to timeout expiration. + * partial result of ranges done so far is included in the response. + * @IWL_TOF_RESPONSE_ABORTED: Measurement aborted by command. + * @IWL_TOF_RESPONSE_FAILED: Measurement request command failed. + */ +enum iwl_tof_response_status { + IWL_TOF_RESPONSE_SUCCESS = 0, + IWL_TOF_RESPONSE_TIMEOUT = 1, + IWL_TOF_RESPONSE_ABORTED = 4, + IWL_TOF_RESPONSE_FAILED = 5, +}; /* LOCATION_RNG_RSP_STATUS */ + +/** + * struct iwl_tof_range_rsp_ntfy_v5 - ranging response notification + * @request_id: A Token ID of the corresponding Range request + * @request_status: status of current measurement session, one of + * &enum iwl_tof_response_status. + * @last_in_batch: reprot policy (when not all responses are uploaded at once) + * @num_of_aps: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @ap: per-AP data + */ +struct iwl_tof_range_rsp_ntfy_v5 { + u8 request_id; + u8 request_status; + u8 last_in_batch; + u8 num_of_aps; + struct iwl_tof_range_rsp_ap_entry_ntfy_v3 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_RSP_NTFY_API_S_VER_5 */ + +/** + * struct iwl_tof_range_rsp_ntfy_v6 - ranging response notification + * @request_id: A Token ID of the corresponding Range request + * @num_of_aps: Number of APs results + * @last_report: 1 if no more FTM sessions are scheduled, 0 otherwise. + * @reserved: reserved + * @ap: per-AP data + */ +struct iwl_tof_range_rsp_ntfy_v6 { + u8 request_id; + u8 num_of_aps; + u8 last_report; + u8 reserved; + struct iwl_tof_range_rsp_ap_entry_ntfy_v4 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_RSP_NTFY_API_S_VER_6 */ + +/** + * struct iwl_tof_range_rsp_ntfy_v7 - ranging response notification + * @request_id: A Token ID of the corresponding Range request + * @num_of_aps: Number of APs results + * @last_report: 1 if no more FTM sessions are scheduled, 0 otherwise. + * @reserved: reserved + * @ap: per-AP data + */ +struct iwl_tof_range_rsp_ntfy_v7 { + u8 request_id; + u8 num_of_aps; + u8 last_report; + u8 reserved; + struct iwl_tof_range_rsp_ap_entry_ntfy_v5 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_RSP_NTFY_API_S_VER_7 */ + +/** + * struct iwl_tof_range_rsp_ntfy_v8 - ranging response notification + * @request_id: A Token ID of the corresponding Range request + * @num_of_aps: Number of APs results + * @last_report: 1 if no more FTM sessions are scheduled, 0 otherwise. + * @reserved: reserved + * @ap: per-AP data + */ +struct iwl_tof_range_rsp_ntfy_v8 { + u8 request_id; + u8 num_of_aps; + u8 last_report; + u8 reserved; + struct iwl_tof_range_rsp_ap_entry_ntfy_v6 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_RSP_NTFY_API_S_VER_8, + LOCATION_RANGE_RSP_NTFY_API_S_VER_9 */ + +#define IWL_MVM_TOF_MCSI_BUF_SIZE (245) +/** + * struct iwl_tof_mcsi_notif - used for debug + * @token: token ID for the current session + * @role: '0' - initiator, '1' - responder + * @reserved: reserved + * @initiator_bssid: initiator machine + * @responder_bssid: responder machine + * @mcsi_buffer: debug data + */ +struct iwl_tof_mcsi_notif { + u8 token; + u8 role; + __le16 reserved; + u8 initiator_bssid[ETH_ALEN]; + u8 responder_bssid[ETH_ALEN]; + u8 mcsi_buffer[IWL_MVM_TOF_MCSI_BUF_SIZE * 4]; +} __packed; + +/** + * struct iwl_tof_range_abort_cmd + * @request_id: corresponds to a range request + * @reserved: reserved + */ +struct iwl_tof_range_abort_cmd { + u8 request_id; + u8 reserved[3]; +} __packed; + +enum ftm_responder_stats_flags { + FTM_RESP_STAT_NON_ASAP_STARTED = BIT(0), + FTM_RESP_STAT_NON_ASAP_IN_WIN = BIT(1), + FTM_RESP_STAT_NON_ASAP_OUT_WIN = BIT(2), + FTM_RESP_STAT_TRIGGER_DUP = BIT(3), + FTM_RESP_STAT_DUP = BIT(4), + FTM_RESP_STAT_DUP_IN_WIN = BIT(5), + FTM_RESP_STAT_DUP_OUT_WIN = BIT(6), + FTM_RESP_STAT_SCHED_SUCCESS = BIT(7), + FTM_RESP_STAT_ASAP_REQ = BIT(8), + FTM_RESP_STAT_NON_ASAP_REQ = BIT(9), + FTM_RESP_STAT_ASAP_RESP = BIT(10), + FTM_RESP_STAT_NON_ASAP_RESP = BIT(11), + FTM_RESP_STAT_FAIL_INITIATOR_INACTIVE = BIT(12), + FTM_RESP_STAT_FAIL_INITIATOR_OUT_WIN = BIT(13), + FTM_RESP_STAT_FAIL_INITIATOR_RETRY_LIM = BIT(14), + FTM_RESP_STAT_FAIL_NEXT_SERVED = BIT(15), + FTM_RESP_STAT_FAIL_TRIGGER_ERR = BIT(16), + FTM_RESP_STAT_FAIL_GC = BIT(17), + FTM_RESP_STAT_SUCCESS = BIT(18), + FTM_RESP_STAT_INTEL_IE = BIT(19), + FTM_RESP_STAT_INITIATOR_ACTIVE = BIT(20), + FTM_RESP_STAT_MEASUREMENTS_AVAILABLE = BIT(21), + FTM_RESP_STAT_TRIGGER_UNKNOWN = BIT(22), + FTM_RESP_STAT_PROCESS_FAIL = BIT(23), + FTM_RESP_STAT_ACK = BIT(24), + FTM_RESP_STAT_NACK = BIT(25), + FTM_RESP_STAT_INVALID_INITIATOR_ID = BIT(26), + FTM_RESP_STAT_TIMER_MIN_DELTA = BIT(27), + FTM_RESP_STAT_INITIATOR_REMOVED = BIT(28), + FTM_RESP_STAT_INITIATOR_ADDED = BIT(29), + FTM_RESP_STAT_ERR_LIST_FULL = BIT(30), + FTM_RESP_STAT_INITIATOR_SCHED_NOW = BIT(31), +}; /* RESP_IND_E */ + +/** + * struct iwl_ftm_responder_stats - FTM responder statistics + * @addr: initiator address + * @success_ftm: number of successful ftm frames + * @ftm_per_burst: num of FTM frames that were received + * @flags: &enum ftm_responder_stats_flags + * @duration: actual duration of FTM + * @allocated_duration: time that was allocated for this FTM session + * @bw: FTM request bandwidth + * @rate: FTM request rate + * @reserved: for alingment and future use + */ +struct iwl_ftm_responder_stats { + u8 addr[ETH_ALEN]; + u8 success_ftm; + u8 ftm_per_burst; + __le32 flags; + __le32 duration; + __le32 allocated_duration; + u8 bw; + u8 rate; + __le16 reserved; +} __packed; /* TOF_RESPONDER_STATISTICS_NTFY_S_VER_2 */ + +#define IWL_CSI_MAX_EXPECTED_CHUNKS 16 + +#define IWL_CSI_CHUNK_CTL_NUM_MASK_VER_1 0x0003 +#define IWL_CSI_CHUNK_CTL_IDX_MASK_VER_1 0x000c + +#define IWL_CSI_CHUNK_CTL_NUM_MASK_VER_2 0x00ff +#define IWL_CSI_CHUNK_CTL_IDX_MASK_VER_2 0xff00 + +struct iwl_csi_chunk_notification { + __le32 token; + __le16 seq; + __le16 ctl; + __le32 size; + u8 data[]; +} __packed; /* CSI_CHUNKS_HDR_NTFY_API_S_VER_1/VER_2 */ + +#endif /* __iwl_fw_api_location_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/mac-cfg.h b/sys/contrib/dev/iwlwifi/fw/api/mac-cfg.h new file mode 100644 index 00000000000..d088c820b1a --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/mac-cfg.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_mac_cfg_h__ +#define __iwl_fw_api_mac_cfg_h__ + +/** + * enum iwl_mac_conf_subcmd_ids - mac configuration command IDs + */ +enum iwl_mac_conf_subcmd_ids { + /** + * @LOW_LATENCY_CMD: &struct iwl_mac_low_latency_cmd + */ + LOW_LATENCY_CMD = 0x3, + /** + * @CHANNEL_SWITCH_TIME_EVENT_CMD: &struct iwl_chan_switch_te_cmd + */ + CHANNEL_SWITCH_TIME_EVENT_CMD = 0x4, + /** + * @MISSED_VAP_NOTIF: &struct iwl_missed_vap_notif + */ + MISSED_VAP_NOTIF = 0xFA, + /** + * @SESSION_PROTECTION_CMD: &struct iwl_mvm_session_prot_cmd + */ + SESSION_PROTECTION_CMD = 0x5, + + /** + * @SESSION_PROTECTION_NOTIF: &struct iwl_mvm_session_prot_notif + */ + SESSION_PROTECTION_NOTIF = 0xFB, + + /** + * @PROBE_RESPONSE_DATA_NOTIF: &struct iwl_probe_resp_data_notif + */ + PROBE_RESPONSE_DATA_NOTIF = 0xFC, + + /** + * @CHANNEL_SWITCH_START_NOTIF: &struct iwl_channel_switch_start_notif + */ + CHANNEL_SWITCH_START_NOTIF = 0xFF, +}; + +#define IWL_P2P_NOA_DESC_COUNT (2) + +/** + * struct iwl_p2p_noa_attr - NOA attr contained in probe resp FW notification + * + * @id: attribute id + * @len_low: length low half + * @len_high: length high half + * @idx: instance of NoA timing + * @ctwin: GO's ct window and pwer save capability + * @desc: NoA descriptor + * @reserved: reserved for alignment purposes + */ +struct iwl_p2p_noa_attr { + u8 id; + u8 len_low; + u8 len_high; + u8 idx; + u8 ctwin; + struct ieee80211_p2p_noa_desc desc[IWL_P2P_NOA_DESC_COUNT]; + u8 reserved; +} __packed; + +#define IWL_PROBE_RESP_DATA_NO_CSA (0xff) + +/** + * struct iwl_probe_resp_data_notif - notification with NOA and CSA counter + * + * @mac_id: the mac which should send the probe response + * @noa_active: notifies if the noa attribute should be handled + * @noa_attr: P2P NOA attribute + * @csa_counter: current csa counter + * @reserved: reserved for alignment purposes + */ +struct iwl_probe_resp_data_notif { + __le32 mac_id; + __le32 noa_active; + struct iwl_p2p_noa_attr noa_attr; + u8 csa_counter; + u8 reserved[3]; +} __packed; /* PROBE_RESPONSE_DATA_NTFY_API_S_VER_1 */ + +/** + * struct iwl_missed_vap_notif - notification of missing vap detection + * + * @mac_id: the mac for which the ucode sends the notification for + * @num_beacon_intervals_elapsed: beacons elpased with no vap profile inside + * @profile_periodicity: beacons period to have our profile inside + * @reserved: reserved for alignment purposes + */ +struct iwl_missed_vap_notif { + __le32 mac_id; + u8 num_beacon_intervals_elapsed; + u8 profile_periodicity; + u8 reserved[2]; +} __packed; /* MISSED_VAP_NTFY_API_S_VER_1 */ + +/** + * struct iwl_channel_switch_start_notif - Channel switch start notification + * + * @id_and_color: ID and color of the MAC + */ +struct iwl_channel_switch_start_notif { + __le32 id_and_color; +} __packed; /* CHANNEL_SWITCH_START_NTFY_API_S_VER_1 */ + +/** + * struct iwl_chan_switch_te_cmd - Channel Switch Time Event command + * + * @mac_id: MAC ID for channel switch + * @action: action to perform, one of FW_CTXT_ACTION_* + * @tsf: beacon tsf + * @cs_count: channel switch count from CSA/eCSA IE + * @cs_delayed_bcn_count: if set to N (!= 0) GO/AP can delay N beacon intervals + * at the new channel after the channel switch, otherwise (N == 0) expect + * beacon right after the channel switch. + * @cs_mode: 1 - quiet, 0 - otherwise + * @reserved: reserved for alignment purposes + */ +struct iwl_chan_switch_te_cmd { + __le32 mac_id; + __le32 action; + __le32 tsf; + u8 cs_count; + u8 cs_delayed_bcn_count; + u8 cs_mode; + u8 reserved; +} __packed; /* MAC_CHANNEL_SWITCH_TIME_EVENT_S_VER_2 */ + +/** + * struct iwl_mac_low_latency_cmd - set/clear mac to 'low-latency mode' + * + * @mac_id: MAC ID to whom to apply the low-latency configurations + * @low_latency_rx: 1/0 to set/clear Rx low latency direction + * @low_latency_tx: 1/0 to set/clear Tx low latency direction + * @reserved: reserved for alignment purposes + */ +struct iwl_mac_low_latency_cmd { + __le32 mac_id; + u8 low_latency_rx; + u8 low_latency_tx; + __le16 reserved; +} __packed; /* MAC_LOW_LATENCY_API_S_VER_1 */ + +#endif /* __iwl_fw_api_mac_cfg_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/mac.h b/sys/contrib/dev/iwlwifi/fw/api/mac.h new file mode 100644 index 00000000000..11f0bd283e4 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/mac.h @@ -0,0 +1,659 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_mac_h__ +#define __iwl_fw_api_mac_h__ + +/* + * The first MAC indices (starting from 0) are available to the driver, + * AUX indices follows - 1 for non-CDB, 2 for CDB. + */ +#define MAC_INDEX_AUX 4 +#define MAC_INDEX_MIN_DRIVER 0 +#define NUM_MAC_INDEX_DRIVER MAC_INDEX_AUX +#define NUM_MAC_INDEX (NUM_MAC_INDEX_DRIVER + 1) +#define NUM_MAC_INDEX_CDB (NUM_MAC_INDEX_DRIVER + 2) + +#define IWL_MVM_STATION_COUNT_MAX 16 +#define IWL_MVM_INVALID_STA 0xFF + +enum iwl_ac { + AC_BK, + AC_BE, + AC_VI, + AC_VO, + AC_NUM, +}; + +/** + * enum iwl_mac_protection_flags - MAC context flags + * @MAC_PROT_FLG_TGG_PROTECT: 11g protection when transmitting OFDM frames, + * this will require CCK RTS/CTS2self. + * RTS/CTS will protect full burst time. + * @MAC_PROT_FLG_HT_PROT: enable HT protection + * @MAC_PROT_FLG_FAT_PROT: protect 40 MHz transmissions + * @MAC_PROT_FLG_SELF_CTS_EN: allow CTS2self + */ +enum iwl_mac_protection_flags { + MAC_PROT_FLG_TGG_PROTECT = BIT(3), + MAC_PROT_FLG_HT_PROT = BIT(23), + MAC_PROT_FLG_FAT_PROT = BIT(24), + MAC_PROT_FLG_SELF_CTS_EN = BIT(30), +}; + +#define MAC_FLG_SHORT_SLOT BIT(4) +#define MAC_FLG_SHORT_PREAMBLE BIT(5) + +/** + * enum iwl_mac_types - Supported MAC types + * @FW_MAC_TYPE_FIRST: lowest supported MAC type + * @FW_MAC_TYPE_AUX: Auxiliary MAC (internal) + * @FW_MAC_TYPE_LISTENER: monitor MAC type (?) + * @FW_MAC_TYPE_PIBSS: Pseudo-IBSS + * @FW_MAC_TYPE_IBSS: IBSS + * @FW_MAC_TYPE_BSS_STA: BSS (managed) station + * @FW_MAC_TYPE_P2P_DEVICE: P2P Device + * @FW_MAC_TYPE_P2P_STA: P2P client + * @FW_MAC_TYPE_GO: P2P GO + * @FW_MAC_TYPE_TEST: ? + * @FW_MAC_TYPE_MAX: highest support MAC type + */ +enum iwl_mac_types { + FW_MAC_TYPE_FIRST = 1, + FW_MAC_TYPE_AUX = FW_MAC_TYPE_FIRST, + FW_MAC_TYPE_LISTENER, + FW_MAC_TYPE_PIBSS, + FW_MAC_TYPE_IBSS, + FW_MAC_TYPE_BSS_STA, + FW_MAC_TYPE_P2P_DEVICE, + FW_MAC_TYPE_P2P_STA, + FW_MAC_TYPE_GO, + FW_MAC_TYPE_TEST, + FW_MAC_TYPE_MAX = FW_MAC_TYPE_TEST +}; /* MAC_CONTEXT_TYPE_API_E_VER_1 */ + +/** + * enum iwl_tsf_id - TSF hw timer ID + * @TSF_ID_A: use TSF A + * @TSF_ID_B: use TSF B + * @TSF_ID_C: use TSF C + * @TSF_ID_D: use TSF D + * @NUM_TSF_IDS: number of TSF timers available + */ +enum iwl_tsf_id { + TSF_ID_A = 0, + TSF_ID_B = 1, + TSF_ID_C = 2, + TSF_ID_D = 3, + NUM_TSF_IDS = 4, +}; /* TSF_ID_API_E_VER_1 */ + +/** + * struct iwl_mac_data_ap - configuration data for AP MAC context + * @beacon_time: beacon transmit time in system time + * @beacon_tsf: beacon transmit time in TSF + * @bi: beacon interval in TU + * @reserved1: reserved + * @dtim_interval: dtim transmit time in TU + * @reserved2: reserved + * @mcast_qid: queue ID for multicast traffic. + * NOTE: obsolete from VER2 and on + * @beacon_template: beacon template ID + */ +struct iwl_mac_data_ap { + __le32 beacon_time; + __le64 beacon_tsf; + __le32 bi; + __le32 reserved1; + __le32 dtim_interval; + __le32 reserved2; + __le32 mcast_qid; + __le32 beacon_template; +} __packed; /* AP_MAC_DATA_API_S_VER_2 */ + +/** + * struct iwl_mac_data_ibss - configuration data for IBSS MAC context + * @beacon_time: beacon transmit time in system time + * @beacon_tsf: beacon transmit time in TSF + * @bi: beacon interval in TU + * @reserved: reserved + * @beacon_template: beacon template ID + */ +struct iwl_mac_data_ibss { + __le32 beacon_time; + __le64 beacon_tsf; + __le32 bi; + __le32 reserved; + __le32 beacon_template; +} __packed; /* IBSS_MAC_DATA_API_S_VER_1 */ + +/** + * enum iwl_mac_data_policy - policy of the data path for this MAC + * @TWT_SUPPORTED: twt is supported + * @MORE_DATA_ACK_SUPPORTED: AP supports More Data Ack according to + * paragraph 9.4.1.17 in P802.11ax_D4 specification. Used for TWT + * early termination detection. + * @FLEXIBLE_TWT_SUPPORTED: AP supports flexible TWT schedule + * @PROTECTED_TWT_SUPPORTED: AP supports protected TWT frames (with 11w) + * @BROADCAST_TWT_SUPPORTED: AP and STA support broadcast TWT + * @COEX_HIGH_PRIORITY_ENABLE: high priority mode for BT coex, to be used + * during 802.1X negotiation (and allowed during 4-way-HS) + */ +enum iwl_mac_data_policy { + TWT_SUPPORTED = BIT(0), + MORE_DATA_ACK_SUPPORTED = BIT(1), + FLEXIBLE_TWT_SUPPORTED = BIT(2), + PROTECTED_TWT_SUPPORTED = BIT(3), + BROADCAST_TWT_SUPPORTED = BIT(4), + COEX_HIGH_PRIORITY_ENABLE = BIT(5), +}; + +/** + * struct iwl_mac_data_sta - configuration data for station MAC context + * @is_assoc: 1 for associated state, 0 otherwise + * @dtim_time: DTIM arrival time in system time + * @dtim_tsf: DTIM arrival time in TSF + * @bi: beacon interval in TU, applicable only when associated + * @reserved1: reserved + * @dtim_interval: DTIM interval in TU, applicable only when associated + * @data_policy: see &enum iwl_mac_data_policy + * @listen_interval: in beacon intervals, applicable only when associated + * @assoc_id: unique ID assigned by the AP during association + * @assoc_beacon_arrive_time: TSF of first beacon after association + */ +struct iwl_mac_data_sta { + __le32 is_assoc; + __le32 dtim_time; + __le64 dtim_tsf; + __le32 bi; + __le32 reserved1; + __le32 dtim_interval; + __le32 data_policy; + __le32 listen_interval; + __le32 assoc_id; + __le32 assoc_beacon_arrive_time; +} __packed; /* STA_MAC_DATA_API_S_VER_2 */ + +/** + * struct iwl_mac_data_go - configuration data for P2P GO MAC context + * @ap: iwl_mac_data_ap struct with most config data + * @ctwin: client traffic window in TU (period after TBTT when GO is present). + * 0 indicates that there is no CT window. + * @opp_ps_enabled: indicate that opportunistic PS allowed + */ +struct iwl_mac_data_go { + struct iwl_mac_data_ap ap; + __le32 ctwin; + __le32 opp_ps_enabled; +} __packed; /* GO_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_p2p_sta - configuration data for P2P client MAC context + * @sta: iwl_mac_data_sta struct with most config data + * @ctwin: client traffic window in TU (period after TBTT when GO is present). + * 0 indicates that there is no CT window. + */ +struct iwl_mac_data_p2p_sta { + struct iwl_mac_data_sta sta; + __le32 ctwin; +} __packed; /* P2P_STA_MAC_DATA_API_S_VER_2 */ + +/** + * struct iwl_mac_data_pibss - Pseudo IBSS config data + * @stats_interval: interval in TU between statistics notifications to host. + */ +struct iwl_mac_data_pibss { + __le32 stats_interval; +} __packed; /* PIBSS_MAC_DATA_API_S_VER_1 */ + +/* + * struct iwl_mac_data_p2p_dev - configuration data for the P2P Device MAC + * context. + * @is_disc_extended: if set to true, P2P Device discoverability is enabled on + * other channels as well. This should be to true only in case that the + * device is discoverable and there is an active GO. Note that setting this + * field when not needed, will increase the number of interrupts and have + * effect on the platform power, as this setting opens the Rx filters on + * all macs. + */ +struct iwl_mac_data_p2p_dev { + __le32 is_disc_extended; +} __packed; /* _P2P_DEV_MAC_DATA_API_S_VER_1 */ + +/** + * enum iwl_mac_filter_flags - MAC context filter flags + * @MAC_FILTER_IN_PROMISC: accept all data frames + * @MAC_FILTER_IN_CONTROL_AND_MGMT: pass all management and + * control frames to the host + * @MAC_FILTER_ACCEPT_GRP: accept multicast frames + * @MAC_FILTER_DIS_DECRYPT: don't decrypt unicast frames + * @MAC_FILTER_DIS_GRP_DECRYPT: don't decrypt multicast frames + * @MAC_FILTER_IN_BEACON: transfer foreign BSS's beacons to host + * (in station mode when associated) + * @MAC_FILTER_OUT_BCAST: filter out all broadcast frames + * @MAC_FILTER_IN_CRC32: extract FCS and append it to frames + * @MAC_FILTER_IN_PROBE_REQUEST: pass probe requests to host + */ +enum iwl_mac_filter_flags { + MAC_FILTER_IN_PROMISC = BIT(0), + MAC_FILTER_IN_CONTROL_AND_MGMT = BIT(1), + MAC_FILTER_ACCEPT_GRP = BIT(2), + MAC_FILTER_DIS_DECRYPT = BIT(3), + MAC_FILTER_DIS_GRP_DECRYPT = BIT(4), + MAC_FILTER_IN_BEACON = BIT(6), + MAC_FILTER_OUT_BCAST = BIT(8), + MAC_FILTER_IN_CRC32 = BIT(11), + MAC_FILTER_IN_PROBE_REQUEST = BIT(12), + /** + * @MAC_FILTER_IN_11AX: mark BSS as supporting 802.11ax + */ + MAC_FILTER_IN_11AX = BIT(14), +}; + +/** + * enum iwl_mac_qos_flags - QoS flags + * @MAC_QOS_FLG_UPDATE_EDCA: ? + * @MAC_QOS_FLG_TGN: HT is enabled + * @MAC_QOS_FLG_TXOP_TYPE: ? + * + */ +enum iwl_mac_qos_flags { + MAC_QOS_FLG_UPDATE_EDCA = BIT(0), + MAC_QOS_FLG_TGN = BIT(1), + MAC_QOS_FLG_TXOP_TYPE = BIT(4), +}; + +/** + * struct iwl_ac_qos - QOS timing params for MAC_CONTEXT_CMD + * @cw_min: Contention window, start value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x0f. + * @cw_max: Contention window, max value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x3f. + * @aifsn: Number of slots in Arbitration Interframe Space (before + * performing random backoff timing prior to Tx). Device default 1. + * @fifos_mask: FIFOs used by this MAC for this AC + * @edca_txop: Length of Tx opportunity, in uSecs. Device default is 0. + * + * One instance of this config struct for each of 4 EDCA access categories + * in struct iwl_qosparam_cmd. + * + * Device will automatically increase contention window by (2*CW) + 1 for each + * transmission retry. Device uses cw_max as a bit mask, ANDed with new CW + * value, to cap the CW value. + */ +struct iwl_ac_qos { + __le16 cw_min; + __le16 cw_max; + u8 aifsn; + u8 fifos_mask; + __le16 edca_txop; +} __packed; /* AC_QOS_API_S_VER_2 */ + +/** + * struct iwl_mac_ctx_cmd - command structure to configure MAC contexts + * ( MAC_CONTEXT_CMD = 0x28 ) + * @id_and_color: ID and color of the MAC + * @action: action to perform, one of FW_CTXT_ACTION_* + * @mac_type: one of &enum iwl_mac_types + * @tsf_id: TSF HW timer, one of &enum iwl_tsf_id + * @node_addr: MAC address + * @reserved_for_node_addr: reserved + * @bssid_addr: BSSID + * @reserved_for_bssid_addr: reserved + * @cck_rates: basic rates available for CCK + * @ofdm_rates: basic rates available for OFDM + * @protection_flags: combination of &enum iwl_mac_protection_flags + * @cck_short_preamble: 0x20 for enabling short preamble, 0 otherwise + * @short_slot: 0x10 for enabling short slots, 0 otherwise + * @filter_flags: combination of &enum iwl_mac_filter_flags + * @qos_flags: from &enum iwl_mac_qos_flags + * @ac: one iwl_mac_qos configuration for each AC + */ +struct iwl_mac_ctx_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* MAC_CONTEXT_COMMON_DATA_API_S_VER_1 */ + __le32 mac_type; + __le32 tsf_id; + u8 node_addr[6]; + __le16 reserved_for_node_addr; + u8 bssid_addr[6]; + __le16 reserved_for_bssid_addr; + __le32 cck_rates; + __le32 ofdm_rates; + __le32 protection_flags; + __le32 cck_short_preamble; + __le32 short_slot; + __le32 filter_flags; + /* MAC_QOS_PARAM_API_S_VER_1 */ + __le32 qos_flags; + struct iwl_ac_qos ac[AC_NUM+1]; + /* MAC_CONTEXT_COMMON_DATA_API_S */ + union { + struct iwl_mac_data_ap ap; + struct iwl_mac_data_go go; + struct iwl_mac_data_sta sta; + struct iwl_mac_data_p2p_sta p2p_sta; + struct iwl_mac_data_p2p_dev p2p_dev; + struct iwl_mac_data_pibss pibss; + struct iwl_mac_data_ibss ibss; + }; +} __packed; /* MAC_CONTEXT_CMD_API_S_VER_1 */ + +#define IWL_NONQOS_SEQ_GET 0x1 +#define IWL_NONQOS_SEQ_SET 0x2 +struct iwl_nonqos_seq_query_cmd { + __le32 get_set_flag; + __le32 mac_id_n_color; + __le16 value; + __le16 reserved; +} __packed; /* NON_QOS_TX_COUNTER_GET_SET_API_S_VER_1 */ + +/** + * struct iwl_missed_beacons_notif - information on missed beacons + * ( MISSED_BEACONS_NOTIFICATION = 0xa2 ) + * @mac_id: interface ID + * @consec_missed_beacons_since_last_rx: number of consecutive missed + * beacons since last RX. + * @consec_missed_beacons: number of consecutive missed beacons + * @num_expected_beacons: number of expected beacons + * @num_recvd_beacons: number of received beacons + */ +struct iwl_missed_beacons_notif { + __le32 mac_id; + __le32 consec_missed_beacons_since_last_rx; + __le32 consec_missed_beacons; + __le32 num_expected_beacons; + __le32 num_recvd_beacons; +} __packed; /* MISSED_BEACON_NTFY_API_S_VER_3 */ + +/** + * struct iwl_he_backoff_conf - used for backoff configuration + * Per each trigger-based AC, (set by MU EDCA Parameter set info-element) + * used for backoff configuration of TXF5..TXF8 trigger based. + * The MU-TIMER is reloaded w/ MU_TIME each time a frame from the AC is sent via + * trigger-based TX. + * @cwmin: CW min + * @cwmax: CW max + * @aifsn: AIFSN + * AIFSN=0, means that no backoff from the specified TRIG-BASED AC is + * allowed till the MU-TIMER is 0 + * @mu_time: MU time in 8TU units + */ +struct iwl_he_backoff_conf { + __le16 cwmin; + __le16 cwmax; + __le16 aifsn; + __le16 mu_time; +} __packed; /* AC_QOS_DOT11AX_API_S */ + +/** + * enum iwl_he_pkt_ext_constellations - PPE constellation indices + * @IWL_HE_PKT_EXT_BPSK: BPSK + * @IWL_HE_PKT_EXT_QPSK: QPSK + * @IWL_HE_PKT_EXT_16QAM: 16-QAM + * @IWL_HE_PKT_EXT_64QAM: 64-QAM + * @IWL_HE_PKT_EXT_256QAM: 256-QAM + * @IWL_HE_PKT_EXT_1024QAM: 1024-QAM + * @IWL_HE_PKT_EXT_RESERVED: reserved value + * @IWL_HE_PKT_EXT_NONE: not defined + */ +enum iwl_he_pkt_ext_constellations { + IWL_HE_PKT_EXT_BPSK = 0, + IWL_HE_PKT_EXT_QPSK, + IWL_HE_PKT_EXT_16QAM, + IWL_HE_PKT_EXT_64QAM, + IWL_HE_PKT_EXT_256QAM, + IWL_HE_PKT_EXT_1024QAM, + IWL_HE_PKT_EXT_RESERVED, + IWL_HE_PKT_EXT_NONE, +}; + +#define MAX_HE_SUPP_NSS 2 +#define MAX_HE_CHANNEL_BW_INDX 4 + +/** + * struct iwl_he_pkt_ext - QAM thresholds + * The required PPE is set via HE Capabilities IE, per Nss x BW x MCS + * The IE is organized in the following way: + * Support for Nss x BW (or RU) matrix: + * (0=SISO, 1=MIMO2) x (0-20MHz, 1-40MHz, 2-80MHz, 3-160MHz) + * Each entry contains 2 QAM thresholds for 8us and 16us: + * 0=BPSK, 1=QPSK, 2=16QAM, 3=64QAM, 4=256QAM, 5=1024QAM, 6=RES, 7=NONE + * i.e. QAM_th1 < QAM_th2 such if TX uses QAM_tx: + * QAM_tx < QAM_th1 --> PPE=0us + * QAM_th1 <= QAM_tx < QAM_th2 --> PPE=8us + * QAM_th2 <= QAM_tx --> PPE=16us + * @pkt_ext_qam_th: QAM thresholds + * For each Nss/Bw define 2 QAM thrsholds (0..5) + * For rates below the low_th, no need for PPE + * For rates between low_th and high_th, need 8us PPE + * For rates equal or higher then the high_th, need 16us PPE + * Nss (0-siso, 1-mimo2) x BW (0-20MHz, 1-40MHz, 2-80MHz, 3-160MHz) x + * (0-low_th, 1-high_th) + */ +struct iwl_he_pkt_ext { + u8 pkt_ext_qam_th[MAX_HE_SUPP_NSS][MAX_HE_CHANNEL_BW_INDX][2]; +} __packed; /* PKT_EXT_DOT11AX_API_S */ + +/** + * enum iwl_he_sta_ctxt_flags - HE STA context flags + * @STA_CTXT_HE_REF_BSSID_VALID: ref bssid addr valid (for receiving specific + * control frames such as TRIG, NDPA, BACK) + * @STA_CTXT_HE_BSS_COLOR_DIS: BSS color disable, don't use the BSS + * color for RX filter but use MAC header + * @STA_CTXT_HE_PARTIAL_BSS_COLOR: partial BSS color allocation + * @STA_CTXT_HE_32BIT_BA_BITMAP: indicates the receiver supports BA bitmap + * of 32-bits + * @STA_CTXT_HE_PACKET_EXT: indicates that the packet-extension info is valid + * and should be used + * @STA_CTXT_HE_TRIG_RND_ALLOC: indicates that trigger based random allocation + * is enabled according to UORA element existence + * @STA_CTXT_HE_CONST_TRIG_RND_ALLOC: used for AV testing + * @STA_CTXT_HE_ACK_ENABLED: indicates that the AP supports receiving ACK- + * enabled AGG, i.e. both BACK and non-BACK frames in a single AGG + * @STA_CTXT_HE_MU_EDCA_CW: indicates that there is an element of MU EDCA + * parameter set, i.e. the backoff counters for trig-based ACs + * @STA_CTXT_HE_NIC_NOT_ACK_ENABLED: mark that the NIC doesn't support receiving + * ACK-enabled AGG, (i.e. both BACK and non-BACK frames in single AGG). + * If the NIC is not ACK_ENABLED it may use the EOF-bit in first non-0 + * len delim to determine if AGG or single. + * @STA_CTXT_HE_RU_2MHZ_BLOCK: indicates that 26-tone RU OFDMA transmission are + * not allowed (as there are OBSS that might classify such transmissions as + * radar pulses). + */ +enum iwl_he_sta_ctxt_flags { + STA_CTXT_HE_REF_BSSID_VALID = BIT(4), + STA_CTXT_HE_BSS_COLOR_DIS = BIT(5), + STA_CTXT_HE_PARTIAL_BSS_COLOR = BIT(6), + STA_CTXT_HE_32BIT_BA_BITMAP = BIT(7), + STA_CTXT_HE_PACKET_EXT = BIT(8), + STA_CTXT_HE_TRIG_RND_ALLOC = BIT(9), + STA_CTXT_HE_CONST_TRIG_RND_ALLOC = BIT(10), + STA_CTXT_HE_ACK_ENABLED = BIT(11), + STA_CTXT_HE_MU_EDCA_CW = BIT(12), + STA_CTXT_HE_NIC_NOT_ACK_ENABLED = BIT(13), + STA_CTXT_HE_RU_2MHZ_BLOCK = BIT(14), +}; + +/** + * enum iwl_he_htc_flags - HE HTC support flags + * @IWL_HE_HTC_SUPPORT: HE-HTC support + * @IWL_HE_HTC_UL_MU_RESP_SCHED: HE UL MU response schedule + * support via A-control field + * @IWL_HE_HTC_BSR_SUPP: BSR support in A-control field + * @IWL_HE_HTC_OMI_SUPP: A-OMI support in A-control field + * @IWL_HE_HTC_BQR_SUPP: A-BQR support in A-control field + */ +enum iwl_he_htc_flags { + IWL_HE_HTC_SUPPORT = BIT(0), + IWL_HE_HTC_UL_MU_RESP_SCHED = BIT(3), + IWL_HE_HTC_BSR_SUPP = BIT(4), + IWL_HE_HTC_OMI_SUPP = BIT(5), + IWL_HE_HTC_BQR_SUPP = BIT(6), +}; + +/* + * @IWL_HE_HTC_LINK_ADAP_NO_FEEDBACK: the STA does not provide HE MFB + * @IWL_HE_HTC_LINK_ADAP_UNSOLICITED: the STA provides only unsolicited HE MFB + * @IWL_HE_HTC_LINK_ADAP_BOTH: the STA is capable of providing HE MFB in + * response to HE MRQ and if the STA provides unsolicited HE MFB + */ +#define IWL_HE_HTC_LINK_ADAP_POS (1) +#define IWL_HE_HTC_LINK_ADAP_NO_FEEDBACK (0) +#define IWL_HE_HTC_LINK_ADAP_UNSOLICITED (2 << IWL_HE_HTC_LINK_ADAP_POS) +#define IWL_HE_HTC_LINK_ADAP_BOTH (3 << IWL_HE_HTC_LINK_ADAP_POS) + +/** + * struct iwl_he_sta_context_cmd_v1 - configure FW to work with HE AP + * @sta_id: STA id + * @tid_limit: max num of TIDs in TX HE-SU multi-TID agg + * 0 - bad value, 1 - multi-tid not supported, 2..8 - tid limit + * @reserved1: reserved byte for future use + * @reserved2: reserved byte for future use + * @flags: see %iwl_11ax_sta_ctxt_flags + * @ref_bssid_addr: reference BSSID used by the AP + * @reserved0: reserved 2 bytes for aligning the ref_bssid_addr field to 8 bytes + * @htc_flags: which features are supported in HTC + * @frag_flags: frag support in A-MSDU + * @frag_level: frag support level + * @frag_max_num: max num of "open" MSDUs in the receiver (in power of 2) + * @frag_min_size: min frag size (except last frag) + * @pkt_ext: optional, exists according to PPE-present bit in the HE-PHY capa + * @bss_color: 11ax AP ID that is used in the HE SIG-A to mark inter BSS frame + * @htc_trig_based_pkt_ext: default PE in 4us units + * @frame_time_rts_th: HE duration RTS threshold, in units of 32us + * @rand_alloc_ecwmin: random CWmin = 2**ECWmin-1 + * @rand_alloc_ecwmax: random CWmax = 2**ECWmax-1 + * @reserved3: reserved byte for future use + * @trig_based_txf: MU EDCA Parameter set for the trigger based traffic queues + */ +struct iwl_he_sta_context_cmd_v1 { + u8 sta_id; + u8 tid_limit; + u8 reserved1; + u8 reserved2; + __le32 flags; + + /* The below fields are set via Multiple BSSID IE */ + u8 ref_bssid_addr[6]; + __le16 reserved0; + + /* The below fields are set via HE-capabilities IE */ + __le32 htc_flags; + + u8 frag_flags; + u8 frag_level; + u8 frag_max_num; + u8 frag_min_size; + + /* The below fields are set via PPE thresholds element */ + struct iwl_he_pkt_ext pkt_ext; + + /* The below fields are set via HE-Operation IE */ + u8 bss_color; + u8 htc_trig_based_pkt_ext; + __le16 frame_time_rts_th; + + /* Random access parameter set (i.e. RAPS) */ + u8 rand_alloc_ecwmin; + u8 rand_alloc_ecwmax; + __le16 reserved3; + + /* The below fields are set via MU EDCA parameter set element */ + struct iwl_he_backoff_conf trig_based_txf[AC_NUM]; +} __packed; /* STA_CONTEXT_DOT11AX_API_S_VER_1 */ + +/** + * struct iwl_he_sta_context_cmd - configure FW to work with HE AP + * @sta_id: STA id + * @tid_limit: max num of TIDs in TX HE-SU multi-TID agg + * 0 - bad value, 1 - multi-tid not supported, 2..8 - tid limit + * @reserved1: reserved byte for future use + * @reserved2: reserved byte for future use + * @flags: see %iwl_11ax_sta_ctxt_flags + * @ref_bssid_addr: reference BSSID used by the AP + * @reserved0: reserved 2 bytes for aligning the ref_bssid_addr field to 8 bytes + * @htc_flags: which features are supported in HTC + * @frag_flags: frag support in A-MSDU + * @frag_level: frag support level + * @frag_max_num: max num of "open" MSDUs in the receiver (in power of 2) + * @frag_min_size: min frag size (except last frag) + * @pkt_ext: optional, exists according to PPE-present bit in the HE-PHY capa + * @bss_color: 11ax AP ID that is used in the HE SIG-A to mark inter BSS frame + * @htc_trig_based_pkt_ext: default PE in 4us units + * @frame_time_rts_th: HE duration RTS threshold, in units of 32us + * @rand_alloc_ecwmin: random CWmin = 2**ECWmin-1 + * @rand_alloc_ecwmax: random CWmax = 2**ECWmax-1 + * @reserved3: reserved byte for future use + * @trig_based_txf: MU EDCA Parameter set for the trigger based traffic queues + * @max_bssid_indicator: indicator of the max bssid supported on the associated + * bss + * @bssid_index: index of the associated VAP + * @ema_ap: AP supports enhanced Multi BSSID advertisement + * @profile_periodicity: number of Beacon periods that are needed to receive the + * complete VAPs info + * @bssid_count: actual number of VAPs in the MultiBSS Set + * @reserved4: alignment + */ +struct iwl_he_sta_context_cmd { + u8 sta_id; + u8 tid_limit; + u8 reserved1; + u8 reserved2; + __le32 flags; + + /* The below fields are set via Multiple BSSID IE */ + u8 ref_bssid_addr[6]; + __le16 reserved0; + + /* The below fields are set via HE-capabilities IE */ + __le32 htc_flags; + + u8 frag_flags; + u8 frag_level; + u8 frag_max_num; + u8 frag_min_size; + + /* The below fields are set via PPE thresholds element */ + struct iwl_he_pkt_ext pkt_ext; + + /* The below fields are set via HE-Operation IE */ + u8 bss_color; + u8 htc_trig_based_pkt_ext; + __le16 frame_time_rts_th; + + /* Random access parameter set (i.e. RAPS) */ + u8 rand_alloc_ecwmin; + u8 rand_alloc_ecwmax; + __le16 reserved3; + + /* The below fields are set via MU EDCA parameter set element */ + struct iwl_he_backoff_conf trig_based_txf[AC_NUM]; + + u8 max_bssid_indicator; + u8 bssid_index; + u8 ema_ap; + u8 profile_periodicity; + u8 bssid_count; + u8 reserved4[3]; +} __packed; /* STA_CONTEXT_DOT11AX_API_S_VER_2 */ + +/** + * struct iwl_he_monitor_cmd - configure air sniffer for HE + * @bssid: the BSSID to sniff for + * @reserved1: reserved for dword alignment + * @aid: the AID to track on for HE MU + * @reserved2: reserved for future use + */ +struct iwl_he_monitor_cmd { + u8 bssid[6]; + __le16 reserved1; + __le16 aid; + u8 reserved2[6]; +} __packed; /* HE_AIR_SNIFFER_CONFIG_CMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_mac_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/nvm-reg.h b/sys/contrib/dev/iwlwifi/fw/api/nvm-reg.h new file mode 100644 index 00000000000..3551a3f1c1a --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/nvm-reg.h @@ -0,0 +1,505 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_nvm_reg_h__ +#define __iwl_fw_api_nvm_reg_h__ + +/** + * enum iwl_regulatory_and_nvm_subcmd_ids - regulatory/NVM commands + */ +enum iwl_regulatory_and_nvm_subcmd_ids { + /** + * @NVM_ACCESS_COMPLETE: &struct iwl_nvm_access_complete_cmd + */ + NVM_ACCESS_COMPLETE = 0x0, + + /** + * @LARI_CONFIG_CHANGE: &struct iwl_lari_config_change_cmd + */ + LARI_CONFIG_CHANGE = 0x1, + + /** + * @NVM_GET_INFO: + * Command is &struct iwl_nvm_get_info, + * response is &struct iwl_nvm_get_info_rsp + */ + NVM_GET_INFO = 0x2, + + /** + * @TAS_CONFIG: &struct iwl_tas_config_cmd + */ + TAS_CONFIG = 0x3, + + /** + * @PNVM_INIT_COMPLETE_NTFY: &struct iwl_pnvm_init_complete_ntfy + */ + PNVM_INIT_COMPLETE_NTFY = 0xFE, +}; + +/** + * enum iwl_nvm_access_op - NVM access opcode + * @IWL_NVM_READ: read NVM + * @IWL_NVM_WRITE: write NVM + */ +enum iwl_nvm_access_op { + IWL_NVM_READ = 0, + IWL_NVM_WRITE = 1, +}; + +/** + * enum iwl_nvm_access_target - target of the NVM_ACCESS_CMD + * @NVM_ACCESS_TARGET_CACHE: access the cache + * @NVM_ACCESS_TARGET_OTP: access the OTP + * @NVM_ACCESS_TARGET_EEPROM: access the EEPROM + */ +enum iwl_nvm_access_target { + NVM_ACCESS_TARGET_CACHE = 0, + NVM_ACCESS_TARGET_OTP = 1, + NVM_ACCESS_TARGET_EEPROM = 2, +}; + +/** + * enum iwl_nvm_section_type - section types for NVM_ACCESS_CMD + * @NVM_SECTION_TYPE_SW: software section + * @NVM_SECTION_TYPE_REGULATORY: regulatory section + * @NVM_SECTION_TYPE_CALIBRATION: calibration section + * @NVM_SECTION_TYPE_PRODUCTION: production section + * @NVM_SECTION_TYPE_REGULATORY_SDP: regulatory section used by 3168 series + * @NVM_SECTION_TYPE_MAC_OVERRIDE: MAC override section + * @NVM_SECTION_TYPE_PHY_SKU: PHY SKU section + * @NVM_MAX_NUM_SECTIONS: number of sections + */ +enum iwl_nvm_section_type { + NVM_SECTION_TYPE_SW = 1, + NVM_SECTION_TYPE_REGULATORY = 3, + NVM_SECTION_TYPE_CALIBRATION = 4, + NVM_SECTION_TYPE_PRODUCTION = 5, + NVM_SECTION_TYPE_REGULATORY_SDP = 8, + NVM_SECTION_TYPE_MAC_OVERRIDE = 11, + NVM_SECTION_TYPE_PHY_SKU = 12, + NVM_MAX_NUM_SECTIONS = 13, +}; + +/** + * struct iwl_nvm_access_cmd - Request the device to send an NVM section + * @op_code: &enum iwl_nvm_access_op + * @target: &enum iwl_nvm_access_target + * @type: &enum iwl_nvm_section_type + * @offset: offset in bytes into the section + * @length: in bytes, to read/write + * @data: if write operation, the data to write. On read its empty + */ +struct iwl_nvm_access_cmd { + u8 op_code; + u8 target; + __le16 type; + __le16 offset; + __le16 length; + u8 data[]; +} __packed; /* NVM_ACCESS_CMD_API_S_VER_2 */ + +/** + * struct iwl_nvm_access_resp_ver2 - response to NVM_ACCESS_CMD + * @offset: offset in bytes into the section + * @length: in bytes, either how much was written or read + * @type: NVM_SECTION_TYPE_* + * @status: 0 for success, fail otherwise + * @data: if read operation, the data returned. Empty on write. + */ +struct iwl_nvm_access_resp { + __le16 offset; + __le16 length; + __le16 type; + __le16 status; + u8 data[]; +} __packed; /* NVM_ACCESS_CMD_RESP_API_S_VER_2 */ + +/* + * struct iwl_nvm_get_info - request to get NVM data + */ +struct iwl_nvm_get_info { + __le32 reserved; +} __packed; /* REGULATORY_NVM_GET_INFO_CMD_API_S_VER_1 */ + +/** + * enum iwl_nvm_info_general_flags - flags in NVM_GET_INFO resp + * @NVM_GENERAL_FLAGS_EMPTY_OTP: 1 if OTP is empty + */ +enum iwl_nvm_info_general_flags { + NVM_GENERAL_FLAGS_EMPTY_OTP = BIT(0), +}; + +/** + * struct iwl_nvm_get_info_general - general NVM data + * @flags: bit 0: 1 - empty, 0 - non-empty + * @nvm_version: nvm version + * @board_type: board type + * @n_hw_addrs: number of reserved MAC addresses + */ +struct iwl_nvm_get_info_general { + __le32 flags; + __le16 nvm_version; + u8 board_type; + u8 n_hw_addrs; +} __packed; /* REGULATORY_NVM_GET_INFO_GENERAL_S_VER_2 */ + +/** + * enum iwl_nvm_mac_sku_flags - flags in &iwl_nvm_get_info_sku + * @NVM_MAC_SKU_FLAGS_BAND_2_4_ENABLED: true if 2.4 band enabled + * @NVM_MAC_SKU_FLAGS_BAND_5_2_ENABLED: true if 5.2 band enabled + * @NVM_MAC_SKU_FLAGS_802_11N_ENABLED: true if 11n enabled + * @NVM_MAC_SKU_FLAGS_802_11AC_ENABLED: true if 11ac enabled + * @NVM_MAC_SKU_FLAGS_MIMO_DISABLED: true if MIMO disabled + * @NVM_MAC_SKU_FLAGS_WAPI_ENABLED: true if WAPI enabled + * @NVM_MAC_SKU_FLAGS_REG_CHECK_ENABLED: true if regulatory checker enabled + * @NVM_MAC_SKU_FLAGS_API_LOCK_ENABLED: true if API lock enabled + */ +enum iwl_nvm_mac_sku_flags { + NVM_MAC_SKU_FLAGS_BAND_2_4_ENABLED = BIT(0), + NVM_MAC_SKU_FLAGS_BAND_5_2_ENABLED = BIT(1), + NVM_MAC_SKU_FLAGS_802_11N_ENABLED = BIT(2), + NVM_MAC_SKU_FLAGS_802_11AC_ENABLED = BIT(3), + /** + * @NVM_MAC_SKU_FLAGS_802_11AX_ENABLED: true if 11ax enabled + */ + NVM_MAC_SKU_FLAGS_802_11AX_ENABLED = BIT(4), + NVM_MAC_SKU_FLAGS_MIMO_DISABLED = BIT(5), + NVM_MAC_SKU_FLAGS_WAPI_ENABLED = BIT(8), + NVM_MAC_SKU_FLAGS_REG_CHECK_ENABLED = BIT(14), + NVM_MAC_SKU_FLAGS_API_LOCK_ENABLED = BIT(15), +}; + +/** + * struct iwl_nvm_get_info_sku - mac information + * @mac_sku_flags: flags for SKU, see &enum iwl_nvm_mac_sku_flags + */ +struct iwl_nvm_get_info_sku { + __le32 mac_sku_flags; +} __packed; /* REGULATORY_NVM_GET_INFO_MAC_SKU_SECTION_S_VER_2 */ + +/** + * struct iwl_nvm_get_info_phy - phy information + * @tx_chains: BIT 0 chain A, BIT 1 chain B + * @rx_chains: BIT 0 chain A, BIT 1 chain B + */ +struct iwl_nvm_get_info_phy { + __le32 tx_chains; + __le32 rx_chains; +} __packed; /* REGULATORY_NVM_GET_INFO_PHY_SKU_SECTION_S_VER_1 */ + +#define IWL_NUM_CHANNELS_V1 51 +#define IWL_NUM_CHANNELS 110 + +/** + * struct iwl_nvm_get_info_regulatory - regulatory information + * @lar_enabled: is LAR enabled + * @channel_profile: regulatory data of this channel + * @reserved: reserved + */ +struct iwl_nvm_get_info_regulatory_v1 { + __le32 lar_enabled; + __le16 channel_profile[IWL_NUM_CHANNELS_V1]; + __le16 reserved; +} __packed; /* REGULATORY_NVM_GET_INFO_REGULATORY_S_VER_1 */ + +/** + * struct iwl_nvm_get_info_regulatory - regulatory information + * @lar_enabled: is LAR enabled + * @n_channels: number of valid channels in the array + * @channel_profile: regulatory data of this channel + */ +struct iwl_nvm_get_info_regulatory { + __le32 lar_enabled; + __le32 n_channels; + __le32 channel_profile[IWL_NUM_CHANNELS]; +} __packed; /* REGULATORY_NVM_GET_INFO_REGULATORY_S_VER_2 */ + +/** + * struct iwl_nvm_get_info_rsp_v3 - response to get NVM data + * @general: general NVM data + * @mac_sku: data relating to MAC sku + * @phy_sku: data relating to PHY sku + * @regulatory: regulatory data + */ +struct iwl_nvm_get_info_rsp_v3 { + struct iwl_nvm_get_info_general general; + struct iwl_nvm_get_info_sku mac_sku; + struct iwl_nvm_get_info_phy phy_sku; + struct iwl_nvm_get_info_regulatory_v1 regulatory; +} __packed; /* REGULATORY_NVM_GET_INFO_RSP_API_S_VER_3 */ + +/** + * struct iwl_nvm_get_info_rsp - response to get NVM data + * @general: general NVM data + * @mac_sku: data relating to MAC sku + * @phy_sku: data relating to PHY sku + * @regulatory: regulatory data + */ +struct iwl_nvm_get_info_rsp { + struct iwl_nvm_get_info_general general; + struct iwl_nvm_get_info_sku mac_sku; + struct iwl_nvm_get_info_phy phy_sku; + struct iwl_nvm_get_info_regulatory regulatory; +} __packed; /* REGULATORY_NVM_GET_INFO_RSP_API_S_VER_4 */ + +/** + * struct iwl_nvm_access_complete_cmd - NVM_ACCESS commands are completed + * @reserved: reserved + */ +struct iwl_nvm_access_complete_cmd { + __le32 reserved; +} __packed; /* NVM_ACCESS_COMPLETE_CMD_API_S_VER_1 */ + +/** + * struct iwl_mcc_update_cmd - Request the device to update geographic + * regulatory profile according to the given MCC (Mobile Country Code). + * The MCC is two letter-code, ascii upper case[A-Z] or '00' for world domain. + * 'ZZ' MCC will be used to switch to NVM default profile; in this case, the + * MCC in the cmd response will be the relevant MCC in the NVM. + * @mcc: given mobile country code + * @source_id: the source from where we got the MCC, see iwl_mcc_source + * @reserved: reserved for alignment + * @key: integrity key for MCC API OEM testing + * @reserved2: reserved + */ +struct iwl_mcc_update_cmd { + __le16 mcc; + u8 source_id; + u8 reserved; + __le32 key; + u8 reserved2[20]; +} __packed; /* LAR_UPDATE_MCC_CMD_API_S_VER_2 */ + +/** + * enum iwl_geo_information - geographic information. + * @GEO_NO_INFO: no special info for this geo profile. + * @GEO_WMM_ETSI_5GHZ_INFO: this geo profile limits the WMM params + * for the 5 GHz band. + */ +enum iwl_geo_information { + GEO_NO_INFO = 0, + GEO_WMM_ETSI_5GHZ_INFO = BIT(0), +}; + +/** + * struct iwl_mcc_update_resp_v3 - response to MCC_UPDATE_CMD. + * Contains the new channel control profile map, if changed, and the new MCC + * (mobile country code). + * The new MCC may be different than what was requested in MCC_UPDATE_CMD. + * @status: see &enum iwl_mcc_update_status + * @mcc: the new applied MCC + * @cap: capabilities for all channels which matches the MCC + * @source_id: the MCC source, see iwl_mcc_source + * @time: time elapsed from the MCC test start (in units of 30 seconds) + * @geo_info: geographic specific profile information + * see &enum iwl_geo_information. + * @n_channels: number of channels in @channels_data. + * @channels: channel control data map, DWORD for each channel. Only the first + * 16bits are used. + */ +struct iwl_mcc_update_resp_v3 { + __le32 status; + __le16 mcc; + u8 cap; + u8 source_id; + __le16 time; + __le16 geo_info; + __le32 n_channels; + __le32 channels[]; +} __packed; /* LAR_UPDATE_MCC_CMD_RESP_S_VER_3 */ + +/** + * struct iwl_mcc_update_resp - response to MCC_UPDATE_CMD. + * Contains the new channel control profile map, if changed, and the new MCC + * (mobile country code). + * The new MCC may be different than what was requested in MCC_UPDATE_CMD. + * @status: see &enum iwl_mcc_update_status + * @mcc: the new applied MCC + * @cap: capabilities for all channels which matches the MCC + * @time: time elapsed from the MCC test start (in units of 30 seconds) + * @geo_info: geographic specific profile information + * see &enum iwl_geo_information. + * @source_id: the MCC source, see iwl_mcc_source + * @reserved: for four bytes alignment. + * @n_channels: number of channels in @channels_data. + * @channels: channel control data map, DWORD for each channel. Only the first + * 16bits are used. + */ +struct iwl_mcc_update_resp { + __le32 status; + __le16 mcc; + __le16 cap; + __le16 time; + __le16 geo_info; + u8 source_id; + u8 reserved[3]; + __le32 n_channels; + __le32 channels[]; +} __packed; /* LAR_UPDATE_MCC_CMD_RESP_S_VER_4 */ + +/** + * struct iwl_mcc_chub_notif - chub notifies of mcc change + * (MCC_CHUB_UPDATE_CMD = 0xc9) + * The Chub (Communication Hub, CommsHUB) is a HW component that connects to + * the cellular and connectivity cores that gets updates of the mcc, and + * notifies the ucode directly of any mcc change. + * The ucode requests the driver to request the device to update geographic + * regulatory profile according to the given MCC (Mobile Country Code). + * The MCC is two letter-code, ascii upper case[A-Z] or '00' for world domain. + * 'ZZ' MCC will be used to switch to NVM default profile; in this case, the + * MCC in the cmd response will be the relevant MCC in the NVM. + * @mcc: given mobile country code + * @source_id: identity of the change originator, see iwl_mcc_source + * @reserved1: reserved for alignment + */ +struct iwl_mcc_chub_notif { + __le16 mcc; + u8 source_id; + u8 reserved1; +} __packed; /* LAR_MCC_NOTIFY_S */ + +enum iwl_mcc_update_status { + MCC_RESP_NEW_CHAN_PROFILE, + MCC_RESP_SAME_CHAN_PROFILE, + MCC_RESP_INVALID, + MCC_RESP_NVM_DISABLED, + MCC_RESP_ILLEGAL, + MCC_RESP_LOW_PRIORITY, + MCC_RESP_TEST_MODE_ACTIVE, + MCC_RESP_TEST_MODE_NOT_ACTIVE, + MCC_RESP_TEST_MODE_DENIAL_OF_SERVICE, +}; + +enum iwl_mcc_source { + MCC_SOURCE_OLD_FW = 0, + MCC_SOURCE_ME = 1, + MCC_SOURCE_BIOS = 2, + MCC_SOURCE_3G_LTE_HOST = 3, + MCC_SOURCE_3G_LTE_DEVICE = 4, + MCC_SOURCE_WIFI = 5, + MCC_SOURCE_RESERVED = 6, + MCC_SOURCE_DEFAULT = 7, + MCC_SOURCE_UNINITIALIZED = 8, + MCC_SOURCE_MCC_API = 9, + MCC_SOURCE_GET_CURRENT = 0x10, + MCC_SOURCE_GETTING_MCC_TEST_MODE = 0x11, +}; + +#define IWL_TAS_BLACK_LIST_MAX 16 +/** + * struct iwl_tas_config_cmd - configures the TAS + * @block_list_size: size of relevant field in block_list_array + * @block_list_array: block list countries (without TAS) + */ +struct iwl_tas_config_cmd { + __le32 block_list_size; + __le32 block_list_array[IWL_TAS_BLACK_LIST_MAX]; +} __packed; /* TAS_CONFIG_CMD_API_S_VER_2 */ + +/** + * enum iwl_lari_configs - bit masks for the various LARI config operations + * @LARI_CONFIG_DISABLE_11AC_UKRAINE_MSK: disable 11ac in ukraine + * @LARI_CONFIG_CHANGE_ETSI_TO_PASSIVE_MSK: ETSI 5.8GHz SRD passive scan + * @LARI_CONFIG_CHANGE_ETSI_TO_DISABLED_MSK: ETSI 5.8GHz SRD disabled + * @LARI_CONFIG_ENABLE_5G2_IN_INDONESIA_MSK: enable 5.15/5.35GHz bands in + * Indonesia + */ +enum iwl_lari_config_masks { + LARI_CONFIG_DISABLE_11AC_UKRAINE_MSK = BIT(0), + LARI_CONFIG_CHANGE_ETSI_TO_PASSIVE_MSK = BIT(1), + LARI_CONFIG_CHANGE_ETSI_TO_DISABLED_MSK = BIT(2), + LARI_CONFIG_ENABLE_5G2_IN_INDONESIA_MSK = BIT(3), +}; + +#define IWL_11AX_UKRAINE_MASK 3 +#define IWL_11AX_UKRAINE_SHIFT 8 + +/** + * struct iwl_lari_config_change_cmd_v1 - change LARI configuration + * @config_bitmap: bit map of the config commands. each bit will trigger a + * different predefined FW config operation + */ +struct iwl_lari_config_change_cmd_v1 { + __le32 config_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_1 */ + +/** + * struct iwl_lari_config_change_cmd_v2 - change LARI configuration + * @config_bitmap: bit map of the config commands. each bit will trigger a + * different predefined FW config operation + * @oem_uhb_allow_bitmap: bitmap of UHB enabled MCC sets + */ +struct iwl_lari_config_change_cmd_v2 { + __le32 config_bitmap; + __le32 oem_uhb_allow_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_2 */ + +/** + * struct iwl_lari_config_change_cmd_v3 - change LARI configuration + * @config_bitmap: bit map of the config commands. each bit will trigger a + * different predefined FW config operation + * @oem_uhb_allow_bitmap: bitmap of UHB enabled MCC sets + * @oem_11ax_allow_bitmap: bitmap of 11ax allowed MCCs. + * For each supported country, a pair of regulatory override bit and 11ax mode exist + * in the bit field. + */ +struct iwl_lari_config_change_cmd_v3 { + __le32 config_bitmap; + __le32 oem_uhb_allow_bitmap; + __le32 oem_11ax_allow_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_3 */ + +/** + * struct iwl_lari_config_change_cmd_v4 - change LARI configuration + * @config_bitmap: Bitmap of the config commands. Each bit will trigger a + * different predefined FW config operation. + * @oem_uhb_allow_bitmap: Bitmap of UHB enabled MCC sets. + * @oem_11ax_allow_bitmap: Bitmap of 11ax allowed MCCs. There are two bits + * per country, one to indicate whether to override and the other to + * indicate the value to use. + * @oem_unii4_allow_bitmap: Bitmap of unii4 allowed MCCs.There are two bits + * per country, one to indicate whether to override and the other to + * indicate allow/disallow unii4 channels. + */ +struct iwl_lari_config_change_cmd_v4 { + __le32 config_bitmap; + __le32 oem_uhb_allow_bitmap; + __le32 oem_11ax_allow_bitmap; + __le32 oem_unii4_allow_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_4 */ + +/** + * struct iwl_lari_config_change_cmd_v5 - change LARI configuration + * @config_bitmap: Bitmap of the config commands. Each bit will trigger a + * different predefined FW config operation. + * @oem_uhb_allow_bitmap: Bitmap of UHB enabled MCC sets. + * @oem_11ax_allow_bitmap: Bitmap of 11ax allowed MCCs. There are two bits + * per country, one to indicate whether to override and the other to + * indicate the value to use. + * @oem_unii4_allow_bitmap: Bitmap of unii4 allowed MCCs.There are two bits + * per country, one to indicate whether to override and the other to + * indicate allow/disallow unii4 channels. + * @chan_state_active_bitmap: Bitmap for overriding channel state to active. + * Each bit represents a country or region to activate, according to the BIOS + * definitions. + */ +struct iwl_lari_config_change_cmd_v5 { + __le32 config_bitmap; + __le32 oem_uhb_allow_bitmap; + __le32 oem_11ax_allow_bitmap; + __le32 oem_unii4_allow_bitmap; + __le32 chan_state_active_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_5 */ + +/** + * struct iwl_pnvm_init_complete_ntfy - PNVM initialization complete + * @status: PNVM image loading status + */ +struct iwl_pnvm_init_complete_ntfy { + __le32 status; +} __packed; /* PNVM_INIT_COMPLETE_NTFY_S_VER_1 */ + +#endif /* __iwl_fw_api_nvm_reg_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/offload.h b/sys/contrib/dev/iwlwifi/fw/api/offload.h new file mode 100644 index 00000000000..5204aa94e72 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/offload.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + * Copyright (C) 2021 Intel Corporation + */ +#ifndef __iwl_fw_api_offload_h__ +#define __iwl_fw_api_offload_h__ + +/** + * enum iwl_prot_offload_subcmd_ids - protocol offload commands + */ +enum iwl_prot_offload_subcmd_ids { + /** + * @STORED_BEACON_NTF: &struct iwl_stored_beacon_notif + */ + STORED_BEACON_NTF = 0xFF, +}; + +#define MAX_STORED_BEACON_SIZE 600 + +/** + * struct iwl_stored_beacon_notif_common - Stored beacon notif common fields + * + * @system_time: system time on air rise + * @tsf: TSF on air rise + * @beacon_timestamp: beacon on air rise + * @band: band, matches &RX_RES_PHY_FLAGS_BAND_24 definition + * @channel: channel this beacon was received on + * @rates: rate in ucode internal format + * @byte_count: frame's byte count + */ +struct iwl_stored_beacon_notif_common { + __le32 system_time; + __le64 tsf; + __le32 beacon_timestamp; + __le16 band; + __le16 channel; + __le32 rates; + __le32 byte_count; +} __packed; + +/** + * struct iwl_stored_beacon_notif - Stored beacon notification + * + * @common: fields common for all versions + * @data: beacon data, length in @byte_count + */ +struct iwl_stored_beacon_notif_v2 { + struct iwl_stored_beacon_notif_common common; + u8 data[MAX_STORED_BEACON_SIZE]; +} __packed; /* WOWLAN_STROED_BEACON_INFO_S_VER_2 */ + +/** + * struct iwl_stored_beacon_notif_v3 - Stored beacon notification + * + * @common: fields common for all versions + * @sta_id: station for which the beacon was received + * @reserved: reserved for alignment + * @data: beacon data, length in @byte_count + */ +struct iwl_stored_beacon_notif_v3 { + struct iwl_stored_beacon_notif_common common; + u8 sta_id; + u8 reserved[3]; + u8 data[MAX_STORED_BEACON_SIZE]; +} __packed; /* WOWLAN_STROED_BEACON_INFO_S_VER_3 */ + +#endif /* __iwl_fw_api_offload_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/paging.h b/sys/contrib/dev/iwlwifi/fw/api/paging.h new file mode 100644 index 00000000000..db59a1897fb --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/paging.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_paging_h__ +#define __iwl_fw_api_paging_h__ + +#define NUM_OF_FW_PAGING_BLOCKS 33 /* 32 for data and 1 block for CSS */ + +/** + * struct iwl_fw_paging_cmd - paging layout + * + * Send to FW the paging layout in the driver. + * + * @flags: various flags for the command + * @block_size: the block size in powers of 2 + * @block_num: number of blocks specified in the command. + * @device_phy_addr: virtual addresses from device side + */ +struct iwl_fw_paging_cmd { + __le32 flags; + __le32 block_size; + __le32 block_num; + __le32 device_phy_addr[NUM_OF_FW_PAGING_BLOCKS]; +} __packed; /* FW_PAGING_BLOCK_CMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_paging_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/phy-ctxt.h b/sys/contrib/dev/iwlwifi/fw/api/phy-ctxt.h new file mode 100644 index 00000000000..68b788b92b7 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/phy-ctxt.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018, 2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_phy_ctxt_h__ +#define __iwl_fw_api_phy_ctxt_h__ + +/* Supported bands */ +#define PHY_BAND_5 (0) +#define PHY_BAND_24 (1) +#define PHY_BAND_6 (2) + +/* Supported channel width, vary if there is VHT support */ +#define PHY_VHT_CHANNEL_MODE20 (0x0) +#define PHY_VHT_CHANNEL_MODE40 (0x1) +#define PHY_VHT_CHANNEL_MODE80 (0x2) +#define PHY_VHT_CHANNEL_MODE160 (0x3) + +/* + * Control channel position: + * For legacy set bit means upper channel, otherwise lower. + * For VHT - bit-2 marks if the control is lower/upper relative to center-freq + * bits-1:0 mark the distance from the center freq. for 20Mhz, offset is 0. + * center_freq + * | + * 40Mhz |_______|_______| + * 80Mhz |_______|_______|_______|_______| + * 160Mhz |_______|_______|_______|_______|_______|_______|_______|_______| + * code 011 010 001 000 | 100 101 110 111 + */ +#define PHY_VHT_CTRL_POS_1_BELOW (0x0) +#define PHY_VHT_CTRL_POS_2_BELOW (0x1) +#define PHY_VHT_CTRL_POS_3_BELOW (0x2) +#define PHY_VHT_CTRL_POS_4_BELOW (0x3) +#define PHY_VHT_CTRL_POS_1_ABOVE (0x4) +#define PHY_VHT_CTRL_POS_2_ABOVE (0x5) +#define PHY_VHT_CTRL_POS_3_ABOVE (0x6) +#define PHY_VHT_CTRL_POS_4_ABOVE (0x7) + +/* + * struct iwl_fw_channel_info_v1 - channel information + * + * @band: PHY_BAND_* + * @channel: channel number + * @width: PHY_[VHT|LEGACY]_CHANNEL_* + * @ctrl channel: PHY_[VHT|LEGACY]_CTRL_* + */ +struct iwl_fw_channel_info_v1 { + u8 band; + u8 channel; + u8 width; + u8 ctrl_pos; +} __packed; /* CHANNEL_CONFIG_API_S_VER_1 */ + +/* + * struct iwl_fw_channel_info - channel information + * + * @channel: channel number + * @band: PHY_BAND_* + * @width: PHY_[VHT|LEGACY]_CHANNEL_* + * @ctrl channel: PHY_[VHT|LEGACY]_CTRL_* + * @reserved: for future use and alignment + */ +struct iwl_fw_channel_info { + __le32 channel; + u8 band; + u8 width; + u8 ctrl_pos; + u8 reserved; +} __packed; /*CHANNEL_CONFIG_API_S_VER_2 */ + +#define PHY_RX_CHAIN_DRIVER_FORCE_POS (0) +#define PHY_RX_CHAIN_DRIVER_FORCE_MSK \ + (0x1 << PHY_RX_CHAIN_DRIVER_FORCE_POS) +#define PHY_RX_CHAIN_VALID_POS (1) +#define PHY_RX_CHAIN_VALID_MSK \ + (0x7 << PHY_RX_CHAIN_VALID_POS) +#define PHY_RX_CHAIN_FORCE_SEL_POS (4) +#define PHY_RX_CHAIN_FORCE_SEL_MSK \ + (0x7 << PHY_RX_CHAIN_FORCE_SEL_POS) +#define PHY_RX_CHAIN_FORCE_MIMO_SEL_POS (7) +#define PHY_RX_CHAIN_FORCE_MIMO_SEL_MSK \ + (0x7 << PHY_RX_CHAIN_FORCE_MIMO_SEL_POS) +#define PHY_RX_CHAIN_CNT_POS (10) +#define PHY_RX_CHAIN_CNT_MSK \ + (0x3 << PHY_RX_CHAIN_CNT_POS) +#define PHY_RX_CHAIN_MIMO_CNT_POS (12) +#define PHY_RX_CHAIN_MIMO_CNT_MSK \ + (0x3 << PHY_RX_CHAIN_MIMO_CNT_POS) +#define PHY_RX_CHAIN_MIMO_FORCE_POS (14) +#define PHY_RX_CHAIN_MIMO_FORCE_MSK \ + (0x1 << PHY_RX_CHAIN_MIMO_FORCE_POS) + +/* TODO: fix the value, make it depend on firmware at runtime? */ +#define NUM_PHY_CTX 3 + +/* TODO: complete missing documentation */ +/** + * struct iwl_phy_context_cmd_tail - tail of iwl_phy_ctx_cmd for alignment with + * various channel structures. + * + * @txchain_info: ??? + * @rxchain_info: ??? + * @acquisition_data: ??? + * @dsp_cfg_flags: set to 0 + */ +struct iwl_phy_context_cmd_tail { + __le32 txchain_info; + __le32 rxchain_info; + __le32 acquisition_data; + __le32 dsp_cfg_flags; +} __packed; + +/** + * struct iwl_phy_context_cmd - config of the PHY context + * ( PHY_CONTEXT_CMD = 0x8 ) + * @id_and_color: ID and color of the relevant Binding + * @action: action to perform, one of FW_CTXT_ACTION_* + * @apply_time: 0 means immediate apply and context switch. + * other value means apply new params after X usecs + * @tx_param_color: ??? + * @ci: channel info + * @tail: command tail + */ +struct iwl_phy_context_cmd_v1 { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* PHY_CONTEXT_DATA_API_S_VER_3 */ + __le32 apply_time; + __le32 tx_param_color; + struct iwl_fw_channel_info ci; + struct iwl_phy_context_cmd_tail tail; +} __packed; /* PHY_CONTEXT_CMD_API_VER_1 */ + +/** + * struct iwl_phy_context_cmd - config of the PHY context + * ( PHY_CONTEXT_CMD = 0x8 ) + * @id_and_color: ID and color of the relevant Binding + * @action: action to perform, one of FW_CTXT_ACTION_* + * @lmac_id: the lmac id the phy context belongs to + * @ci: channel info + * @rxchain_info: ??? + * @dsp_cfg_flags: set to 0 + * @reserved: reserved to align to 64 bit + */ +struct iwl_phy_context_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* PHY_CONTEXT_DATA_API_S_VER_3 */ + struct iwl_fw_channel_info ci; + __le32 lmac_id; + __le32 rxchain_info; + __le32 dsp_cfg_flags; + __le32 reserved; +} __packed; /* PHY_CONTEXT_CMD_API_VER_3 */ +#endif /* __iwl_fw_api_phy_ctxt_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/phy.h b/sys/contrib/dev/iwlwifi/fw/api/phy.h new file mode 100644 index 00000000000..c04f2521fcb --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/phy.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2019-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_phy_h__ +#define __iwl_fw_api_phy_h__ + +/** + * enum iwl_phy_ops_subcmd_ids - PHY group commands + */ +enum iwl_phy_ops_subcmd_ids { + /** + * @CMD_DTS_MEASUREMENT_TRIGGER_WIDE: + * Uses either &struct iwl_dts_measurement_cmd or + * &struct iwl_ext_dts_measurement_cmd + */ + CMD_DTS_MEASUREMENT_TRIGGER_WIDE = 0x0, + + /** + * @CTDP_CONFIG_CMD: &struct iwl_mvm_ctdp_cmd + */ + CTDP_CONFIG_CMD = 0x03, + + /** + * @TEMP_REPORTING_THRESHOLDS_CMD: &struct temp_report_ths_cmd + */ + TEMP_REPORTING_THRESHOLDS_CMD = 0x04, + + /** + * @PER_CHAIN_LIMIT_OFFSET_CMD: &struct iwl_geo_tx_power_profiles_cmd + */ + PER_CHAIN_LIMIT_OFFSET_CMD = 0x05, + + /** + * @PER_PLATFORM_ANT_GAIN_CMD: &struct iwl_ppag_table_cmd + */ + PER_PLATFORM_ANT_GAIN_CMD = 0x07, + + /** + * @CT_KILL_NOTIFICATION: &struct ct_kill_notif + */ + CT_KILL_NOTIFICATION = 0xFE, + + /** + * @DTS_MEASUREMENT_NOTIF_WIDE: + * &struct iwl_dts_measurement_notif_v1 or + * &struct iwl_dts_measurement_notif_v2 + */ + DTS_MEASUREMENT_NOTIF_WIDE = 0xFF, +}; + +/* DTS measurements */ + +enum iwl_dts_measurement_flags { + DTS_TRIGGER_CMD_FLAGS_TEMP = BIT(0), + DTS_TRIGGER_CMD_FLAGS_VOLT = BIT(1), +}; + +/** + * struct iwl_dts_measurement_cmd - request DTS temp and/or voltage measurements + * + * @flags: indicates which measurements we want as specified in + * &enum iwl_dts_measurement_flags + */ +struct iwl_dts_measurement_cmd { + __le32 flags; +} __packed; /* TEMPERATURE_MEASUREMENT_TRIGGER_CMD_S */ + +/** +* enum iwl_dts_control_measurement_mode - DTS measurement type +* @DTS_AUTOMATIC: Automatic mode (full SW control). Provide temperature read +* back (latest value. Not waiting for new value). Use automatic +* SW DTS configuration. +* @DTS_REQUEST_READ: Request DTS read. Configure DTS with manual settings, +* trigger DTS reading and provide read back temperature read +* when available. +* @DTS_OVER_WRITE: over-write the DTS temperatures in the SW until next read +* @DTS_DIRECT_WITHOUT_MEASURE: DTS returns its latest temperature result, +* without measurement trigger. +*/ +enum iwl_dts_control_measurement_mode { + DTS_AUTOMATIC = 0, + DTS_REQUEST_READ = 1, + DTS_OVER_WRITE = 2, + DTS_DIRECT_WITHOUT_MEASURE = 3, +}; + +/** +* enum iwl_dts_used - DTS to use or used for measurement in the DTS request +* @DTS_USE_TOP: Top +* @DTS_USE_CHAIN_A: chain A +* @DTS_USE_CHAIN_B: chain B +* @DTS_USE_CHAIN_C: chain C +* @XTAL_TEMPERATURE: read temperature from xtal +*/ +enum iwl_dts_used { + DTS_USE_TOP = 0, + DTS_USE_CHAIN_A = 1, + DTS_USE_CHAIN_B = 2, + DTS_USE_CHAIN_C = 3, + XTAL_TEMPERATURE = 4, +}; + +/** +* enum iwl_dts_bit_mode - bit-mode to use in DTS request read mode +* @DTS_BIT6_MODE: bit 6 mode +* @DTS_BIT8_MODE: bit 8 mode +*/ +enum iwl_dts_bit_mode { + DTS_BIT6_MODE = 0, + DTS_BIT8_MODE = 1, +}; + +/** + * struct iwl_ext_dts_measurement_cmd - request extended DTS temp measurements + * @control_mode: see &enum iwl_dts_control_measurement_mode + * @temperature: used when over write DTS mode is selected + * @sensor: set temperature sensor to use. See &enum iwl_dts_used + * @avg_factor: average factor to DTS in request DTS read mode + * @bit_mode: value defines the DTS bit mode to use. See &enum iwl_dts_bit_mode + * @step_duration: step duration for the DTS + */ +struct iwl_ext_dts_measurement_cmd { + __le32 control_mode; + __le32 temperature; + __le32 sensor; + __le32 avg_factor; + __le32 bit_mode; + __le32 step_duration; +} __packed; /* XVT_FW_DTS_CONTROL_MEASUREMENT_REQUEST_API_S */ + +/** + * struct iwl_dts_measurement_notif_v1 - measurements notification + * + * @temp: the measured temperature + * @voltage: the measured voltage + */ +struct iwl_dts_measurement_notif_v1 { + __le32 temp; + __le32 voltage; +} __packed; /* TEMPERATURE_MEASUREMENT_TRIGGER_NTFY_S_VER_1*/ + +/** + * struct iwl_dts_measurement_notif_v2 - measurements notification + * + * @temp: the measured temperature + * @voltage: the measured voltage + * @threshold_idx: the trip index that was crossed + */ +struct iwl_dts_measurement_notif_v2 { + __le32 temp; + __le32 voltage; + __le32 threshold_idx; +} __packed; /* TEMPERATURE_MEASUREMENT_TRIGGER_NTFY_S_VER_2 */ + +/** + * struct iwl_dts_measurement_resp - measurements response + * + * @temp: the measured temperature + */ +struct iwl_dts_measurement_resp { + __le32 temp; +} __packed; /* CMD_DTS_MEASUREMENT_RSP_API_S_VER_1 */ + +/** + * struct ct_kill_notif - CT-kill entry notification + * + * @temperature: the current temperature in celsius + * @reserved: reserved + */ +struct ct_kill_notif { + __le16 temperature; + __le16 reserved; +} __packed; /* GRP_PHY_CT_KILL_NTF */ + +/** +* enum ctdp_cmd_operation - CTDP command operations +* @CTDP_CMD_OPERATION_START: update the current budget +* @CTDP_CMD_OPERATION_STOP: stop ctdp +* @CTDP_CMD_OPERATION_REPORT: get the average budget +*/ +enum iwl_mvm_ctdp_cmd_operation { + CTDP_CMD_OPERATION_START = 0x1, + CTDP_CMD_OPERATION_STOP = 0x2, + CTDP_CMD_OPERATION_REPORT = 0x4, +};/* CTDP_CMD_OPERATION_TYPE_E */ + +/** + * struct iwl_mvm_ctdp_cmd - track and manage the FW power consumption budget + * + * @operation: see &enum iwl_mvm_ctdp_cmd_operation + * @budget: the budget in milliwatt + * @window_size: defined in API but not used + */ +struct iwl_mvm_ctdp_cmd { + __le32 operation; + __le32 budget; + __le32 window_size; +} __packed; + +#define IWL_MAX_DTS_TRIPS 8 + +/** + * struct temp_report_ths_cmd - set temperature thresholds + * + * @num_temps: number of temperature thresholds passed + * @thresholds: array with the thresholds to be configured + */ +struct temp_report_ths_cmd { + __le32 num_temps; + __le16 thresholds[IWL_MAX_DTS_TRIPS]; +} __packed; /* GRP_PHY_TEMP_REPORTING_THRESHOLDS_CMD */ + +#endif /* __iwl_fw_api_phy_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/power.h b/sys/contrib/dev/iwlwifi/fw/api/power.h new file mode 100644 index 00000000000..4d671c878bb --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/power.h @@ -0,0 +1,633 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_power_h__ +#define __iwl_fw_api_power_h__ + +/* Power Management Commands, Responses, Notifications */ + +/** + * enum iwl_ltr_config_flags - masks for LTR config command flags + * @LTR_CFG_FLAG_FEATURE_ENABLE: Feature operational status + * @LTR_CFG_FLAG_HW_DIS_ON_SHADOW_REG_ACCESS: allow LTR change on shadow + * memory access + * @LTR_CFG_FLAG_HW_EN_SHRT_WR_THROUGH: allow LTR msg send on ANY LTR + * reg change + * @LTR_CFG_FLAG_HW_DIS_ON_D0_2_D3: allow LTR msg send on transition from + * D0 to D3 + * @LTR_CFG_FLAG_SW_SET_SHORT: fixed static short LTR register + * @LTR_CFG_FLAG_SW_SET_LONG: fixed static short LONG register + * @LTR_CFG_FLAG_DENIE_C10_ON_PD: allow going into C10 on PD + * @LTR_CFG_FLAG_UPDATE_VALUES: update config values and short + * idle timeout + */ +enum iwl_ltr_config_flags { + LTR_CFG_FLAG_FEATURE_ENABLE = BIT(0), + LTR_CFG_FLAG_HW_DIS_ON_SHADOW_REG_ACCESS = BIT(1), + LTR_CFG_FLAG_HW_EN_SHRT_WR_THROUGH = BIT(2), + LTR_CFG_FLAG_HW_DIS_ON_D0_2_D3 = BIT(3), + LTR_CFG_FLAG_SW_SET_SHORT = BIT(4), + LTR_CFG_FLAG_SW_SET_LONG = BIT(5), + LTR_CFG_FLAG_DENIE_C10_ON_PD = BIT(6), + LTR_CFG_FLAG_UPDATE_VALUES = BIT(7), +}; + +/** + * struct iwl_ltr_config_cmd_v1 - configures the LTR + * @flags: See &enum iwl_ltr_config_flags + * @static_long: static LTR Long register value. + * @static_short: static LTR Short register value. + */ +struct iwl_ltr_config_cmd_v1 { + __le32 flags; + __le32 static_long; + __le32 static_short; +} __packed; /* LTR_CAPABLE_API_S_VER_1 */ + +#define LTR_VALID_STATES_NUM 4 + +/** + * struct iwl_ltr_config_cmd - configures the LTR + * @flags: See &enum iwl_ltr_config_flags + * @static_long: static LTR Long register value. + * @static_short: static LTR Short register value. + * @ltr_cfg_values: LTR parameters table values (in usec) in folowing order: + * TX, RX, Short Idle, Long Idle. Used only if %LTR_CFG_FLAG_UPDATE_VALUES + * is set. + * @ltr_short_idle_timeout: LTR Short Idle timeout (in usec). Used only if + * %LTR_CFG_FLAG_UPDATE_VALUES is set. + */ +struct iwl_ltr_config_cmd { + __le32 flags; + __le32 static_long; + __le32 static_short; + __le32 ltr_cfg_values[LTR_VALID_STATES_NUM]; + __le32 ltr_short_idle_timeout; +} __packed; /* LTR_CAPABLE_API_S_VER_2 */ + +/* Radio LP RX Energy Threshold measured in dBm */ +#define POWER_LPRX_RSSI_THRESHOLD 75 +#define POWER_LPRX_RSSI_THRESHOLD_MAX 94 +#define POWER_LPRX_RSSI_THRESHOLD_MIN 30 + +/** + * enum iwl_power_flags - masks for power table command flags + * @POWER_FLAGS_POWER_SAVE_ENA_MSK: '1' Allow to save power by turning off + * receiver and transmitter. '0' - does not allow. + * @POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK: '0' Driver disables power management, + * '1' Driver enables PM (use rest of parameters) + * @POWER_FLAGS_SKIP_OVER_DTIM_MSK: '0' PM have to walk up every DTIM, + * '1' PM could sleep over DTIM till listen Interval. + * @POWER_FLAGS_SNOOZE_ENA_MSK: Enable snoozing only if uAPSD is enabled and all + * access categories are both delivery and trigger enabled. + * @POWER_FLAGS_BT_SCO_ENA: Enable BT SCO coex only if uAPSD and + * PBW Snoozing enabled + * @POWER_FLAGS_ADVANCE_PM_ENA_MSK: Advanced PM (uAPSD) enable mask + * @POWER_FLAGS_LPRX_ENA_MSK: Low Power RX enable. + * @POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK: AP/GO's uAPSD misbehaving + * detection enablement +*/ +enum iwl_power_flags { + POWER_FLAGS_POWER_SAVE_ENA_MSK = BIT(0), + POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK = BIT(1), + POWER_FLAGS_SKIP_OVER_DTIM_MSK = BIT(2), + POWER_FLAGS_SNOOZE_ENA_MSK = BIT(5), + POWER_FLAGS_BT_SCO_ENA = BIT(8), + POWER_FLAGS_ADVANCE_PM_ENA_MSK = BIT(9), + POWER_FLAGS_LPRX_ENA_MSK = BIT(11), + POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK = BIT(12), +}; + +#define IWL_POWER_VEC_SIZE 5 + +/** + * struct iwl_powertable_cmd - legacy power command. Beside old API support this + * is used also with a new power API for device wide power settings. + * POWER_TABLE_CMD = 0x77 (command, has simple generic response) + * + * @flags: Power table command flags from POWER_FLAGS_* + * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. + * Minimum allowed:- 3 * DTIM. Keep alive period must be + * set regardless of power scheme or current power state. + * FW use this value also when PM is disabled. + * @debug_flags: debug flags + * @rx_data_timeout: Minimum time (usec) from last Rx packet for AM to + * PSM transition - legacy PM + * @tx_data_timeout: Minimum time (usec) from last Tx packet for AM to + * PSM transition - legacy PM + * @sleep_interval: not in use + * @skip_dtim_periods: Number of DTIM periods to skip if Skip over DTIM flag + * is set. For example, if it is required to skip over + * one DTIM, this value need to be set to 2 (DTIM periods). + * @lprx_rssi_threshold: Signal strength up to which LP RX can be enabled. + * Default: 80dbm + */ +struct iwl_powertable_cmd { + /* PM_POWER_TABLE_CMD_API_S_VER_6 */ + __le16 flags; + u8 keep_alive_seconds; + u8 debug_flags; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + __le32 sleep_interval[IWL_POWER_VEC_SIZE]; + __le32 skip_dtim_periods; + __le32 lprx_rssi_threshold; +} __packed; + +/** + * enum iwl_device_power_flags - masks for device power command flags + * @DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK: + * '1' Allow to save power by turning off + * receiver and transmitter. '0' - does not allow. + * @DEVICE_POWER_FLAGS_ALLOW_MEM_RETENTION_MSK: + * Device Retention indication, '1' indicate retention is enabled. + * @DEVICE_POWER_FLAGS_32K_CLK_VALID_MSK: + * 32Khz external slow clock valid indication, '1' indicate cloack is + * valid. +*/ +enum iwl_device_power_flags { + DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK = BIT(0), + DEVICE_POWER_FLAGS_ALLOW_MEM_RETENTION_MSK = BIT(1), + DEVICE_POWER_FLAGS_32K_CLK_VALID_MSK = BIT(12), +}; + +/** + * struct iwl_device_power_cmd - device wide power command. + * DEVICE_POWER_CMD = 0x77 (command, has simple generic response) + * + * @flags: Power table command flags from &enum iwl_device_power_flags + * @reserved: reserved (padding) + */ +struct iwl_device_power_cmd { + /* PM_POWER_TABLE_CMD_API_S_VER_6 */ + __le16 flags; + __le16 reserved; +} __packed; + +/** + * struct iwl_mac_power_cmd - New power command containing uAPSD support + * MAC_PM_POWER_TABLE = 0xA9 (command, has simple generic response) + * @id_and_color: MAC contex identifier, &enum iwl_ctxt_id_and_color + * @flags: Power table command flags from POWER_FLAGS_* + * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. + * Minimum allowed:- 3 * DTIM. Keep alive period must be + * set regardless of power scheme or current power state. + * FW use this value also when PM is disabled. + * @rx_data_timeout: Minimum time (usec) from last Rx packet for AM to + * PSM transition - legacy PM + * @tx_data_timeout: Minimum time (usec) from last Tx packet for AM to + * PSM transition - legacy PM + * @skip_dtim_periods: Number of DTIM periods to skip if Skip over DTIM flag + * is set. For example, if it is required to skip over + * one DTIM, this value need to be set to 2 (DTIM periods). + * @rx_data_timeout_uapsd: Minimum time (usec) from last Rx packet for AM to + * PSM transition - uAPSD + * @tx_data_timeout_uapsd: Minimum time (usec) from last Tx packet for AM to + * PSM transition - uAPSD + * @lprx_rssi_threshold: Signal strength up to which LP RX can be enabled. + * Default: 80dbm + * @snooze_interval: Maximum time between attempts to retrieve buffered data + * from the AP [msec] + * @snooze_window: A window of time in which PBW snoozing insures that all + * packets received. It is also the minimum time from last + * received unicast RX packet, before client stops snoozing + * for data. [msec] + * @snooze_step: TBD + * @qndp_tid: TID client shall use for uAPSD QNDP triggers + * @uapsd_ac_flags: Set trigger-enabled and delivery-enabled indication for + * each corresponding AC. + * Use IEEE80211_WMM_IE_STA_QOSINFO_AC* for correct values. + * @uapsd_max_sp: Use IEEE80211_WMM_IE_STA_QOSINFO_SP_* for correct + * values. + * @heavy_tx_thld_packets: TX threshold measured in number of packets + * @heavy_rx_thld_packets: RX threshold measured in number of packets + * @heavy_tx_thld_percentage: TX threshold measured in load's percentage + * @heavy_rx_thld_percentage: RX threshold measured in load's percentage + * @limited_ps_threshold: (unused) + * @reserved: reserved (padding) + */ +struct iwl_mac_power_cmd { + /* CONTEXT_DESC_API_T_VER_1 */ + __le32 id_and_color; + + /* CLIENT_PM_POWER_TABLE_S_VER_1 */ + __le16 flags; + __le16 keep_alive_seconds; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + __le32 rx_data_timeout_uapsd; + __le32 tx_data_timeout_uapsd; + u8 lprx_rssi_threshold; + u8 skip_dtim_periods; + __le16 snooze_interval; + __le16 snooze_window; + u8 snooze_step; + u8 qndp_tid; + u8 uapsd_ac_flags; + u8 uapsd_max_sp; + u8 heavy_tx_thld_packets; + u8 heavy_rx_thld_packets; + u8 heavy_tx_thld_percentage; + u8 heavy_rx_thld_percentage; + u8 limited_ps_threshold; + u8 reserved; +} __packed; + +/* + * struct iwl_uapsd_misbehaving_ap_notif - FW sends this notification when + * associated AP is identified as improperly implementing uAPSD protocol. + * PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION = 0x78 + * @sta_id: index of station in uCode's station table - associated AP ID in + * this context. + */ +struct iwl_uapsd_misbehaving_ap_notif { + __le32 sta_id; + u8 mac_id; + u8 reserved[3]; +} __packed; + +/** + * struct iwl_reduce_tx_power_cmd - TX power reduction command + * REDUCE_TX_POWER_CMD = 0x9f + * @flags: (reserved for future implementation) + * @mac_context_id: id of the mac ctx for which we are reducing TX power. + * @pwr_restriction: TX power restriction in dBms. + */ +struct iwl_reduce_tx_power_cmd { + u8 flags; + u8 mac_context_id; + __le16 pwr_restriction; +} __packed; /* TX_REDUCED_POWER_API_S_VER_1 */ + +enum iwl_dev_tx_power_cmd_mode { + IWL_TX_POWER_MODE_SET_MAC = 0, + IWL_TX_POWER_MODE_SET_DEVICE = 1, + IWL_TX_POWER_MODE_SET_CHAINS = 2, + IWL_TX_POWER_MODE_SET_ACK = 3, + IWL_TX_POWER_MODE_SET_SAR_TIMER = 4, + IWL_TX_POWER_MODE_SET_SAR_TIMER_DEFAULT_TABLE = 5, +}; /* TX_POWER_REDUCED_FLAGS_TYPE_API_E_VER_5 */; + +#define IWL_NUM_CHAIN_TABLES 1 +#define IWL_NUM_CHAIN_TABLES_V2 2 +#define IWL_NUM_CHAIN_LIMITS 2 +#define IWL_NUM_SUB_BANDS_V1 5 +#define IWL_NUM_SUB_BANDS_V2 11 + +/** + * struct iwl_dev_tx_power_common - Common part of the TX power reduction cmd + * @set_mode: see &enum iwl_dev_tx_power_cmd_mode + * @mac_context_id: id of the mac ctx for which we are reducing TX power. + * @pwr_restriction: TX power restriction in 1/8 dBms. + * @dev_24: device TX power restriction in 1/8 dBms + * @dev_52_low: device TX power restriction upper band - low + * @dev_52_high: device TX power restriction upper band - high + */ +struct iwl_dev_tx_power_common { + __le32 set_mode; + __le32 mac_context_id; + __le16 pwr_restriction; + __le16 dev_24; + __le16 dev_52_low; + __le16 dev_52_high; +}; + +/** + * struct iwl_dev_tx_power_cmd_v3 - TX power reduction command version 3 + * @per_chain: per chain restrictions + */ +struct iwl_dev_tx_power_cmd_v3 { + __le16 per_chain[IWL_NUM_CHAIN_TABLES][IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V1]; +} __packed; /* TX_REDUCED_POWER_API_S_VER_3 */ + +#define IWL_DEV_MAX_TX_POWER 0x7FFF + +/** + * struct iwl_dev_tx_power_cmd_v4 - TX power reduction command version 4 + * @per_chain: per chain restrictions + * @enable_ack_reduction: enable or disable close range ack TX power + * reduction. + * @reserved: reserved (padding) + */ +struct iwl_dev_tx_power_cmd_v4 { + __le16 per_chain[IWL_NUM_CHAIN_TABLES][IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V1]; + u8 enable_ack_reduction; + u8 reserved[3]; +} __packed; /* TX_REDUCED_POWER_API_S_VER_4 */ + +/** + * struct iwl_dev_tx_power_cmd_v5 - TX power reduction command version 5 + * @per_chain: per chain restrictions + * @enable_ack_reduction: enable or disable close range ack TX power + * reduction. + * @per_chain_restriction_changed: is per_chain_restriction has changed + * from last command. used if set_mode is + * IWL_TX_POWER_MODE_SET_SAR_TIMER. + * note: if not changed, the command is used for keep alive only. + * @reserved: reserved (padding) + * @timer_period: timer in milliseconds. if expires FW will change to default + * BIOS values. relevant if setMode is IWL_TX_POWER_MODE_SET_SAR_TIMER + */ +struct iwl_dev_tx_power_cmd_v5 { + __le16 per_chain[IWL_NUM_CHAIN_TABLES][IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V1]; + u8 enable_ack_reduction; + u8 per_chain_restriction_changed; + u8 reserved[2]; + __le32 timer_period; +} __packed; /* TX_REDUCED_POWER_API_S_VER_5 */ + +/** + * struct iwl_dev_tx_power_cmd_v5 - TX power reduction command version 5 + * @per_chain: per chain restrictions + * @enable_ack_reduction: enable or disable close range ack TX power + * reduction. + * @per_chain_restriction_changed: is per_chain_restriction has changed + * from last command. used if set_mode is + * IWL_TX_POWER_MODE_SET_SAR_TIMER. + * note: if not changed, the command is used for keep alive only. + * @reserved: reserved (padding) + * @timer_period: timer in milliseconds. if expires FW will change to default + * BIOS values. relevant if setMode is IWL_TX_POWER_MODE_SET_SAR_TIMER + */ +struct iwl_dev_tx_power_cmd_v6 { + __le16 per_chain[IWL_NUM_CHAIN_TABLES_V2][IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V2]; + u8 enable_ack_reduction; + u8 per_chain_restriction_changed; + u8 reserved[2]; + __le32 timer_period; +} __packed; /* TX_REDUCED_POWER_API_S_VER_6 */ + +/** + * struct iwl_dev_tx_power_cmd - TX power reduction command (multiversion) + * @common: common part of the command + * @v3: version 3 part of the command + * @v4: version 4 part of the command + * @v5: version 5 part of the command + * @v6: version 6 part of the command + */ +struct iwl_dev_tx_power_cmd { + struct iwl_dev_tx_power_common common; + union { + struct iwl_dev_tx_power_cmd_v3 v3; + struct iwl_dev_tx_power_cmd_v4 v4; + struct iwl_dev_tx_power_cmd_v5 v5; + struct iwl_dev_tx_power_cmd_v6 v6; + }; +}; + +#define IWL_NUM_GEO_PROFILES 3 +#define IWL_NUM_GEO_PROFILES_V3 8 +#define IWL_NUM_BANDS_PER_CHAIN_V1 2 +#define IWL_NUM_BANDS_PER_CHAIN_V2 3 + +/** + * enum iwl_geo_per_chain_offset_operation - type of operation + * @IWL_PER_CHAIN_OFFSET_SET_TABLES: send the tables from the host to the FW. + * @IWL_PER_CHAIN_OFFSET_GET_CURRENT_TABLE: retrieve the last configured table. + */ +enum iwl_geo_per_chain_offset_operation { + IWL_PER_CHAIN_OFFSET_SET_TABLES, + IWL_PER_CHAIN_OFFSET_GET_CURRENT_TABLE, +}; /* PER_CHAIN_OFFSET_OPERATION_E */ + +/** + * struct iwl_per_chain_offset - embedded struct for PER_CHAIN_LIMIT_OFFSET_CMD. + * @max_tx_power: maximum allowed tx power. + * @chain_a: tx power offset for chain a. + * @chain_b: tx power offset for chain b. + */ +struct iwl_per_chain_offset { + __le16 max_tx_power; + u8 chain_a; + u8 chain_b; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_PER_CHAIN_S_VER_1 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v1 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + */ +struct iwl_geo_tx_power_profiles_cmd_v1 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES][IWL_NUM_BANDS_PER_CHAIN_V1]; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_1 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v2 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + * @table_revision: BIOS table revision. + */ +struct iwl_geo_tx_power_profiles_cmd_v2 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES][IWL_NUM_BANDS_PER_CHAIN_V1]; + __le32 table_revision; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_2 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v3 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + * @table_revision: BIOS table revision. + */ +struct iwl_geo_tx_power_profiles_cmd_v3 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES][IWL_NUM_BANDS_PER_CHAIN_V2]; + __le32 table_revision; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_3 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v4 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + * @table_revision: BIOS table revision. + */ +struct iwl_geo_tx_power_profiles_cmd_v4 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES_V3][IWL_NUM_BANDS_PER_CHAIN_V1]; + __le32 table_revision; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_4 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v5 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + * @table_revision: BIOS table revision. + */ +struct iwl_geo_tx_power_profiles_cmd_v5 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES_V3][IWL_NUM_BANDS_PER_CHAIN_V2]; + __le32 table_revision; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_5 */ + +union iwl_geo_tx_power_profiles_cmd { + struct iwl_geo_tx_power_profiles_cmd_v1 v1; + struct iwl_geo_tx_power_profiles_cmd_v2 v2; + struct iwl_geo_tx_power_profiles_cmd_v3 v3; + struct iwl_geo_tx_power_profiles_cmd_v4 v4; + struct iwl_geo_tx_power_profiles_cmd_v5 v5; +}; + +/** + * struct iwl_geo_tx_power_profiles_resp - response to PER_CHAIN_LIMIT_OFFSET_CMD cmd + * @profile_idx: current geo profile in use + */ +struct iwl_geo_tx_power_profiles_resp { + __le32 profile_idx; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_RSP */ + +/** + * union iwl_ppag_table_cmd - union for all versions of PPAG command + * @v1: version 1 + * @v2: version 2 + * + * @flags: bit 0 - indicates enablement of PPAG for ETSI + * bit 1 - indicates enablement of PPAG for CHINA BIOS + * bit 1 can be used only in v3 (identical to v2) + * @gain: table of antenna gain values per chain and sub-band + * @reserved: reserved + */ +union iwl_ppag_table_cmd { + struct { + __le32 flags; + s8 gain[IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V1]; + s8 reserved[2]; + } v1; + struct { + __le32 flags; + s8 gain[IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V2]; + s8 reserved[2]; + } v2; +} __packed; + +/** + * struct iwl_beacon_filter_cmd + * REPLY_BEACON_FILTERING_CMD = 0xd2 (command) + * @bf_energy_delta: Used for RSSI filtering, if in 'normal' state. Send beacon + * to driver if delta in Energy values calculated for this and last + * passed beacon is greater than this threshold. Zero value means that + * the Energy change is ignored for beacon filtering, and beacon will + * not be forced to be sent to driver regardless of this delta. Typical + * energy delta 5dB. + * @bf_roaming_energy_delta: Used for RSSI filtering, if in 'roaming' state. + * Send beacon to driver if delta in Energy values calculated for this + * and last passed beacon is greater than this threshold. Zero value + * means that the Energy change is ignored for beacon filtering while in + * Roaming state, typical energy delta 1dB. + * @bf_roaming_state: Used for RSSI filtering. If absolute Energy values + * calculated for current beacon is less than the threshold, use + * Roaming Energy Delta Threshold, otherwise use normal Energy Delta + * Threshold. Typical energy threshold is -72dBm. + * @bf_temp_threshold: This threshold determines the type of temperature + * filtering (Slow or Fast) that is selected (Units are in Celsuis): + * If the current temperature is above this threshold - Fast filter + * will be used, If the current temperature is below this threshold - + * Slow filter will be used. + * @bf_temp_fast_filter: Send Beacon to driver if delta in temperature values + * calculated for this and the last passed beacon is greater than this + * threshold. Zero value means that the temperature change is ignored for + * beacon filtering; beacons will not be forced to be sent to driver + * regardless of whether its temerature has been changed. + * @bf_temp_slow_filter: Send Beacon to driver if delta in temperature values + * calculated for this and the last passed beacon is greater than this + * threshold. Zero value means that the temperature change is ignored for + * beacon filtering; beacons will not be forced to be sent to driver + * regardless of whether its temerature has been changed. + * @bf_enable_beacon_filter: 1, beacon filtering is enabled; 0, disabled. + * @bf_debug_flag: beacon filtering debug configuration + * @bf_escape_timer: Send beacons to to driver if no beacons were passed + * for a specific period of time. Units: Beacons. + * @ba_escape_timer: Fully receive and parse beacon if no beacons were passed + * for a longer period of time then this escape-timeout. Units: Beacons. + * @ba_enable_beacon_abort: 1, beacon abort is enabled; 0, disabled. + * @bf_threshold_absolute_low: See below. + * @bf_threshold_absolute_high: Send Beacon to driver if Energy value calculated + * for this beacon crossed this absolute threshold. For the 'Increase' + * direction the bf_energy_absolute_low[i] is used. For the 'Decrease' + * direction the bf_energy_absolute_high[i] is used. Zero value means + * that this specific threshold is ignored for beacon filtering, and + * beacon will not be forced to be sent to driver due to this setting. + */ +struct iwl_beacon_filter_cmd { + __le32 bf_energy_delta; + __le32 bf_roaming_energy_delta; + __le32 bf_roaming_state; + __le32 bf_temp_threshold; + __le32 bf_temp_fast_filter; + __le32 bf_temp_slow_filter; + __le32 bf_enable_beacon_filter; + __le32 bf_debug_flag; + __le32 bf_escape_timer; + __le32 ba_escape_timer; + __le32 ba_enable_beacon_abort; + __le32 bf_threshold_absolute_low[2]; + __le32 bf_threshold_absolute_high[2]; +} __packed; /* BEACON_FILTER_CONFIG_API_S_VER_4 */ + +/* Beacon filtering and beacon abort */ +#define IWL_BF_ENERGY_DELTA_DEFAULT 5 +#define IWL_BF_ENERGY_DELTA_D0I3 20 +#define IWL_BF_ENERGY_DELTA_MAX 255 +#define IWL_BF_ENERGY_DELTA_MIN 0 + +#define IWL_BF_ROAMING_ENERGY_DELTA_DEFAULT 1 +#define IWL_BF_ROAMING_ENERGY_DELTA_D0I3 20 +#define IWL_BF_ROAMING_ENERGY_DELTA_MAX 255 +#define IWL_BF_ROAMING_ENERGY_DELTA_MIN 0 + +#define IWL_BF_ROAMING_STATE_DEFAULT 72 +#define IWL_BF_ROAMING_STATE_D0I3 72 +#define IWL_BF_ROAMING_STATE_MAX 255 +#define IWL_BF_ROAMING_STATE_MIN 0 + +#define IWL_BF_TEMP_THRESHOLD_DEFAULT 112 +#define IWL_BF_TEMP_THRESHOLD_D0I3 112 +#define IWL_BF_TEMP_THRESHOLD_MAX 255 +#define IWL_BF_TEMP_THRESHOLD_MIN 0 + +#define IWL_BF_TEMP_FAST_FILTER_DEFAULT 1 +#define IWL_BF_TEMP_FAST_FILTER_D0I3 1 +#define IWL_BF_TEMP_FAST_FILTER_MAX 255 +#define IWL_BF_TEMP_FAST_FILTER_MIN 0 + +#define IWL_BF_TEMP_SLOW_FILTER_DEFAULT 5 +#define IWL_BF_TEMP_SLOW_FILTER_D0I3 20 +#define IWL_BF_TEMP_SLOW_FILTER_MAX 255 +#define IWL_BF_TEMP_SLOW_FILTER_MIN 0 + +#define IWL_BF_ENABLE_BEACON_FILTER_DEFAULT 1 + +#define IWL_BF_DEBUG_FLAG_DEFAULT 0 +#define IWL_BF_DEBUG_FLAG_D0I3 0 + +#define IWL_BF_ESCAPE_TIMER_DEFAULT 0 +#define IWL_BF_ESCAPE_TIMER_D0I3 0 +#define IWL_BF_ESCAPE_TIMER_MAX 1024 +#define IWL_BF_ESCAPE_TIMER_MIN 0 + +#define IWL_BA_ESCAPE_TIMER_DEFAULT 6 +#define IWL_BA_ESCAPE_TIMER_D0I3 6 +#define IWL_BA_ESCAPE_TIMER_D3 9 +#define IWL_BA_ESCAPE_TIMER_MAX 1024 +#define IWL_BA_ESCAPE_TIMER_MIN 0 + +#define IWL_BA_ENABLE_BEACON_ABORT_DEFAULT 1 + +#define IWL_BF_CMD_CONFIG(mode) \ + .bf_energy_delta = cpu_to_le32(IWL_BF_ENERGY_DELTA ## mode), \ + .bf_roaming_energy_delta = \ + cpu_to_le32(IWL_BF_ROAMING_ENERGY_DELTA ## mode), \ + .bf_roaming_state = cpu_to_le32(IWL_BF_ROAMING_STATE ## mode), \ + .bf_temp_threshold = cpu_to_le32(IWL_BF_TEMP_THRESHOLD ## mode), \ + .bf_temp_fast_filter = cpu_to_le32(IWL_BF_TEMP_FAST_FILTER ## mode), \ + .bf_temp_slow_filter = cpu_to_le32(IWL_BF_TEMP_SLOW_FILTER ## mode), \ + .bf_debug_flag = cpu_to_le32(IWL_BF_DEBUG_FLAG ## mode), \ + .bf_escape_timer = cpu_to_le32(IWL_BF_ESCAPE_TIMER ## mode), \ + .ba_escape_timer = cpu_to_le32(IWL_BA_ESCAPE_TIMER ## mode) + +#define IWL_BF_CMD_CONFIG_DEFAULTS IWL_BF_CMD_CONFIG(_DEFAULT) +#define IWL_BF_CMD_CONFIG_D0I3 IWL_BF_CMD_CONFIG(_D0I3) +#endif /* __iwl_fw_api_power_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/rfi.h b/sys/contrib/dev/iwlwifi/fw/api/rfi.h new file mode 100644 index 00000000000..c678b9aa9b5 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/rfi.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2020 Intel Corporation + */ +#ifndef __iwl_fw_api_rfi_h__ +#define __iwl_fw_api_rfi_h__ + +#define IWL_RFI_LUT_ENTRY_CHANNELS_NUM 15 +#define IWL_RFI_LUT_SIZE 24 +#define IWL_RFI_LUT_INSTALLED_SIZE 4 + +/** + * struct iwl_rfi_lut_entry - an entry in the RFI frequency LUT. + * + * @freq: frequency + * @channels: channels that can be interfered at frequency freq (at most 15) + * @bands: the corresponding bands + */ +struct iwl_rfi_lut_entry { + __le16 freq; + u8 channels[IWL_RFI_LUT_ENTRY_CHANNELS_NUM]; + u8 bands[IWL_RFI_LUT_ENTRY_CHANNELS_NUM]; +} __packed; + +/** + * struct iwl_rfi_config_cmd - RFI configuration table + * + * @entry: a table can have 24 frequency/channel mappings + * @oem: specifies if this is the default table or set by OEM + */ +struct iwl_rfi_config_cmd { + struct iwl_rfi_lut_entry table[IWL_RFI_LUT_SIZE]; + u8 oem; + u8 reserved[3]; +} __packed; /* RFI_CONFIG_CMD_API_S_VER_1 */ + +/** + * iwl_rfi_freq_table_status - status of the frequency table query + * @RFI_FREQ_TABLE_OK: can be used + * @RFI_FREQ_TABLE_DVFS_NOT_READY: DVFS is not ready yet, should try later + * @RFI_FREQ_TABLE_DISABLED: the feature is disabled in FW + */ +enum iwl_rfi_freq_table_status { + RFI_FREQ_TABLE_OK, + RFI_FREQ_TABLE_DVFS_NOT_READY, + RFI_FREQ_TABLE_DISABLED, +}; + +/** + * struct iwl_rfi_freq_table_resp_cmd - get the rfi freq table used by FW + * + * @table: table used by FW + * @status: see &iwl_rfi_freq_table_status + */ +struct iwl_rfi_freq_table_resp_cmd { + struct iwl_rfi_lut_entry table[IWL_RFI_LUT_INSTALLED_SIZE]; + __le32 status; +} __packed; /* RFI_CONFIG_CMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_rfi_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/rs.h b/sys/contrib/dev/iwlwifi/fw/api/rs.h new file mode 100644 index 00000000000..a09081d7ed4 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/rs.h @@ -0,0 +1,720 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_rs_h__ +#define __iwl_fw_api_rs_h__ + +#include "mac.h" + +/** + * enum iwl_tlc_mng_cfg_flags_enum - options for TLC config flags + * @IWL_TLC_MNG_CFG_FLAGS_STBC_MSK: enable STBC. For HE this enables STBC for + * bandwidths <= 80MHz + * @IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK: enable LDPC + * @IWL_TLC_MNG_CFG_FLAGS_HE_STBC_160MHZ_MSK: enable STBC in HE at 160MHz + * bandwidth + * @IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_1_MSK: enable HE Dual Carrier Modulation + * for BPSK (MCS 0) with 1 spatial + * stream + * @IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_2_MSK: enable HE Dual Carrier Modulation + * for BPSK (MCS 0) with 2 spatial + * streams + */ +enum iwl_tlc_mng_cfg_flags { + IWL_TLC_MNG_CFG_FLAGS_STBC_MSK = BIT(0), + IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK = BIT(1), + IWL_TLC_MNG_CFG_FLAGS_HE_STBC_160MHZ_MSK = BIT(2), + IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_1_MSK = BIT(3), + IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_2_MSK = BIT(4), +}; + +/** + * enum iwl_tlc_mng_cfg_cw - channel width options + * @IWL_TLC_MNG_CH_WIDTH_20MHZ: 20MHZ channel + * @IWL_TLC_MNG_CH_WIDTH_40MHZ: 40MHZ channel + * @IWL_TLC_MNG_CH_WIDTH_80MHZ: 80MHZ channel + * @IWL_TLC_MNG_CH_WIDTH_160MHZ: 160MHZ channel + * @IWL_TLC_MNG_CH_WIDTH_LAST: maximum value + */ +enum iwl_tlc_mng_cfg_cw { + IWL_TLC_MNG_CH_WIDTH_20MHZ, + IWL_TLC_MNG_CH_WIDTH_40MHZ, + IWL_TLC_MNG_CH_WIDTH_80MHZ, + IWL_TLC_MNG_CH_WIDTH_160MHZ, + IWL_TLC_MNG_CH_WIDTH_LAST = IWL_TLC_MNG_CH_WIDTH_160MHZ, +}; + +/** + * enum iwl_tlc_mng_cfg_chains - possible chains + * @IWL_TLC_MNG_CHAIN_A_MSK: chain A + * @IWL_TLC_MNG_CHAIN_B_MSK: chain B + */ +enum iwl_tlc_mng_cfg_chains { + IWL_TLC_MNG_CHAIN_A_MSK = BIT(0), + IWL_TLC_MNG_CHAIN_B_MSK = BIT(1), +}; + +/** + * enum iwl_tlc_mng_cfg_mode - supported modes + * @IWL_TLC_MNG_MODE_CCK: enable CCK + * @IWL_TLC_MNG_MODE_OFDM_NON_HT: enable OFDM (non HT) + * @IWL_TLC_MNG_MODE_NON_HT: enable non HT + * @IWL_TLC_MNG_MODE_HT: enable HT + * @IWL_TLC_MNG_MODE_VHT: enable VHT + * @IWL_TLC_MNG_MODE_HE: enable HE + * @IWL_TLC_MNG_MODE_INVALID: invalid value + * @IWL_TLC_MNG_MODE_NUM: a count of possible modes + */ +enum iwl_tlc_mng_cfg_mode { + IWL_TLC_MNG_MODE_CCK = 0, + IWL_TLC_MNG_MODE_OFDM_NON_HT = IWL_TLC_MNG_MODE_CCK, + IWL_TLC_MNG_MODE_NON_HT = IWL_TLC_MNG_MODE_CCK, + IWL_TLC_MNG_MODE_HT, + IWL_TLC_MNG_MODE_VHT, + IWL_TLC_MNG_MODE_HE, + IWL_TLC_MNG_MODE_INVALID, + IWL_TLC_MNG_MODE_NUM = IWL_TLC_MNG_MODE_INVALID, +}; + +/** + * enum iwl_tlc_mng_ht_rates - HT/VHT/HE rates + * @IWL_TLC_MNG_HT_RATE_MCS0: index of MCS0 + * @IWL_TLC_MNG_HT_RATE_MCS1: index of MCS1 + * @IWL_TLC_MNG_HT_RATE_MCS2: index of MCS2 + * @IWL_TLC_MNG_HT_RATE_MCS3: index of MCS3 + * @IWL_TLC_MNG_HT_RATE_MCS4: index of MCS4 + * @IWL_TLC_MNG_HT_RATE_MCS5: index of MCS5 + * @IWL_TLC_MNG_HT_RATE_MCS6: index of MCS6 + * @IWL_TLC_MNG_HT_RATE_MCS7: index of MCS7 + * @IWL_TLC_MNG_HT_RATE_MCS8: index of MCS8 + * @IWL_TLC_MNG_HT_RATE_MCS9: index of MCS9 + * @IWL_TLC_MNG_HT_RATE_MCS10: index of MCS10 + * @IWL_TLC_MNG_HT_RATE_MCS11: index of MCS11 + * @IWL_TLC_MNG_HT_RATE_MAX: maximal rate for HT/VHT + */ +enum iwl_tlc_mng_ht_rates { + IWL_TLC_MNG_HT_RATE_MCS0 = 0, + IWL_TLC_MNG_HT_RATE_MCS1, + IWL_TLC_MNG_HT_RATE_MCS2, + IWL_TLC_MNG_HT_RATE_MCS3, + IWL_TLC_MNG_HT_RATE_MCS4, + IWL_TLC_MNG_HT_RATE_MCS5, + IWL_TLC_MNG_HT_RATE_MCS6, + IWL_TLC_MNG_HT_RATE_MCS7, + IWL_TLC_MNG_HT_RATE_MCS8, + IWL_TLC_MNG_HT_RATE_MCS9, + IWL_TLC_MNG_HT_RATE_MCS10, + IWL_TLC_MNG_HT_RATE_MCS11, + IWL_TLC_MNG_HT_RATE_MAX = IWL_TLC_MNG_HT_RATE_MCS11, +}; + +enum IWL_TLC_MNG_NSS { + IWL_TLC_NSS_1, + IWL_TLC_NSS_2, + IWL_TLC_NSS_MAX +}; + +enum IWL_TLC_HT_BW_RATES { + IWL_TLC_HT_BW_NONE_160, + IWL_TLC_HT_BW_160, +}; + +/** + * struct tlc_config_cmd - TLC configuration + * @sta_id: station id + * @reserved1: reserved + * @max_ch_width: max supported channel width from @enum iwl_tlc_mng_cfg_cw + * @mode: &enum iwl_tlc_mng_cfg_mode + * @chains: bitmask of &enum iwl_tlc_mng_cfg_chains + * @amsdu: TX amsdu is supported + * @flags: bitmask of &enum iwl_tlc_mng_cfg_flags + * @non_ht_rates: bitmap of supported legacy rates + * @ht_rates: bitmap of &enum iwl_tlc_mng_ht_rates, per + * pair (0 - 80mhz width and below, 1 - 160mhz). + * @max_mpdu_len: max MPDU length, in bytes + * @sgi_ch_width_supp: bitmap of SGI support per channel width + * use BIT(@enum iwl_tlc_mng_cfg_cw) + * @reserved2: reserved + * @max_tx_op: max TXOP in uSecs for all AC (BK, BE, VO, VI), + * set zero for no limit. + */ +struct iwl_tlc_config_cmd { + u8 sta_id; + u8 reserved1[3]; + u8 max_ch_width; + u8 mode; + u8 chains; + u8 amsdu; + __le16 flags; + __le16 non_ht_rates; + __le16 ht_rates[IWL_TLC_NSS_MAX][2]; + __le16 max_mpdu_len; + u8 sgi_ch_width_supp; + u8 reserved2; + __le32 max_tx_op; +} __packed; /* TLC_MNG_CONFIG_CMD_API_S_VER_3 */ + +/** + * enum iwl_tlc_update_flags - updated fields + * @IWL_TLC_NOTIF_FLAG_RATE: last initial rate update + * @IWL_TLC_NOTIF_FLAG_AMSDU: umsdu parameters update + */ +enum iwl_tlc_update_flags { + IWL_TLC_NOTIF_FLAG_RATE = BIT(0), + IWL_TLC_NOTIF_FLAG_AMSDU = BIT(1), +}; + +/** + * struct iwl_tlc_update_notif - TLC notification from FW + * @sta_id: station id + * @reserved: reserved + * @flags: bitmap of notifications reported + * @rate: current initial rate + * @amsdu_size: Max AMSDU size, in bytes + * @amsdu_enabled: bitmap for per-TID AMSDU enablement + */ +struct iwl_tlc_update_notif { + u8 sta_id; + u8 reserved[3]; + __le32 flags; + __le32 rate; + __le32 amsdu_size; + __le32 amsdu_enabled; +} __packed; /* TLC_MNG_UPDATE_NTFY_API_S_VER_2 */ + + +#define IWL_MAX_MCS_DISPLAY_SIZE 12 + +struct iwl_rate_mcs_info { + char mbps[IWL_MAX_MCS_DISPLAY_SIZE]; + char mcs[IWL_MAX_MCS_DISPLAY_SIZE]; +}; + +/* + * These serve as indexes into + * struct iwl_rate_info fw_rate_idx_to_plcp[IWL_RATE_COUNT]; + * TODO: avoid overlap between legacy and HT rates + */ +enum { + IWL_RATE_1M_INDEX = 0, + IWL_FIRST_CCK_RATE = IWL_RATE_1M_INDEX, + IWL_RATE_2M_INDEX, + IWL_RATE_5M_INDEX, + IWL_RATE_11M_INDEX, + IWL_LAST_CCK_RATE = IWL_RATE_11M_INDEX, + IWL_RATE_6M_INDEX, + IWL_FIRST_OFDM_RATE = IWL_RATE_6M_INDEX, + IWL_RATE_MCS_0_INDEX = IWL_RATE_6M_INDEX, + IWL_FIRST_HT_RATE = IWL_RATE_MCS_0_INDEX, + IWL_FIRST_VHT_RATE = IWL_RATE_MCS_0_INDEX, + IWL_RATE_9M_INDEX, + IWL_RATE_12M_INDEX, + IWL_RATE_MCS_1_INDEX = IWL_RATE_12M_INDEX, + IWL_RATE_18M_INDEX, + IWL_RATE_MCS_2_INDEX = IWL_RATE_18M_INDEX, + IWL_RATE_24M_INDEX, + IWL_RATE_MCS_3_INDEX = IWL_RATE_24M_INDEX, + IWL_RATE_36M_INDEX, + IWL_RATE_MCS_4_INDEX = IWL_RATE_36M_INDEX, + IWL_RATE_48M_INDEX, + IWL_RATE_MCS_5_INDEX = IWL_RATE_48M_INDEX, + IWL_RATE_54M_INDEX, + IWL_RATE_MCS_6_INDEX = IWL_RATE_54M_INDEX, + IWL_LAST_NON_HT_RATE = IWL_RATE_54M_INDEX, + IWL_RATE_60M_INDEX, + IWL_RATE_MCS_7_INDEX = IWL_RATE_60M_INDEX, + IWL_LAST_HT_RATE = IWL_RATE_MCS_7_INDEX, + IWL_RATE_MCS_8_INDEX, + IWL_RATE_MCS_9_INDEX, + IWL_LAST_VHT_RATE = IWL_RATE_MCS_9_INDEX, + IWL_RATE_MCS_10_INDEX, + IWL_RATE_MCS_11_INDEX, + IWL_LAST_HE_RATE = IWL_RATE_MCS_11_INDEX, + IWL_RATE_COUNT_LEGACY = IWL_LAST_NON_HT_RATE + 1, + IWL_RATE_COUNT = IWL_LAST_HE_RATE + 1, + IWL_RATE_INVM_INDEX = IWL_RATE_COUNT, + IWL_RATE_INVALID = IWL_RATE_COUNT, +}; + +#define IWL_RATE_BIT_MSK(r) BIT(IWL_RATE_##r##M_INDEX) + +/* fw API values for legacy bit rates, both OFDM and CCK */ +enum { + IWL_RATE_6M_PLCP = 13, + IWL_RATE_9M_PLCP = 15, + IWL_RATE_12M_PLCP = 5, + IWL_RATE_18M_PLCP = 7, + IWL_RATE_24M_PLCP = 9, + IWL_RATE_36M_PLCP = 11, + IWL_RATE_48M_PLCP = 1, + IWL_RATE_54M_PLCP = 3, + IWL_RATE_1M_PLCP = 10, + IWL_RATE_2M_PLCP = 20, + IWL_RATE_5M_PLCP = 55, + IWL_RATE_11M_PLCP = 110, + IWL_RATE_INVM_PLCP = -1, +}; + +/* + * rate_n_flags bit fields version 1 + * + * The 32-bit value has different layouts in the low 8 bites depending on the + * format. There are three formats, HT, VHT and legacy (11abg, with subformats + * for CCK and OFDM). + * + * High-throughput (HT) rate format + * bit 8 is 1, bit 26 is 0, bit 9 is 0 (OFDM) + * Very High-throughput (VHT) rate format + * bit 8 is 0, bit 26 is 1, bit 9 is 0 (OFDM) + * Legacy OFDM rate format for bits 7:0 + * bit 8 is 0, bit 26 is 0, bit 9 is 0 (OFDM) + * Legacy CCK rate format for bits 7:0: + * bit 8 is 0, bit 26 is 0, bit 9 is 1 (CCK) + */ + +/* Bit 8: (1) HT format, (0) legacy or VHT format */ +#define RATE_MCS_HT_POS 8 +#define RATE_MCS_HT_MSK_V1 BIT(RATE_MCS_HT_POS) + +/* Bit 9: (1) CCK, (0) OFDM. HT (bit 8) must be "0" for this bit to be valid */ +#define RATE_MCS_CCK_POS_V1 9 +#define RATE_MCS_CCK_MSK_V1 BIT(RATE_MCS_CCK_POS_V1) + +/* Bit 26: (1) VHT format, (0) legacy format in bits 8:0 */ +#define RATE_MCS_VHT_POS_V1 26 +#define RATE_MCS_VHT_MSK_V1 BIT(RATE_MCS_VHT_POS_V1) + + +/* + * High-throughput (HT) rate format for bits 7:0 + * + * 2-0: MCS rate base + * 0) 6 Mbps + * 1) 12 Mbps + * 2) 18 Mbps + * 3) 24 Mbps + * 4) 36 Mbps + * 5) 48 Mbps + * 6) 54 Mbps + * 7) 60 Mbps + * 4-3: 0) Single stream (SISO) + * 1) Dual stream (MIMO) + * 2) Triple stream (MIMO) + * 5: Value of 0x20 in bits 7:0 indicates 6 Mbps HT40 duplicate data + * (bits 7-6 are zero) + * + * Together the low 5 bits work out to the MCS index because we don't + * support MCSes above 15/23, and 0-7 have one stream, 8-15 have two + * streams and 16-23 have three streams. We could also support MCS 32 + * which is the duplicate 20 MHz MCS (bit 5 set, all others zero.) + */ +#define RATE_HT_MCS_RATE_CODE_MSK_V1 0x7 +#define RATE_HT_MCS_NSS_POS_V1 3 +#define RATE_HT_MCS_NSS_MSK_V1 (3 << RATE_HT_MCS_NSS_POS_V1) +#define RATE_HT_MCS_MIMO2_MSK BIT(RATE_HT_MCS_NSS_POS_V1) + +/* Bit 10: (1) Use Green Field preamble */ +#define RATE_HT_MCS_GF_POS 10 +#define RATE_HT_MCS_GF_MSK (1 << RATE_HT_MCS_GF_POS) + +#define RATE_HT_MCS_INDEX_MSK_V1 0x3f + +/* + * Very High-throughput (VHT) rate format for bits 7:0 + * + * 3-0: VHT MCS (0-9) + * 5-4: number of streams - 1: + * 0) Single stream (SISO) + * 1) Dual stream (MIMO) + * 2) Triple stream (MIMO) + */ + +/* Bit 4-5: (0) SISO, (1) MIMO2 (2) MIMO3 */ +#define RATE_VHT_MCS_RATE_CODE_MSK 0xf +#define RATE_VHT_MCS_NSS_POS 4 +#define RATE_VHT_MCS_NSS_MSK (3 << RATE_VHT_MCS_NSS_POS) +#define RATE_VHT_MCS_MIMO2_MSK BIT(RATE_VHT_MCS_NSS_POS) + +/* + * Legacy OFDM rate format for bits 7:0 + * + * 3-0: 0xD) 6 Mbps + * 0xF) 9 Mbps + * 0x5) 12 Mbps + * 0x7) 18 Mbps + * 0x9) 24 Mbps + * 0xB) 36 Mbps + * 0x1) 48 Mbps + * 0x3) 54 Mbps + * (bits 7-4 are 0) + * + * Legacy CCK rate format for bits 7:0: + * bit 8 is 0, bit 26 is 0, bit 9 is 1 (CCK): + * + * 6-0: 10) 1 Mbps + * 20) 2 Mbps + * 55) 5.5 Mbps + * 110) 11 Mbps + * (bit 7 is 0) + */ +#define RATE_LEGACY_RATE_MSK_V1 0xff + +/* Bit 10 - OFDM HE */ +#define RATE_MCS_HE_POS_V1 10 +#define RATE_MCS_HE_MSK_V1 BIT(RATE_MCS_HE_POS_V1) + +/* + * Bit 11-12: (0) 20MHz, (1) 40MHz, (2) 80MHz, (3) 160MHz + * 0 and 1 are valid for HT and VHT, 2 and 3 only for VHT + */ +#define RATE_MCS_CHAN_WIDTH_POS 11 +#define RATE_MCS_CHAN_WIDTH_MSK_V1 (3 << RATE_MCS_CHAN_WIDTH_POS) + +/* Bit 13: (1) Short guard interval (0.4 usec), (0) normal GI (0.8 usec) */ +#define RATE_MCS_SGI_POS_V1 13 +#define RATE_MCS_SGI_MSK_V1 BIT(RATE_MCS_SGI_POS_V1) + +/* Bit 14-16: Antenna selection (1) Ant A, (2) Ant B, (4) Ant C */ +#define RATE_MCS_ANT_POS 14 +#define RATE_MCS_ANT_A_MSK (1 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_B_MSK (2 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_AB_MSK (RATE_MCS_ANT_A_MSK | \ + RATE_MCS_ANT_B_MSK) +#define RATE_MCS_ANT_MSK RATE_MCS_ANT_AB_MSK + +/* Bit 17: (0) SS, (1) SS*2 */ +#define RATE_MCS_STBC_POS 17 +#define RATE_MCS_STBC_MSK BIT(RATE_MCS_STBC_POS) + +/* Bit 18: OFDM-HE dual carrier mode */ +#define RATE_HE_DUAL_CARRIER_MODE 18 +#define RATE_HE_DUAL_CARRIER_MODE_MSK BIT(RATE_HE_DUAL_CARRIER_MODE) + +/* Bit 19: (0) Beamforming is off, (1) Beamforming is on */ +#define RATE_MCS_BF_POS 19 +#define RATE_MCS_BF_MSK (1 << RATE_MCS_BF_POS) + +/* + * Bit 20-21: HE LTF type and guard interval + * HE (ext) SU: + * 0 1xLTF+0.8us + * 1 2xLTF+0.8us + * 2 2xLTF+1.6us + * 3 & SGI (bit 13) clear 4xLTF+3.2us + * 3 & SGI (bit 13) set 4xLTF+0.8us + * HE MU: + * 0 4xLTF+0.8us + * 1 2xLTF+0.8us + * 2 2xLTF+1.6us + * 3 4xLTF+3.2us + * HE TRIG: + * 0 1xLTF+1.6us + * 1 2xLTF+1.6us + * 2 4xLTF+3.2us + * 3 (does not occur) + */ +#define RATE_MCS_HE_GI_LTF_POS 20 +#define RATE_MCS_HE_GI_LTF_MSK_V1 (3 << RATE_MCS_HE_GI_LTF_POS) + +/* Bit 22-23: HE type. (0) SU, (1) SU_EXT, (2) MU, (3) trigger based */ +#define RATE_MCS_HE_TYPE_POS_V1 22 +#define RATE_MCS_HE_TYPE_SU_V1 (0 << RATE_MCS_HE_TYPE_POS_V1) +#define RATE_MCS_HE_TYPE_EXT_SU_V1 BIT(RATE_MCS_HE_TYPE_POS_V1) +#define RATE_MCS_HE_TYPE_MU_V1 (2 << RATE_MCS_HE_TYPE_POS_V1) +#define RATE_MCS_HE_TYPE_TRIG_V1 (3 << RATE_MCS_HE_TYPE_POS_V1) +#define RATE_MCS_HE_TYPE_MSK_V1 (3 << RATE_MCS_HE_TYPE_POS_V1) + +/* Bit 24-25: (0) 20MHz (no dup), (1) 2x20MHz, (2) 4x20MHz, 3 8x20MHz */ +#define RATE_MCS_DUP_POS_V1 24 +#define RATE_MCS_DUP_MSK_V1 (3 << RATE_MCS_DUP_POS_V1) + +/* Bit 27: (1) LDPC enabled, (0) LDPC disabled */ +#define RATE_MCS_LDPC_POS_V1 27 +#define RATE_MCS_LDPC_MSK_V1 BIT(RATE_MCS_LDPC_POS_V1) + +/* Bit 28: (1) 106-tone RX (8 MHz RU), (0) normal bandwidth */ +#define RATE_MCS_HE_106T_POS_V1 28 +#define RATE_MCS_HE_106T_MSK_V1 BIT(RATE_MCS_HE_106T_POS_V1) + +/* Bit 30-31: (1) RTS, (2) CTS */ +#define RATE_MCS_RTS_REQUIRED_POS (30) +#define RATE_MCS_RTS_REQUIRED_MSK (0x1 << RATE_MCS_RTS_REQUIRED_POS) + +#define RATE_MCS_CTS_REQUIRED_POS (31) +#define RATE_MCS_CTS_REQUIRED_MSK (0x1 << RATE_MCS_CTS_REQUIRED_POS) + +/* rate_n_flags bit field version 2 + * + * The 32-bit value has different layouts in the low 8 bits depending on the + * format. There are three formats, HT, VHT and legacy (11abg, with subformats + * for CCK and OFDM). + * + */ + +/* Bits 10-8: rate format + * (0) Legacy CCK (1) Legacy OFDM (2) High-throughput (HT) + * (3) Very High-throughput (VHT) (4) High-efficiency (HE) + * (5) Extremely High-throughput (EHT) + */ +#define RATE_MCS_MOD_TYPE_POS 8 +#define RATE_MCS_MOD_TYPE_MSK (0x7 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_CCK_MSK (0 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_LEGACY_OFDM_MSK (1 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_HT_MSK (2 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_VHT_MSK (3 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_HE_MSK (4 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_EHT_MSK (5 << RATE_MCS_MOD_TYPE_POS) + +/* + * Legacy CCK rate format for bits 0:3: + * + * (0) 0xa - 1 Mbps + * (1) 0x14 - 2 Mbps + * (2) 0x37 - 5.5 Mbps + * (3) 0x6e - 11 nbps + * + * Legacy OFDM rate format for bis 3:0: + * + * (0) 6 Mbps + * (1) 9 Mbps + * (2) 12 Mbps + * (3) 18 Mbps + * (4) 24 Mbps + * (5) 36 Mbps + * (6) 48 Mbps + * (7) 54 Mbps + * + */ +#define RATE_LEGACY_RATE_MSK 0x7 + +/* + * HT, VHT, HE, EHT rate format for bits 3:0 + * 3-0: MCS + * + */ +#define RATE_HT_MCS_CODE_MSK 0x7 +#define RATE_MCS_NSS_POS 4 +#define RATE_MCS_NSS_MSK (1 << RATE_MCS_NSS_POS) +#define RATE_MCS_CODE_MSK 0xf +#define RATE_HT_MCS_INDEX(r) ((((r) & RATE_MCS_NSS_MSK) >> 1) | \ + ((r) & RATE_HT_MCS_CODE_MSK)) + +/* Bits 7-5: reserved */ + +/* + * Bits 13-11: (0) 20MHz, (1) 40MHz, (2) 80MHz, (3) 160MHz, (4) 320MHz + */ +#define RATE_MCS_CHAN_WIDTH_MSK (0x7 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_20 (0 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_40 (1 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_80 (2 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_160 (3 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_320 (4 << RATE_MCS_CHAN_WIDTH_POS) + +/* Bit 15-14: Antenna selection: + * Bit 14: Ant A active + * Bit 15: Ant B active + * + * All relevant definitions are same as in v1 + */ + +/* Bit 16 (1) LDPC enables, (0) LDPC disabled */ +#define RATE_MCS_LDPC_POS 16 +#define RATE_MCS_LDPC_MSK (1 << RATE_MCS_LDPC_POS) + +/* Bit 17: (0) SS, (1) SS*2 (same as v1) */ + +/* Bit 18: OFDM-HE dual carrier mode (same as v1) */ + +/* Bit 19: (0) Beamforming is off, (1) Beamforming is on (same as v1) */ + +/* + * Bit 22-20: HE LTF type and guard interval + * CCK: + * 0 long preamble + * 1 short preamble + * HT/VHT: + * 0 0.8us + * 1 0.4us + * HE (ext) SU: + * 0 1xLTF+0.8us + * 1 2xLTF+0.8us + * 2 2xLTF+1.6us + * 3 4xLTF+3.2us + * 4 4xLTF+0.8us + * HE MU: + * 0 4xLTF+0.8us + * 1 2xLTF+0.8us + * 2 2xLTF+1.6us + * 3 4xLTF+3.2us + * HE TRIG: + * 0 1xLTF+1.6us + * 1 2xLTF+1.6us + * 2 4xLTF+3.2us + * */ +#define RATE_MCS_HE_GI_LTF_MSK (0x7 << RATE_MCS_HE_GI_LTF_POS) +#define RATE_MCS_SGI_POS RATE_MCS_HE_GI_LTF_POS +#define RATE_MCS_SGI_MSK (1 << RATE_MCS_SGI_POS) +#define RATE_MCS_HE_SU_4_LTF 3 +#define RATE_MCS_HE_SU_4_LTF_08_GI 4 + +/* Bit 24-23: HE type. (0) SU, (1) SU_EXT, (2) MU, (3) trigger based */ +#define RATE_MCS_HE_TYPE_POS 23 +#define RATE_MCS_HE_TYPE_SU (0 << RATE_MCS_HE_TYPE_POS) +#define RATE_MCS_HE_TYPE_EXT_SU (1 << RATE_MCS_HE_TYPE_POS) +#define RATE_MCS_HE_TYPE_MU (2 << RATE_MCS_HE_TYPE_POS) +#define RATE_MCS_HE_TYPE_TRIG (3 << RATE_MCS_HE_TYPE_POS) +#define RATE_MCS_HE_TYPE_MSK (3 << RATE_MCS_HE_TYPE_POS) + +/* Bit 25: duplicate channel enabled + * + * if this bit is set, duplicate is according to BW (bits 11-13): + * + * CCK: 2x 20MHz + * OFDM Legacy: N x 20Mhz, (N = BW \ 2 , either 2, 4, 8, 16) + * EHT: 2 x BW/2, (80 - 2x40, 160 - 2x80, 320 - 2x160) + * */ +#define RATE_MCS_DUP_POS 25 +#define RATE_MCS_DUP_MSK (1 << RATE_MCS_DUP_POS) + +/* Bit 26: (1) 106-tone RX (8 MHz RU), (0) normal bandwidth */ +#define RATE_MCS_HE_106T_POS 26 +#define RATE_MCS_HE_106T_MSK (1 << RATE_MCS_HE_106T_POS) + +/* Bit 27: EHT extra LTF: + * instead of 1 LTF for SISO use 2 LTFs, + * instead of 2 LTFs for NSTS=2 use 4 LTFs*/ +#define RATE_MCS_EHT_EXTRA_LTF_POS 27 +#define RATE_MCS_EHT_EXTRA_LTF_MSK (1 << RATE_MCS_EHT_EXTRA_LTF_POS) + +/* Bit 31-28: reserved */ + +/* Link Quality definitions */ + +/* # entries in rate scale table to support Tx retries */ +#define LQ_MAX_RETRY_NUM 16 + +/* Link quality command flags bit fields */ + +/* Bit 0: (0) Don't use RTS (1) Use RTS */ +#define LQ_FLAG_USE_RTS_POS 0 +#define LQ_FLAG_USE_RTS_MSK (1 << LQ_FLAG_USE_RTS_POS) + +/* Bit 1-3: LQ command color. Used to match responses to LQ commands */ +#define LQ_FLAG_COLOR_POS 1 +#define LQ_FLAG_COLOR_MSK (7 << LQ_FLAG_COLOR_POS) +#define LQ_FLAG_COLOR_GET(_f) (((_f) & LQ_FLAG_COLOR_MSK) >>\ + LQ_FLAG_COLOR_POS) +#define LQ_FLAGS_COLOR_INC(_c) ((((_c) + 1) << LQ_FLAG_COLOR_POS) &\ + LQ_FLAG_COLOR_MSK) +#define LQ_FLAG_COLOR_SET(_f, _c) ((_c) | ((_f) & ~LQ_FLAG_COLOR_MSK)) + +/* Bit 4-5: Tx RTS BW Signalling + * (0) No RTS BW signalling + * (1) Static BW signalling + * (2) Dynamic BW signalling + */ +#define LQ_FLAG_RTS_BW_SIG_POS 4 +#define LQ_FLAG_RTS_BW_SIG_NONE (0 << LQ_FLAG_RTS_BW_SIG_POS) +#define LQ_FLAG_RTS_BW_SIG_STATIC (1 << LQ_FLAG_RTS_BW_SIG_POS) +#define LQ_FLAG_RTS_BW_SIG_DYNAMIC (2 << LQ_FLAG_RTS_BW_SIG_POS) + +/* Bit 6: (0) No dynamic BW selection (1) Allow dynamic BW selection + * Dyanmic BW selection allows Tx with narrower BW then requested in rates + */ +#define LQ_FLAG_DYNAMIC_BW_POS 6 +#define LQ_FLAG_DYNAMIC_BW_MSK (1 << LQ_FLAG_DYNAMIC_BW_POS) + +/* Single Stream Tx Parameters (lq_cmd->ss_params) + * Flags to control a smart FW decision about whether BFER/STBC/SISO will be + * used for single stream Tx. + */ + +/* Bit 0-1: Max STBC streams allowed. Can be 0-3. + * (0) - No STBC allowed + * (1) - 2x1 STBC allowed (HT/VHT) + * (2) - 4x2 STBC allowed (HT/VHT) + * (3) - 3x2 STBC allowed (HT only) + * All our chips are at most 2 antennas so only (1) is valid for now. + */ +#define LQ_SS_STBC_ALLOWED_POS 0 +#define LQ_SS_STBC_ALLOWED_MSK (3 << LQ_SS_STBC_ALLOWED_MSK) + +/* 2x1 STBC is allowed */ +#define LQ_SS_STBC_1SS_ALLOWED (1 << LQ_SS_STBC_ALLOWED_POS) + +/* Bit 2: Beamformer (VHT only) is allowed */ +#define LQ_SS_BFER_ALLOWED_POS 2 +#define LQ_SS_BFER_ALLOWED (1 << LQ_SS_BFER_ALLOWED_POS) + +/* Bit 3: Force BFER or STBC for testing + * If this is set: + * If BFER is allowed then force the ucode to choose BFER else + * If STBC is allowed then force the ucode to choose STBC over SISO + */ +#define LQ_SS_FORCE_POS 3 +#define LQ_SS_FORCE (1 << LQ_SS_FORCE_POS) + +/* Bit 31: ss_params field is valid. Used for FW backward compatibility + * with other drivers which don't support the ss_params API yet + */ +#define LQ_SS_PARAMS_VALID_POS 31 +#define LQ_SS_PARAMS_VALID (1 << LQ_SS_PARAMS_VALID_POS) + +/** + * struct iwl_lq_cmd - link quality command + * @sta_id: station to update + * @reduced_tpc: reduced transmit power control value + * @control: not used + * @flags: combination of LQ_FLAG_* + * @mimo_delim: the first SISO index in rs_table, which separates MIMO + * and SISO rates + * @single_stream_ant_msk: best antenna for SISO (can be dual in CDD). + * Should be ANT_[ABC] + * @dual_stream_ant_msk: best antennas for MIMO, combination of ANT_[ABC] + * @initial_rate_index: first index from rs_table per AC category + * @agg_time_limit: aggregation max time threshold in usec/100, meaning + * value of 100 is one usec. Range is 100 to 8000 + * @agg_disable_start_th: try-count threshold for starting aggregation. + * If a frame has higher try-count, it should not be selected for + * starting an aggregation sequence. + * @agg_frame_cnt_limit: max frame count in an aggregation. + * 0: no limit + * 1: no aggregation (one frame per aggregation) + * 2 - 0x3f: maximal number of frames (up to 3f == 63) + * @reserved2: reserved + * @rs_table: array of rates for each TX try, each is rate_n_flags, + * meaning it is a combination of RATE_MCS_* and IWL_RATE_*_PLCP + * @ss_params: single stream features. declare whether STBC or BFER are allowed. + */ +struct iwl_lq_cmd { + u8 sta_id; + u8 reduced_tpc; + __le16 control; + /* LINK_QUAL_GENERAL_PARAMS_API_S_VER_1 */ + u8 flags; + u8 mimo_delim; + u8 single_stream_ant_msk; + u8 dual_stream_ant_msk; + u8 initial_rate_index[AC_NUM]; + /* LINK_QUAL_AGG_PARAMS_API_S_VER_1 */ + __le16 agg_time_limit; + u8 agg_disable_start_th; + u8 agg_frame_cnt_limit; + __le32 reserved2; + __le32 rs_table[LQ_MAX_RETRY_NUM]; + __le32 ss_params; +}; /* LINK_QUALITY_CMD_API_S_VER_1 */ + +u8 iwl_fw_rate_idx_to_plcp(int idx); +u32 iwl_new_rate_from_v1(u32 rate_v1); +u32 iwl_legacy_rate_to_fw_idx(u32 rate_n_flags); +const struct iwl_rate_mcs_info *iwl_rate_mcs(int idx); +const char *iwl_rs_pretty_ant(u8 ant); +const char *iwl_rs_pretty_bw(int bw); +int rs_pretty_print_rate(char *buf, int bufsz, const u32 rate); +bool iwl_he_is_sgi(u32 rate_n_flags); + +#endif /* __iwl_fw_api_rs_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/rx.h b/sys/contrib/dev/iwlwifi/fw/api/rx.h new file mode 100644 index 00000000000..45658aa5933 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/rx.h @@ -0,0 +1,852 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_rx_h__ +#define __iwl_fw_api_rx_h__ + +/* API for pre-9000 hardware */ + +#define IWL_RX_INFO_PHY_CNT 8 +#define IWL_RX_INFO_ENERGY_ANT_ABC_IDX 1 +#define IWL_RX_INFO_ENERGY_ANT_A_MSK 0x000000ff +#define IWL_RX_INFO_ENERGY_ANT_B_MSK 0x0000ff00 +#define IWL_RX_INFO_ENERGY_ANT_A_POS 0 +#define IWL_RX_INFO_ENERGY_ANT_B_POS 8 +#define IWL_RX_INFO_ENERGY_ANT_C_POS 16 + +enum iwl_mac_context_info { + MAC_CONTEXT_INFO_NONE, + MAC_CONTEXT_INFO_GSCAN, +}; + +/** + * struct iwl_rx_phy_info - phy info + * (REPLY_RX_PHY_CMD = 0xc0) + * @non_cfg_phy_cnt: non configurable DSP phy data byte count + * @cfg_phy_cnt: configurable DSP phy data byte count + * @stat_id: configurable DSP phy data set ID + * @reserved1: reserved + * @system_timestamp: GP2 at on air rise + * @timestamp: TSF at on air rise + * @beacon_time_stamp: beacon at on-air rise + * @phy_flags: general phy flags: band, modulation, ... + * @channel: channel number + * @non_cfg_phy: for various implementations of non_cfg_phy + * @rate_n_flags: RATE_MCS_* + * @byte_count: frame's byte-count + * @frame_time: frame's time on the air, based on byte count and frame rate + * calculation + * @mac_active_msk: what MACs were active when the frame was received + * @mac_context_info: additional info on the context in which the frame was + * received as defined in &enum iwl_mac_context_info + * + * Before each Rx, the device sends this data. It contains PHY information + * about the reception of the packet. + */ +struct iwl_rx_phy_info { + u8 non_cfg_phy_cnt; + u8 cfg_phy_cnt; + u8 stat_id; + u8 reserved1; + __le32 system_timestamp; + __le64 timestamp; + __le32 beacon_time_stamp; + __le16 phy_flags; + __le16 channel; + __le32 non_cfg_phy[IWL_RX_INFO_PHY_CNT]; + __le32 rate_n_flags; + __le32 byte_count; + u8 mac_active_msk; + u8 mac_context_info; + __le16 frame_time; +} __packed; + +/* + * TCP offload Rx assist info + * + * bits 0:3 - reserved + * bits 4:7 - MIC CRC length + * bits 8:12 - MAC header length + * bit 13 - Padding indication + * bit 14 - A-AMSDU indication + * bit 15 - Offload enabled + */ +enum iwl_csum_rx_assist_info { + CSUM_RXA_RESERVED_MASK = 0x000f, + CSUM_RXA_MICSIZE_MASK = 0x00f0, + CSUM_RXA_HEADERLEN_MASK = 0x1f00, + CSUM_RXA_PADD = BIT(13), + CSUM_RXA_AMSDU = BIT(14), + CSUM_RXA_ENA = BIT(15) +}; + +/** + * struct iwl_rx_mpdu_res_start - phy info + * @byte_count: byte count of the frame + * @assist: see &enum iwl_csum_rx_assist_info + */ +struct iwl_rx_mpdu_res_start { + __le16 byte_count; + __le16 assist; +} __packed; /* _RX_MPDU_RES_START_API_S_VER_2 */ + +/** + * enum iwl_rx_phy_flags - to parse %iwl_rx_phy_info phy_flags + * @RX_RES_PHY_FLAGS_BAND_24: true if the packet was received on 2.4 band + * @RX_RES_PHY_FLAGS_MOD_CCK: modulation is CCK + * @RX_RES_PHY_FLAGS_SHORT_PREAMBLE: true if packet's preamble was short + * @RX_RES_PHY_FLAGS_NARROW_BAND: narrow band (<20 MHz) receive + * @RX_RES_PHY_FLAGS_ANTENNA: antenna on which the packet was received + * @RX_RES_PHY_FLAGS_ANTENNA_POS: antenna bit position + * @RX_RES_PHY_FLAGS_AGG: set if the packet was part of an A-MPDU + * @RX_RES_PHY_FLAGS_OFDM_HT: The frame was an HT frame + * @RX_RES_PHY_FLAGS_OFDM_GF: The frame used GF preamble + * @RX_RES_PHY_FLAGS_OFDM_VHT: The frame was a VHT frame + */ +enum iwl_rx_phy_flags { + RX_RES_PHY_FLAGS_BAND_24 = BIT(0), + RX_RES_PHY_FLAGS_MOD_CCK = BIT(1), + RX_RES_PHY_FLAGS_SHORT_PREAMBLE = BIT(2), + RX_RES_PHY_FLAGS_NARROW_BAND = BIT(3), + RX_RES_PHY_FLAGS_ANTENNA = (0x7 << 4), + RX_RES_PHY_FLAGS_ANTENNA_POS = 4, + RX_RES_PHY_FLAGS_AGG = BIT(7), + RX_RES_PHY_FLAGS_OFDM_HT = BIT(8), + RX_RES_PHY_FLAGS_OFDM_GF = BIT(9), + RX_RES_PHY_FLAGS_OFDM_VHT = BIT(10), +}; + +/** + * enum iwl_mvm_rx_status - written by fw for each Rx packet + * @RX_MPDU_RES_STATUS_CRC_OK: CRC is fine + * @RX_MPDU_RES_STATUS_OVERRUN_OK: there was no RXE overflow + * @RX_MPDU_RES_STATUS_SRC_STA_FOUND: station was found + * @RX_MPDU_RES_STATUS_KEY_VALID: key was valid + * @RX_MPDU_RES_STATUS_ICV_OK: ICV is fine, if not, the packet is destroyed + * @RX_MPDU_RES_STATUS_MIC_OK: used for CCM alg only. TKIP MIC is checked + * in the driver. + * @RX_MPDU_RES_STATUS_TTAK_OK: TTAK is fine + * @RX_MPDU_RES_STATUS_MNG_FRAME_REPLAY_ERR: valid for alg = CCM_CMAC or + * alg = CCM only. Checks replay attack for 11w frames. + * @RX_MPDU_RES_STATUS_SEC_NO_ENC: this frame is not encrypted + * @RX_MPDU_RES_STATUS_SEC_WEP_ENC: this frame is encrypted using WEP + * @RX_MPDU_RES_STATUS_SEC_CCM_ENC: this frame is encrypted using CCM + * @RX_MPDU_RES_STATUS_SEC_TKIP_ENC: this frame is encrypted using TKIP + * @RX_MPDU_RES_STATUS_SEC_EXT_ENC: this frame is encrypted using extension + * algorithm + * @RX_MPDU_RES_STATUS_SEC_CMAC_GMAC_ENC: this frame is protected using + * CMAC or GMAC + * @RX_MPDU_RES_STATUS_SEC_ENC_ERR: this frame couldn't be decrypted + * @RX_MPDU_RES_STATUS_SEC_ENC_MSK: bitmask of the encryption algorithm + * @RX_MPDU_RES_STATUS_DEC_DONE: this frame has been successfully decrypted + * @RX_MPDU_RES_STATUS_CSUM_DONE: checksum was done by the hw + * @RX_MPDU_RES_STATUS_CSUM_OK: checksum found no errors + * @RX_MPDU_RES_STATUS_STA_ID_MSK: station ID mask + * @RX_MDPU_RES_STATUS_STA_ID_SHIFT: station ID bit shift + */ +enum iwl_mvm_rx_status { + RX_MPDU_RES_STATUS_CRC_OK = BIT(0), + RX_MPDU_RES_STATUS_OVERRUN_OK = BIT(1), + RX_MPDU_RES_STATUS_SRC_STA_FOUND = BIT(2), + RX_MPDU_RES_STATUS_KEY_VALID = BIT(3), + RX_MPDU_RES_STATUS_ICV_OK = BIT(5), + RX_MPDU_RES_STATUS_MIC_OK = BIT(6), + RX_MPDU_RES_STATUS_TTAK_OK = BIT(7), + RX_MPDU_RES_STATUS_MNG_FRAME_REPLAY_ERR = BIT(7), + RX_MPDU_RES_STATUS_SEC_NO_ENC = (0 << 8), + RX_MPDU_RES_STATUS_SEC_WEP_ENC = (1 << 8), + RX_MPDU_RES_STATUS_SEC_CCM_ENC = (2 << 8), + RX_MPDU_RES_STATUS_SEC_TKIP_ENC = (3 << 8), + RX_MPDU_RES_STATUS_SEC_EXT_ENC = (4 << 8), + RX_MPDU_RES_STATUS_SEC_CMAC_GMAC_ENC = (6 << 8), + RX_MPDU_RES_STATUS_SEC_ENC_ERR = (7 << 8), + RX_MPDU_RES_STATUS_SEC_ENC_MSK = (7 << 8), + RX_MPDU_RES_STATUS_DEC_DONE = BIT(11), + RX_MPDU_RES_STATUS_CSUM_DONE = BIT(16), + RX_MPDU_RES_STATUS_CSUM_OK = BIT(17), + RX_MDPU_RES_STATUS_STA_ID_SHIFT = 24, + RX_MPDU_RES_STATUS_STA_ID_MSK = 0x1f << RX_MDPU_RES_STATUS_STA_ID_SHIFT, +}; + +/* 9000 series API */ +enum iwl_rx_mpdu_mac_flags1 { + IWL_RX_MDPU_MFLG1_ADDRTYPE_MASK = 0x03, + IWL_RX_MPDU_MFLG1_MIC_CRC_LEN_MASK = 0xf0, + /* shift should be 4, but the length is measured in 2-byte + * words, so shifting only by 3 gives a byte result + */ + IWL_RX_MPDU_MFLG1_MIC_CRC_LEN_SHIFT = 3, +}; + +enum iwl_rx_mpdu_mac_flags2 { + /* in 2-byte words */ + IWL_RX_MPDU_MFLG2_HDR_LEN_MASK = 0x1f, + IWL_RX_MPDU_MFLG2_PAD = 0x20, + IWL_RX_MPDU_MFLG2_AMSDU = 0x40, +}; + +enum iwl_rx_mpdu_amsdu_info { + IWL_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK = 0x7f, + IWL_RX_MPDU_AMSDU_LAST_SUBFRAME = 0x80, +}; + +#define RX_MPDU_BAND_POS 6 +#define RX_MPDU_BAND_MASK 0xC0 +#define BAND_IN_RX_STATUS(_val) \ + (((_val) & RX_MPDU_BAND_MASK) >> RX_MPDU_BAND_POS) + +enum iwl_rx_l3_proto_values { + IWL_RX_L3_TYPE_NONE, + IWL_RX_L3_TYPE_IPV4, + IWL_RX_L3_TYPE_IPV4_FRAG, + IWL_RX_L3_TYPE_IPV6_FRAG, + IWL_RX_L3_TYPE_IPV6, + IWL_RX_L3_TYPE_IPV6_IN_IPV4, + IWL_RX_L3_TYPE_ARP, + IWL_RX_L3_TYPE_EAPOL, +}; + +#define IWL_RX_L3_PROTO_POS 4 + +enum iwl_rx_l3l4_flags { + IWL_RX_L3L4_IP_HDR_CSUM_OK = BIT(0), + IWL_RX_L3L4_TCP_UDP_CSUM_OK = BIT(1), + IWL_RX_L3L4_TCP_FIN_SYN_RST_PSH = BIT(2), + IWL_RX_L3L4_TCP_ACK = BIT(3), + IWL_RX_L3L4_L3_PROTO_MASK = 0xf << IWL_RX_L3_PROTO_POS, + IWL_RX_L3L4_L4_PROTO_MASK = 0xf << 8, + IWL_RX_L3L4_RSS_HASH_MASK = 0xf << 12, +}; + +enum iwl_rx_mpdu_status { + IWL_RX_MPDU_STATUS_CRC_OK = BIT(0), + IWL_RX_MPDU_STATUS_OVERRUN_OK = BIT(1), + IWL_RX_MPDU_STATUS_SRC_STA_FOUND = BIT(2), + IWL_RX_MPDU_STATUS_KEY_VALID = BIT(3), + IWL_RX_MPDU_STATUS_ICV_OK = BIT(5), + IWL_RX_MPDU_STATUS_MIC_OK = BIT(6), + IWL_RX_MPDU_RES_STATUS_TTAK_OK = BIT(7), + /* overlayed since IWL_UCODE_TLV_API_DEPRECATE_TTAK */ + IWL_RX_MPDU_STATUS_REPLAY_ERROR = BIT(7), + IWL_RX_MPDU_STATUS_SEC_MASK = 0x7 << 8, + IWL_RX_MPDU_STATUS_SEC_UNKNOWN = IWL_RX_MPDU_STATUS_SEC_MASK, + IWL_RX_MPDU_STATUS_SEC_NONE = 0x0 << 8, + IWL_RX_MPDU_STATUS_SEC_WEP = 0x1 << 8, + IWL_RX_MPDU_STATUS_SEC_CCM = 0x2 << 8, + IWL_RX_MPDU_STATUS_SEC_TKIP = 0x3 << 8, + IWL_RX_MPDU_STATUS_SEC_EXT_ENC = 0x4 << 8, + IWL_RX_MPDU_STATUS_SEC_GCM = 0x5 << 8, +#if defined(__FreeBSD__) + IWL_RX_MPDU_STATUS_SEC_ENC_ERR = 0x7 << 8, +#endif + IWL_RX_MPDU_STATUS_DECRYPTED = BIT(11), + IWL_RX_MPDU_STATUS_ROBUST_MNG_FRAME = BIT(15), + + IWL_RX_MPDU_STATUS_DUPLICATE = BIT(22), + + IWL_RX_MPDU_STATUS_STA_ID = 0x1f000000, +}; + +#define IWL_RX_REORDER_DATA_INVALID_BAID 0x7f + +enum iwl_rx_mpdu_reorder_data { + IWL_RX_MPDU_REORDER_NSSN_MASK = 0x00000fff, + IWL_RX_MPDU_REORDER_SN_MASK = 0x00fff000, + IWL_RX_MPDU_REORDER_SN_SHIFT = 12, + IWL_RX_MPDU_REORDER_BAID_MASK = 0x7f000000, + IWL_RX_MPDU_REORDER_BAID_SHIFT = 24, + IWL_RX_MPDU_REORDER_BA_OLD_SN = 0x80000000, +}; + +enum iwl_rx_mpdu_phy_info { + IWL_RX_MPDU_PHY_AMPDU = BIT(5), + IWL_RX_MPDU_PHY_AMPDU_TOGGLE = BIT(6), + IWL_RX_MPDU_PHY_SHORT_PREAMBLE = BIT(7), + /* short preamble is only for CCK, for non-CCK overridden by this */ + IWL_RX_MPDU_PHY_NCCK_ADDTL_NTFY = BIT(7), + IWL_RX_MPDU_PHY_TSF_OVERLOAD = BIT(8), +}; + +enum iwl_rx_mpdu_mac_info { + IWL_RX_MPDU_PHY_MAC_INDEX_MASK = 0x0f, + IWL_RX_MPDU_PHY_PHY_INDEX_MASK = 0xf0, +}; + +/* TSF overload low dword */ +enum iwl_rx_phy_data0 { + /* info type: HE any */ + IWL_RX_PHY_DATA0_HE_BEAM_CHNG = 0x00000001, + IWL_RX_PHY_DATA0_HE_UPLINK = 0x00000002, + IWL_RX_PHY_DATA0_HE_BSS_COLOR_MASK = 0x000000fc, + IWL_RX_PHY_DATA0_HE_SPATIAL_REUSE_MASK = 0x00000f00, + /* 1 bit reserved */ + IWL_RX_PHY_DATA0_HE_TXOP_DUR_MASK = 0x000fe000, + IWL_RX_PHY_DATA0_HE_LDPC_EXT_SYM = 0x00100000, + IWL_RX_PHY_DATA0_HE_PRE_FEC_PAD_MASK = 0x00600000, + IWL_RX_PHY_DATA0_HE_PE_DISAMBIG = 0x00800000, + IWL_RX_PHY_DATA0_HE_DOPPLER = 0x01000000, + /* 6 bits reserved */ + IWL_RX_PHY_DATA0_HE_DELIM_EOF = 0x80000000, +}; + +enum iwl_rx_phy_info_type { + IWL_RX_PHY_INFO_TYPE_NONE = 0, + IWL_RX_PHY_INFO_TYPE_CCK = 1, + IWL_RX_PHY_INFO_TYPE_OFDM_LGCY = 2, + IWL_RX_PHY_INFO_TYPE_HT = 3, + IWL_RX_PHY_INFO_TYPE_VHT_SU = 4, + IWL_RX_PHY_INFO_TYPE_VHT_MU = 5, + IWL_RX_PHY_INFO_TYPE_HE_SU = 6, + IWL_RX_PHY_INFO_TYPE_HE_MU = 7, + IWL_RX_PHY_INFO_TYPE_HE_TB = 8, + IWL_RX_PHY_INFO_TYPE_HE_MU_EXT = 9, + IWL_RX_PHY_INFO_TYPE_HE_TB_EXT = 10, +}; + +/* TSF overload high dword */ +enum iwl_rx_phy_data1 { + /* + * check this first - if TSF overload is set, + * see &enum iwl_rx_phy_info_type + */ + IWL_RX_PHY_DATA1_INFO_TYPE_MASK = 0xf0000000, + + /* info type: HT/VHT/HE any */ + IWL_RX_PHY_DATA1_LSIG_LEN_MASK = 0x0fff0000, + + /* info type: HE MU/MU-EXT */ + IWL_RX_PHY_DATA1_HE_MU_SIGB_COMPRESSION = 0x00000001, + IWL_RX_PHY_DATA1_HE_MU_SIBG_SYM_OR_USER_NUM_MASK = 0x0000001e, + + /* info type: HE any */ + IWL_RX_PHY_DATA1_HE_LTF_NUM_MASK = 0x000000e0, + IWL_RX_PHY_DATA1_HE_RU_ALLOC_SEC80 = 0x00000100, + /* trigger encoded */ + IWL_RX_PHY_DATA1_HE_RU_ALLOC_MASK = 0x0000fe00, + + /* info type: HE TB/TX-EXT */ + IWL_RX_PHY_DATA1_HE_TB_PILOT_TYPE = 0x00000001, + IWL_RX_PHY_DATA1_HE_TB_LOW_SS_MASK = 0x0000000e, +}; + +/* goes into Metadata DW 7 */ +enum iwl_rx_phy_data2 { + /* info type: HE MU-EXT */ + /* the a1/a2/... is what the PHY/firmware calls the values */ + IWL_RX_PHY_DATA2_HE_MU_EXT_CH1_RU0 = 0x000000ff, /* a1 */ + IWL_RX_PHY_DATA2_HE_MU_EXT_CH1_RU2 = 0x0000ff00, /* a2 */ + IWL_RX_PHY_DATA2_HE_MU_EXT_CH2_RU0 = 0x00ff0000, /* b1 */ + IWL_RX_PHY_DATA2_HE_MU_EXT_CH2_RU2 = 0xff000000, /* b2 */ + + /* info type: HE TB-EXT */ + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE1 = 0x0000000f, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE2 = 0x000000f0, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE3 = 0x00000f00, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE4 = 0x0000f000, +}; + +/* goes into Metadata DW 8 */ +enum iwl_rx_phy_data3 { + /* info type: HE MU-EXT */ + IWL_RX_PHY_DATA3_HE_MU_EXT_CH1_RU1 = 0x000000ff, /* c1 */ + IWL_RX_PHY_DATA3_HE_MU_EXT_CH1_RU3 = 0x0000ff00, /* c2 */ + IWL_RX_PHY_DATA3_HE_MU_EXT_CH2_RU1 = 0x00ff0000, /* d1 */ + IWL_RX_PHY_DATA3_HE_MU_EXT_CH2_RU3 = 0xff000000, /* d2 */ +}; + +/* goes into Metadata DW 4 high 16 bits */ +enum iwl_rx_phy_data4 { + /* info type: HE MU-EXT */ + IWL_RX_PHY_DATA4_HE_MU_EXT_CH1_CTR_RU = 0x0001, + IWL_RX_PHY_DATA4_HE_MU_EXT_CH2_CTR_RU = 0x0002, + IWL_RX_PHY_DATA4_HE_MU_EXT_CH1_CRC_OK = 0x0004, + IWL_RX_PHY_DATA4_HE_MU_EXT_CH2_CRC_OK = 0x0008, + IWL_RX_PHY_DATA4_HE_MU_EXT_SIGB_MCS_MASK = 0x00f0, + IWL_RX_PHY_DATA4_HE_MU_EXT_SIGB_DCM = 0x0100, + IWL_RX_PHY_DATA4_HE_MU_EXT_PREAMBLE_PUNC_TYPE_MASK = 0x0600, +}; + +/** + * struct iwl_rx_mpdu_desc_v1 - RX MPDU descriptor + */ +struct iwl_rx_mpdu_desc_v1 { + /* DW7 - carries rss_hash only when rpa_en == 1 */ + union { + /** + * @rss_hash: RSS hash value + */ + __le32 rss_hash; + + /** + * @phy_data2: depends on info type (see @phy_data1) + */ + __le32 phy_data2; + }; + + /* DW8 - carries filter_match only when rpa_en == 1 */ + union { + /** + * @filter_match: filter match value + */ + __le32 filter_match; + + /** + * @phy_data3: depends on info type (see @phy_data1) + */ + __le32 phy_data3; + }; + + /* DW9 */ + /** + * @rate_n_flags: RX rate/flags encoding + */ + __le32 rate_n_flags; + /* DW10 */ + /** + * @energy_a: energy chain A + */ + u8 energy_a; + /** + * @energy_b: energy chain B + */ + u8 energy_b; + /** + * @channel: channel number + */ + u8 channel; + /** + * @mac_context: MAC context mask + */ + u8 mac_context; + /* DW11 */ + /** + * @gp2_on_air_rise: GP2 timer value on air rise (INA) + */ + __le32 gp2_on_air_rise; + /* DW12 & DW13 */ + union { + /** + * @tsf_on_air_rise: + * TSF value on air rise (INA), only valid if + * %IWL_RX_MPDU_PHY_TSF_OVERLOAD isn't set + */ + __le64 tsf_on_air_rise; + + struct { + /** + * @phy_data0: depends on info_type, see @phy_data1 + */ + __le32 phy_data0; + /** + * @phy_data1: valid only if + * %IWL_RX_MPDU_PHY_TSF_OVERLOAD is set, + * see &enum iwl_rx_phy_data1. + */ + __le32 phy_data1; + }; + }; +} __packed; /* RX_MPDU_RES_START_API_S_VER_4 */ + +/** + * struct iwl_rx_mpdu_desc_v3 - RX MPDU descriptor + */ +struct iwl_rx_mpdu_desc_v3 { + /* DW7 - carries filter_match only when rpa_en == 1 */ + union { + /** + * @filter_match: filter match value + */ + __le32 filter_match; + + /** + * @phy_data3: depends on info type (see @phy_data1) + */ + __le32 phy_data3; + }; + + /* DW8 - carries rss_hash only when rpa_en == 1 */ + union { + /** + * @rss_hash: RSS hash value + */ + __le32 rss_hash; + + /** + * @phy_data2: depends on info type (see @phy_data1) + */ + __le32 phy_data2; + }; + /* DW9 */ + /** + * @partial_hash: 31:0 ip/tcp header hash + * w/o some fields (such as IP SRC addr) + */ + __le32 partial_hash; + /* DW10 */ + /** + * @raw_xsum: raw xsum value + */ + __be16 raw_xsum; + /** + * @reserved_xsum: reserved high bits in the raw checksum + */ + __le16 reserved_xsum; + /* DW11 */ + /** + * @rate_n_flags: RX rate/flags encoding + */ + __le32 rate_n_flags; + /* DW12 */ + /** + * @energy_a: energy chain A + */ + u8 energy_a; + /** + * @energy_b: energy chain B + */ + u8 energy_b; + /** + * @channel: channel number + */ + u8 channel; + /** + * @mac_context: MAC context mask + */ + u8 mac_context; + /* DW13 */ + /** + * @gp2_on_air_rise: GP2 timer value on air rise (INA) + */ + __le32 gp2_on_air_rise; + /* DW14 & DW15 */ + union { + /** + * @tsf_on_air_rise: + * TSF value on air rise (INA), only valid if + * %IWL_RX_MPDU_PHY_TSF_OVERLOAD isn't set + */ + __le64 tsf_on_air_rise; + + struct { + /** + * @phy_data0: depends on info_type, see @phy_data1 + */ + __le32 phy_data0; + /** + * @phy_data1: valid only if + * %IWL_RX_MPDU_PHY_TSF_OVERLOAD is set, + * see &enum iwl_rx_phy_data1. + */ + __le32 phy_data1; + }; + }; + /* DW16 & DW17 */ + /** + * @reserved: reserved + */ + __le32 reserved[2]; +} __packed; /* RX_MPDU_RES_START_API_S_VER_3, + RX_MPDU_RES_START_API_S_VER_5 */ + +/** + * struct iwl_rx_mpdu_desc - RX MPDU descriptor + */ +struct iwl_rx_mpdu_desc { + /* DW2 */ + /** + * @mpdu_len: MPDU length + */ + __le16 mpdu_len; + /** + * @mac_flags1: &enum iwl_rx_mpdu_mac_flags1 + */ + u8 mac_flags1; + /** + * @mac_flags2: &enum iwl_rx_mpdu_mac_flags2 + */ + u8 mac_flags2; + /* DW3 */ + /** + * @amsdu_info: &enum iwl_rx_mpdu_amsdu_info + */ + u8 amsdu_info; + /** + * @phy_info: &enum iwl_rx_mpdu_phy_info + */ + __le16 phy_info; + /** + * @mac_phy_idx: MAC/PHY index + */ + u8 mac_phy_idx; + /* DW4 - carries csum data only when rpa_en == 1 */ + /** + * @raw_csum: raw checksum (alledgedly unreliable) + */ + __le16 raw_csum; + + union { + /** + * @l3l4_flags: &enum iwl_rx_l3l4_flags + */ + __le16 l3l4_flags; + + /** + * @phy_data4: depends on info type, see phy_data1 + */ + __le16 phy_data4; + }; + /* DW5 */ + /** + * @status: &enum iwl_rx_mpdu_status + */ + __le32 status; + + /* DW6 */ + /** + * @reorder_data: &enum iwl_rx_mpdu_reorder_data + */ + __le32 reorder_data; + + union { + struct iwl_rx_mpdu_desc_v1 v1; + struct iwl_rx_mpdu_desc_v3 v3; + }; +} __packed; /* RX_MPDU_RES_START_API_S_VER_3, + RX_MPDU_RES_START_API_S_VER_4, + RX_MPDU_RES_START_API_S_VER_5 */ + +#define IWL_RX_DESC_SIZE_V1 offsetofend(struct iwl_rx_mpdu_desc, v1) + +#define RX_NO_DATA_CHAIN_A_POS 0 +#define RX_NO_DATA_CHAIN_A_MSK (0xff << RX_NO_DATA_CHAIN_A_POS) +#define RX_NO_DATA_CHAIN_B_POS 8 +#define RX_NO_DATA_CHAIN_B_MSK (0xff << RX_NO_DATA_CHAIN_B_POS) +#define RX_NO_DATA_CHANNEL_POS 16 +#define RX_NO_DATA_CHANNEL_MSK (0xff << RX_NO_DATA_CHANNEL_POS) + +#define RX_NO_DATA_INFO_TYPE_POS 0 +#define RX_NO_DATA_INFO_TYPE_MSK (0xff << RX_NO_DATA_INFO_TYPE_POS) +#define RX_NO_DATA_INFO_TYPE_NONE 0 +#define RX_NO_DATA_INFO_TYPE_RX_ERR 1 +#define RX_NO_DATA_INFO_TYPE_NDP 2 +#define RX_NO_DATA_INFO_TYPE_MU_UNMATCHED 3 +#define RX_NO_DATA_INFO_TYPE_HE_TB_UNMATCHED 4 + +#define RX_NO_DATA_INFO_ERR_POS 8 +#define RX_NO_DATA_INFO_ERR_MSK (0xff << RX_NO_DATA_INFO_ERR_POS) +#define RX_NO_DATA_INFO_ERR_NONE 0 +#define RX_NO_DATA_INFO_ERR_BAD_PLCP 1 +#define RX_NO_DATA_INFO_ERR_UNSUPPORTED_RATE 2 +#define RX_NO_DATA_INFO_ERR_NO_DELIM 3 +#define RX_NO_DATA_INFO_ERR_BAD_MAC_HDR 4 + +#define RX_NO_DATA_FRAME_TIME_POS 0 +#define RX_NO_DATA_FRAME_TIME_MSK (0xfffff << RX_NO_DATA_FRAME_TIME_POS) + +#define RX_NO_DATA_RX_VEC0_HE_NSTS_MSK 0x03800000 +#define RX_NO_DATA_RX_VEC0_VHT_NSTS_MSK 0x38000000 + +/** + * struct iwl_rx_no_data - RX no data descriptor + * @info: 7:0 frame type, 15:8 RX error type + * @rssi: 7:0 energy chain-A, + * 15:8 chain-B, measured at FINA time (FINA_ENERGY), 16:23 channel + * @on_air_rise_time: GP2 during on air rise + * @fr_time: frame time + * @rate: rate/mcs of frame + * @phy_info: &enum iwl_rx_phy_data0 and &enum iwl_rx_phy_info_type + * @rx_vec: DW-12:9 raw RX vectors from DSP according to modulation type. + * for VHT: OFDM_RX_VECTOR_SIGA1_OUT, OFDM_RX_VECTOR_SIGA2_OUT + * for HE: OFDM_RX_VECTOR_HE_SIGA1_OUT, OFDM_RX_VECTOR_HE_SIGA2_OUT + */ +struct iwl_rx_no_data { + __le32 info; + __le32 rssi; + __le32 on_air_rise_time; + __le32 fr_time; + __le32 rate; + __le32 phy_info[2]; + __le32 rx_vec[2]; +} __packed; /* RX_NO_DATA_NTFY_API_S_VER_1, + TX_NO_DATA_NTFY_API_S_VER_2 */ + +struct iwl_frame_release { + u8 baid; + u8 reserved; + __le16 nssn; +}; + +/** + * enum iwl_bar_frame_release_sta_tid - STA/TID information for BAR release + * @IWL_BAR_FRAME_RELEASE_TID_MASK: TID mask + * @IWL_BAR_FRAME_RELEASE_STA_MASK: STA mask + */ +enum iwl_bar_frame_release_sta_tid { + IWL_BAR_FRAME_RELEASE_TID_MASK = 0x0000000f, + IWL_BAR_FRAME_RELEASE_STA_MASK = 0x000001f0, +}; + +/** + * enum iwl_bar_frame_release_ba_info - BA information for BAR release + * @IWL_BAR_FRAME_RELEASE_NSSN_MASK: NSSN mask + * @IWL_BAR_FRAME_RELEASE_SN_MASK: SN mask (ignored by driver) + * @IWL_BAR_FRAME_RELEASE_BAID_MASK: BAID mask + */ +enum iwl_bar_frame_release_ba_info { + IWL_BAR_FRAME_RELEASE_NSSN_MASK = 0x00000fff, + IWL_BAR_FRAME_RELEASE_SN_MASK = 0x00fff000, + IWL_BAR_FRAME_RELEASE_BAID_MASK = 0x3f000000, +}; + +/** + * struct iwl_bar_frame_release - frame release from BAR info + * @sta_tid: STA & TID information, see &enum iwl_bar_frame_release_sta_tid. + * @ba_info: BA information, see &enum iwl_bar_frame_release_ba_info. + */ +struct iwl_bar_frame_release { + __le32 sta_tid; + __le32 ba_info; +} __packed; /* RX_BAR_TO_FRAME_RELEASE_API_S_VER_1 */ + +enum iwl_rss_hash_func_en { + IWL_RSS_HASH_TYPE_IPV4_TCP, + IWL_RSS_HASH_TYPE_IPV4_UDP, + IWL_RSS_HASH_TYPE_IPV4_PAYLOAD, + IWL_RSS_HASH_TYPE_IPV6_TCP, + IWL_RSS_HASH_TYPE_IPV6_UDP, + IWL_RSS_HASH_TYPE_IPV6_PAYLOAD, +}; + +#define IWL_RSS_HASH_KEY_CNT 10 +#define IWL_RSS_INDIRECTION_TABLE_SIZE 128 +#define IWL_RSS_ENABLE 1 + +/** + * struct iwl_rss_config_cmd - RSS (Receive Side Scaling) configuration + * + * @flags: 1 - enable, 0 - disable + * @hash_mask: Type of RSS to use. Values are from %iwl_rss_hash_func_en + * @reserved: reserved + * @secret_key: 320 bit input of random key configuration from driver + * @indirection_table: indirection table + */ +struct iwl_rss_config_cmd { + __le32 flags; + u8 hash_mask; + u8 reserved[3]; + __le32 secret_key[IWL_RSS_HASH_KEY_CNT]; + u8 indirection_table[IWL_RSS_INDIRECTION_TABLE_SIZE]; +} __packed; /* RSS_CONFIG_CMD_API_S_VER_1 */ + +#define IWL_MULTI_QUEUE_SYNC_SENDER_POS 0 +#define IWL_MULTI_QUEUE_SYNC_SENDER_MSK 0xf + +/** + * struct iwl_rxq_sync_cmd - RXQ notification trigger + * + * @flags: flags of the notification. bit 0:3 are the sender queue + * @rxq_mask: rx queues to send the notification on + * @count: number of bytes in payload, should be DWORD aligned + * @payload: data to send to rx queues + */ +struct iwl_rxq_sync_cmd { + __le32 flags; + __le32 rxq_mask; + __le32 count; + u8 payload[0]; +} __packed; /* MULTI_QUEUE_DRV_SYNC_HDR_CMD_API_S_VER_1 */ + +/** + * struct iwl_rxq_sync_notification - Notification triggered by RXQ + * sync command + * + * @count: number of bytes in payload + * @payload: data to send to rx queues + */ +struct iwl_rxq_sync_notification { + __le32 count; + u8 payload[0]; +} __packed; /* MULTI_QUEUE_DRV_SYNC_HDR_CMD_API_S_VER_1 */ + +/** + * enum iwl_mvm_pm_event - type of station PM event + * @IWL_MVM_PM_EVENT_AWAKE: station woke up + * @IWL_MVM_PM_EVENT_ASLEEP: station went to sleep + * @IWL_MVM_PM_EVENT_UAPSD: station sent uAPSD trigger + * @IWL_MVM_PM_EVENT_PS_POLL: station sent PS-Poll + */ +enum iwl_mvm_pm_event { + IWL_MVM_PM_EVENT_AWAKE, + IWL_MVM_PM_EVENT_ASLEEP, + IWL_MVM_PM_EVENT_UAPSD, + IWL_MVM_PM_EVENT_PS_POLL, +}; /* PEER_PM_NTFY_API_E_VER_1 */ + +/** + * struct iwl_mvm_pm_state_notification - station PM state notification + * @sta_id: station ID of the station changing state + * @type: the new powersave state, see &enum iwl_mvm_pm_event + */ +struct iwl_mvm_pm_state_notification { + u8 sta_id; + u8 type; + /* private: */ + __le16 reserved; +} __packed; /* PEER_PM_NTFY_API_S_VER_1 */ + +#define BA_WINDOW_STREAMS_MAX 16 +#define BA_WINDOW_STATUS_TID_MSK 0x000F +#define BA_WINDOW_STATUS_STA_ID_POS 4 +#define BA_WINDOW_STATUS_STA_ID_MSK 0x01F0 +#define BA_WINDOW_STATUS_VALID_MSK BIT(9) + +/** + * struct iwl_ba_window_status_notif - reordering window's status notification + * @bitmap: bitmap of received frames [start_seq_num + 0]..[start_seq_num + 63] + * @ra_tid: bit 3:0 - TID, bit 8:4 - STA_ID, bit 9 - valid + * @start_seq_num: the start sequence number of the bitmap + * @mpdu_rx_count: the number of received MPDUs since entering D0i3 + */ +struct iwl_ba_window_status_notif { + __le64 bitmap[BA_WINDOW_STREAMS_MAX]; + __le16 ra_tid[BA_WINDOW_STREAMS_MAX]; + __le32 start_seq_num[BA_WINDOW_STREAMS_MAX]; + __le16 mpdu_rx_count[BA_WINDOW_STREAMS_MAX]; +} __packed; /* BA_WINDOW_STATUS_NTFY_API_S_VER_1 */ + +/** + * struct iwl_rfh_queue_config - RX queue configuration + * @q_num: Q num + * @enable: enable queue + * @reserved: alignment + * @urbd_stts_wrptr: DMA address of urbd_stts_wrptr + * @fr_bd_cb: DMA address of freeRB table + * @ur_bd_cb: DMA address of used RB table + * @fr_bd_wid: Initial index of the free table + */ +struct iwl_rfh_queue_data { + u8 q_num; + u8 enable; + __le16 reserved; + __le64 urbd_stts_wrptr; + __le64 fr_bd_cb; + __le64 ur_bd_cb; + __le32 fr_bd_wid; +} __packed; /* RFH_QUEUE_CONFIG_S_VER_1 */ + +/** + * struct iwl_rfh_queue_config - RX queue configuration + * @num_queues: number of queues configured + * @reserved: alignment + * @data: DMA addresses per-queue + */ +struct iwl_rfh_queue_config { + u8 num_queues; + u8 reserved[3]; + struct iwl_rfh_queue_data data[0]; +} __packed; /* RFH_QUEUE_CONFIG_API_S_VER_1 */ + +#endif /* __iwl_fw_api_rx_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/scan.h b/sys/contrib/dev/iwlwifi/fw/api/scan.h new file mode 100644 index 00000000000..8b200379f7c --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/scan.h @@ -0,0 +1,1190 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_scan_h__ +#define __iwl_fw_api_scan_h__ + +/* Scan Commands, Responses, Notifications */ + +/* Max number of IEs for direct SSID scans in a command */ +#define PROBE_OPTION_MAX 20 + +#define SCAN_SHORT_SSID_MAX_SIZE 8 +#define SCAN_BSSID_MAX_SIZE 16 + +/** + * struct iwl_ssid_ie - directed scan network information element + * + * Up to 20 of these may appear in REPLY_SCAN_CMD, + * selected by "type" bit field in struct iwl_scan_channel; + * each channel may select different ssids from among the 20 entries. + * SSID IEs get transmitted in reverse order of entry. + * + * @id: element ID + * @len: element length + * @ssid: element (SSID) data + */ +struct iwl_ssid_ie { + u8 id; + u8 len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +} __packed; /* SCAN_DIRECT_SSID_IE_API_S_VER_1 */ + +/* scan offload */ +#define IWL_SCAN_MAX_BLACKLIST_LEN 64 +#define IWL_SCAN_SHORT_BLACKLIST_LEN 16 +#define IWL_SCAN_MAX_PROFILES 11 +#define IWL_SCAN_MAX_PROFILES_V2 8 +#define SCAN_OFFLOAD_PROBE_REQ_SIZE 512 +#define SCAN_NUM_BAND_PROBE_DATA_V_1 2 +#define SCAN_NUM_BAND_PROBE_DATA_V_2 3 + +/* Default watchdog (in MS) for scheduled scan iteration */ +#define IWL_SCHED_SCAN_WATCHDOG cpu_to_le16(15000) + +#define IWL_GOOD_CRC_TH_DEFAULT cpu_to_le16(1) +#define CAN_ABORT_STATUS 1 + +#define IWL_FULL_SCAN_MULTIPLIER 5 +#define IWL_FAST_SCHED_SCAN_ITERATIONS 3 +#define IWL_MAX_SCHED_SCAN_PLANS 2 + +enum scan_framework_client { + SCAN_CLIENT_SCHED_SCAN = BIT(0), + SCAN_CLIENT_NETDETECT = BIT(1), + SCAN_CLIENT_ASSET_TRACKING = BIT(2), +}; + +/** + * struct iwl_scan_offload_blocklist - SCAN_OFFLOAD_BLACKLIST_S + * @ssid: MAC address to filter out + * @reported_rssi: AP rssi reported to the host + * @client_bitmap: clients ignore this entry - enum scan_framework_client + */ +struct iwl_scan_offload_blocklist { + u8 ssid[ETH_ALEN]; + u8 reported_rssi; + u8 client_bitmap; +} __packed; + +enum iwl_scan_offload_network_type { + IWL_NETWORK_TYPE_BSS = 1, + IWL_NETWORK_TYPE_IBSS = 2, + IWL_NETWORK_TYPE_ANY = 3, +}; + +enum iwl_scan_offload_band_selection { + IWL_SCAN_OFFLOAD_SELECT_2_4 = 0x4, + IWL_SCAN_OFFLOAD_SELECT_5_2 = 0x8, + IWL_SCAN_OFFLOAD_SELECT_ANY = 0xc, +}; + +/** + * struct iwl_scan_offload_profile - SCAN_OFFLOAD_PROFILE_S + * @ssid_index: index to ssid list in fixed part + * @unicast_cipher: encryption algorithm to match - bitmap + * @auth_alg: authentication algorithm to match - bitmap + * @network_type: enum iwl_scan_offload_network_type + * @band_selection: enum iwl_scan_offload_band_selection + * @client_bitmap: clients waiting for match - enum scan_framework_client + * @reserved: reserved + */ +struct iwl_scan_offload_profile { + u8 ssid_index; + u8 unicast_cipher; + u8 auth_alg; + u8 network_type; + u8 band_selection; + u8 client_bitmap; + u8 reserved[2]; +} __packed; + +/** + * struct iwl_scan_offload_profile_cfg_data + * @blocklist_len: length of blocklist + * @num_profiles: num of profiles in the list + * @match_notify: clients waiting for match found notification + * @pass_match: clients waiting for the results + * @active_clients: active clients bitmap - enum scan_framework_client + * @any_beacon_notify: clients waiting for match notification without match + * @reserved: reserved + */ +struct iwl_scan_offload_profile_cfg_data { + u8 blocklist_len; + u8 num_profiles; + u8 match_notify; + u8 pass_match; + u8 active_clients; + u8 any_beacon_notify; + u8 reserved[2]; +} __packed; + +/** + * struct iwl_scan_offload_profile_cfg + * @profiles: profiles to search for match + * @data: the rest of the data for profile_cfg + */ +struct iwl_scan_offload_profile_cfg_v1 { + struct iwl_scan_offload_profile profiles[IWL_SCAN_MAX_PROFILES]; + struct iwl_scan_offload_profile_cfg_data data; +} __packed; /* SCAN_OFFLOAD_PROFILES_CFG_API_S_VER_1-2*/ + +/** + * struct iwl_scan_offload_profile_cfg + * @profiles: profiles to search for match + * @data: the rest of the data for profile_cfg + */ +struct iwl_scan_offload_profile_cfg { + struct iwl_scan_offload_profile profiles[IWL_SCAN_MAX_PROFILES_V2]; + struct iwl_scan_offload_profile_cfg_data data; +} __packed; /* SCAN_OFFLOAD_PROFILES_CFG_API_S_VER_3*/ + +/** + * struct iwl_scan_schedule_lmac - schedule of scan offload + * @delay: delay between iterations, in seconds. + * @iterations: num of scan iterations + * @full_scan_mul: number of partial scans before each full scan + */ +struct iwl_scan_schedule_lmac { + __le16 delay; + u8 iterations; + u8 full_scan_mul; +} __packed; /* SCAN_SCHEDULE_API_S */ + +enum iwl_scan_offload_complete_status { + IWL_SCAN_OFFLOAD_COMPLETED = 1, + IWL_SCAN_OFFLOAD_ABORTED = 2, +}; + +enum iwl_scan_ebs_status { + IWL_SCAN_EBS_SUCCESS, + IWL_SCAN_EBS_FAILED, + IWL_SCAN_EBS_CHAN_NOT_FOUND, + IWL_SCAN_EBS_INACTIVE, +}; + +/** + * struct iwl_scan_req_tx_cmd - SCAN_REQ_TX_CMD_API_S + * @tx_flags: combination of TX_CMD_FLG_* + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + * cleared. Combination of RATE_MCS_* + * @sta_id: index of destination station in FW station table + * @reserved: for alignment and future use + */ +struct iwl_scan_req_tx_cmd { + __le32 tx_flags; + __le32 rate_n_flags; + u8 sta_id; + u8 reserved[3]; +} __packed; + +enum iwl_scan_channel_flags_lmac { + IWL_UNIFIED_SCAN_CHANNEL_FULL = BIT(27), + IWL_UNIFIED_SCAN_CHANNEL_PARTIAL = BIT(28), +}; + +/** + * struct iwl_scan_channel_cfg_lmac - SCAN_CHANNEL_CFG_S_VER2 + * @flags: bits 1-20: directed scan to i'th ssid + * other bits &enum iwl_scan_channel_flags_lmac + * @channel_num: channel number 1-13 etc + * @iter_count: scan iteration on this channel + * @iter_interval: interval in seconds between iterations on one channel + */ +struct iwl_scan_channel_cfg_lmac { + __le32 flags; + __le16 channel_num; + __le16 iter_count; + __le32 iter_interval; +} __packed; + +/* + * struct iwl_scan_probe_segment - PROBE_SEGMENT_API_S_VER_1 + * @offset: offset in the data block + * @len: length of the segment + */ +struct iwl_scan_probe_segment { + __le16 offset; + __le16 len; +} __packed; + +/* iwl_scan_probe_req - PROBE_REQUEST_FRAME_API_S_VER_2 + * @mac_header: first (and common) part of the probe + * @band_data: band specific data + * @common_data: last (and common) part of the probe + * @buf: raw data block + */ +struct iwl_scan_probe_req_v1 { + struct iwl_scan_probe_segment mac_header; + struct iwl_scan_probe_segment band_data[SCAN_NUM_BAND_PROBE_DATA_V_1]; + struct iwl_scan_probe_segment common_data; + u8 buf[SCAN_OFFLOAD_PROBE_REQ_SIZE]; +} __packed; + +/* iwl_scan_probe_req - PROBE_REQUEST_FRAME_API_S_VER_v2 + * @mac_header: first (and common) part of the probe + * @band_data: band specific data + * @common_data: last (and common) part of the probe + * @buf: raw data block + */ +struct iwl_scan_probe_req { + struct iwl_scan_probe_segment mac_header; + struct iwl_scan_probe_segment band_data[SCAN_NUM_BAND_PROBE_DATA_V_2]; + struct iwl_scan_probe_segment common_data; + u8 buf[SCAN_OFFLOAD_PROBE_REQ_SIZE]; +} __packed; + +enum iwl_scan_channel_flags { + IWL_SCAN_CHANNEL_FLAG_EBS = BIT(0), + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE = BIT(1), + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD = BIT(2), + IWL_SCAN_CHANNEL_FLAG_EBS_FRAG = BIT(3), + IWL_SCAN_CHANNEL_FLAG_FORCE_EBS = BIT(4), + IWL_SCAN_CHANNEL_FLAG_ENABLE_CHAN_ORDER = BIT(5), + IWL_SCAN_CHANNEL_FLAG_6G_PSC_NO_FILTER = BIT(6), +}; + +/* struct iwl_scan_channel_opt - CHANNEL_OPTIMIZATION_API_S + * @flags: enum iwl_scan_channel_flags + * @non_ebs_ratio: defines the ratio of number of scan iterations where EBS is + * involved. + * 1 - EBS is disabled. + * 2 - every second scan will be full scan(and so on). + */ +struct iwl_scan_channel_opt { + __le16 flags; + __le16 non_ebs_ratio; +} __packed; + +/** + * enum iwl_mvm_lmac_scan_flags - LMAC scan flags + * @IWL_MVM_LMAC_SCAN_FLAG_PASS_ALL: pass all beacons and probe responses + * without filtering. + * @IWL_MVM_LMAC_SCAN_FLAG_PASSIVE: force passive scan on all channels + * @IWL_MVM_LMAC_SCAN_FLAG_PRE_CONNECTION: single channel scan + * @IWL_MVM_LMAC_SCAN_FLAG_ITER_COMPLETE: send iteration complete notification + * @IWL_MVM_LMAC_SCAN_FLAG_MULTIPLE_SSIDS: multiple SSID matching + * @IWL_MVM_LMAC_SCAN_FLAG_FRAGMENTED: all passive scans will be fragmented + * @IWL_MVM_LMAC_SCAN_FLAGS_RRM_ENABLED: insert WFA vendor-specific TPC report + * and DS parameter set IEs into probe requests. + * @IWL_MVM_LMAC_SCAN_FLAG_EXTENDED_DWELL: use extended dwell time on channels + * 1, 6 and 11. + * @IWL_MVM_LMAC_SCAN_FLAG_MATCH: Send match found notification on matches + */ +enum iwl_mvm_lmac_scan_flags { + IWL_MVM_LMAC_SCAN_FLAG_PASS_ALL = BIT(0), + IWL_MVM_LMAC_SCAN_FLAG_PASSIVE = BIT(1), + IWL_MVM_LMAC_SCAN_FLAG_PRE_CONNECTION = BIT(2), + IWL_MVM_LMAC_SCAN_FLAG_ITER_COMPLETE = BIT(3), + IWL_MVM_LMAC_SCAN_FLAG_MULTIPLE_SSIDS = BIT(4), + IWL_MVM_LMAC_SCAN_FLAG_FRAGMENTED = BIT(5), + IWL_MVM_LMAC_SCAN_FLAGS_RRM_ENABLED = BIT(6), + IWL_MVM_LMAC_SCAN_FLAG_EXTENDED_DWELL = BIT(7), + IWL_MVM_LMAC_SCAN_FLAG_MATCH = BIT(9), +}; + +enum iwl_scan_priority { + IWL_SCAN_PRIORITY_LOW, + IWL_SCAN_PRIORITY_MEDIUM, + IWL_SCAN_PRIORITY_HIGH, +}; + +enum iwl_scan_priority_ext { + IWL_SCAN_PRIORITY_EXT_0_LOWEST, + IWL_SCAN_PRIORITY_EXT_1, + IWL_SCAN_PRIORITY_EXT_2, + IWL_SCAN_PRIORITY_EXT_3, + IWL_SCAN_PRIORITY_EXT_4, + IWL_SCAN_PRIORITY_EXT_5, + IWL_SCAN_PRIORITY_EXT_6, + IWL_SCAN_PRIORITY_EXT_7_HIGHEST, +}; + +/** + * struct iwl_scan_req_lmac - SCAN_REQUEST_CMD_API_S_VER_1 + * @reserved1: for alignment and future use + * @n_channels: num of channels to scan + * @active_dwell: dwell time for active channels + * @passive_dwell: dwell time for passive channels + * @fragmented_dwell: dwell time for fragmented passive scan + * @extended_dwell: dwell time for channels 1, 6 and 11 (in certain cases) + * @reserved2: for alignment and future use + * @rx_chain_select: PHY_RX_CHAIN_* flags + * @scan_flags: &enum iwl_mvm_lmac_scan_flags + * @max_out_time: max time (in TU) to be out of associated channel + * @suspend_time: pause scan this long (TUs) when returning to service channel + * @flags: RXON flags + * @filter_flags: RXON filter + * @tx_cmd: tx command for active scan; for 2GHz and for 5GHz + * @direct_scan: list of SSIDs for directed active scan + * @scan_prio: enum iwl_scan_priority + * @iter_num: number of scan iterations + * @delay: delay in seconds before first iteration + * @schedule: two scheduling plans. The first one is finite, the second one can + * be infinite. + * @channel_opt: channel optimization options, for full and partial scan + * @data: channel configuration and probe request packet. + */ +struct iwl_scan_req_lmac { + /* SCAN_REQUEST_FIXED_PART_API_S_VER_7 */ + __le32 reserved1; + u8 n_channels; + u8 active_dwell; + u8 passive_dwell; + u8 fragmented_dwell; + u8 extended_dwell; + u8 reserved2; + __le16 rx_chain_select; + __le32 scan_flags; + __le32 max_out_time; + __le32 suspend_time; + /* RX_ON_FLAGS_API_S_VER_1 */ + __le32 flags; + __le32 filter_flags; + struct iwl_scan_req_tx_cmd tx_cmd[2]; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + __le32 scan_prio; + /* SCAN_REQ_PERIODIC_PARAMS_API_S */ + __le32 iter_num; + __le32 delay; + struct iwl_scan_schedule_lmac schedule[IWL_MAX_SCHED_SCAN_PLANS]; + struct iwl_scan_channel_opt channel_opt[2]; + u8 data[]; +} __packed; + +/** + * struct iwl_scan_results_notif - scan results for one channel - + * SCAN_RESULT_NTF_API_S_VER_3 + * @channel: which channel the results are from + * @band: 0 for 5.2 GHz, 1 for 2.4 GHz + * @probe_status: SCAN_PROBE_STATUS_*, indicates success of probe request + * @num_probe_not_sent: # of request that weren't sent due to not enough time + * @duration: duration spent in channel, in usecs + */ +struct iwl_scan_results_notif { + u8 channel; + u8 band; + u8 probe_status; + u8 num_probe_not_sent; + __le32 duration; +} __packed; + +/** + * struct iwl_lmac_scan_complete_notif - notifies end of scanning (all channels) + * SCAN_COMPLETE_NTF_API_S_VER_3 + * @scanned_channels: number of channels scanned (and number of valid results) + * @status: one of SCAN_COMP_STATUS_* + * @bt_status: BT on/off status + * @last_channel: last channel that was scanned + * @tsf_low: TSF timer (lower half) in usecs + * @tsf_high: TSF timer (higher half) in usecs + * @results: an array of scan results, only "scanned_channels" of them are valid + */ +struct iwl_lmac_scan_complete_notif { + u8 scanned_channels; + u8 status; + u8 bt_status; + u8 last_channel; + __le32 tsf_low; + __le32 tsf_high; + struct iwl_scan_results_notif results[]; +} __packed; + +/** + * struct iwl_scan_offload_complete - PERIODIC_SCAN_COMPLETE_NTF_API_S_VER_2 + * @last_schedule_line: last schedule line executed (fast or regular) + * @last_schedule_iteration: last scan iteration executed before scan abort + * @status: &enum iwl_scan_offload_complete_status + * @ebs_status: EBS success status &enum iwl_scan_ebs_status + * @time_after_last_iter: time in seconds elapsed after last iteration + * @reserved: reserved + */ +struct iwl_periodic_scan_complete { + u8 last_schedule_line; + u8 last_schedule_iteration; + u8 status; + u8 ebs_status; + __le32 time_after_last_iter; + __le32 reserved; +} __packed; + +/* UMAC Scan API */ + +/* The maximum of either of these cannot exceed 8, because we use an + * 8-bit mask (see IWL_MVM_SCAN_MASK in mvm.h). + */ +#define IWL_MVM_MAX_UMAC_SCANS 4 +#define IWL_MVM_MAX_LMAC_SCANS 1 + +enum scan_config_flags { + SCAN_CONFIG_FLAG_ACTIVATE = BIT(0), + SCAN_CONFIG_FLAG_DEACTIVATE = BIT(1), + SCAN_CONFIG_FLAG_FORBID_CHUB_REQS = BIT(2), + SCAN_CONFIG_FLAG_ALLOW_CHUB_REQS = BIT(3), + SCAN_CONFIG_FLAG_SET_TX_CHAINS = BIT(8), + SCAN_CONFIG_FLAG_SET_RX_CHAINS = BIT(9), + SCAN_CONFIG_FLAG_SET_AUX_STA_ID = BIT(10), + SCAN_CONFIG_FLAG_SET_ALL_TIMES = BIT(11), + SCAN_CONFIG_FLAG_SET_EFFECTIVE_TIMES = BIT(12), + SCAN_CONFIG_FLAG_SET_CHANNEL_FLAGS = BIT(13), + SCAN_CONFIG_FLAG_SET_LEGACY_RATES = BIT(14), + SCAN_CONFIG_FLAG_SET_MAC_ADDR = BIT(15), + SCAN_CONFIG_FLAG_SET_FRAGMENTED = BIT(16), + SCAN_CONFIG_FLAG_CLEAR_FRAGMENTED = BIT(17), + SCAN_CONFIG_FLAG_SET_CAM_MODE = BIT(18), + SCAN_CONFIG_FLAG_CLEAR_CAM_MODE = BIT(19), + SCAN_CONFIG_FLAG_SET_PROMISC_MODE = BIT(20), + SCAN_CONFIG_FLAG_CLEAR_PROMISC_MODE = BIT(21), + SCAN_CONFIG_FLAG_SET_LMAC2_FRAGMENTED = BIT(22), + SCAN_CONFIG_FLAG_CLEAR_LMAC2_FRAGMENTED = BIT(23), + + /* Bits 26-31 are for num of channels in channel_array */ +#define SCAN_CONFIG_N_CHANNELS(n) ((n) << 26) +}; + +enum scan_config_rates { + /* OFDM basic rates */ + SCAN_CONFIG_RATE_6M = BIT(0), + SCAN_CONFIG_RATE_9M = BIT(1), + SCAN_CONFIG_RATE_12M = BIT(2), + SCAN_CONFIG_RATE_18M = BIT(3), + SCAN_CONFIG_RATE_24M = BIT(4), + SCAN_CONFIG_RATE_36M = BIT(5), + SCAN_CONFIG_RATE_48M = BIT(6), + SCAN_CONFIG_RATE_54M = BIT(7), + /* CCK basic rates */ + SCAN_CONFIG_RATE_1M = BIT(8), + SCAN_CONFIG_RATE_2M = BIT(9), + SCAN_CONFIG_RATE_5M = BIT(10), + SCAN_CONFIG_RATE_11M = BIT(11), + + /* Bits 16-27 are for supported rates */ +#define SCAN_CONFIG_SUPPORTED_RATE(rate) ((rate) << 16) +}; + +enum iwl_channel_flags { + IWL_CHANNEL_FLAG_EBS = BIT(0), + IWL_CHANNEL_FLAG_ACCURATE_EBS = BIT(1), + IWL_CHANNEL_FLAG_EBS_ADD = BIT(2), + IWL_CHANNEL_FLAG_PRE_SCAN_PASSIVE2ACTIVE = BIT(3), +}; + +enum iwl_uhb_chan_cfg_flags { + IWL_UHB_CHAN_CFG_FLAG_UNSOLICITED_PROBE_RES = BIT(24), + IWL_UHB_CHAN_CFG_FLAG_PSC_CHAN_NO_LISTEN = BIT(25), + IWL_UHB_CHAN_CFG_FLAG_FORCE_PASSIVE = BIT(26), +}; +/** + * struct iwl_scan_dwell + * @active: default dwell time for active scan + * @passive: default dwell time for passive scan + * @fragmented: default dwell time for fragmented scan + * @extended: default dwell time for channels 1, 6 and 11 + */ +struct iwl_scan_dwell { + u8 active; + u8 passive; + u8 fragmented; + u8 extended; +} __packed; + +/** + * struct iwl_scan_config_v1 + * @flags: enum scan_config_flags + * @tx_chains: valid_tx antenna - ANT_* definitions + * @rx_chains: valid_rx antenna - ANT_* definitions + * @legacy_rates: default legacy rates - enum scan_config_rates + * @out_of_channel_time: default max out of serving channel time + * @suspend_time: default max suspend time + * @dwell: dwells for the scan + * @mac_addr: default mac address to be used in probes + * @bcast_sta_id: the index of the station in the fw + * @channel_flags: default channel flags - enum iwl_channel_flags + * scan_config_channel_flag + * @channel_array: default supported channels + */ +struct iwl_scan_config_v1 { + __le32 flags; + __le32 tx_chains; + __le32 rx_chains; + __le32 legacy_rates; + __le32 out_of_channel_time; + __le32 suspend_time; + struct iwl_scan_dwell dwell; + u8 mac_addr[ETH_ALEN]; + u8 bcast_sta_id; + u8 channel_flags; + u8 channel_array[]; +} __packed; /* SCAN_CONFIG_DB_CMD_API_S */ + +#define SCAN_TWO_LMACS 2 +#define SCAN_LB_LMAC_IDX 0 +#define SCAN_HB_LMAC_IDX 1 + +struct iwl_scan_config_v2 { + __le32 flags; + __le32 tx_chains; + __le32 rx_chains; + __le32 legacy_rates; + __le32 out_of_channel_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + struct iwl_scan_dwell dwell; + u8 mac_addr[ETH_ALEN]; + u8 bcast_sta_id; + u8 channel_flags; + u8 channel_array[]; +} __packed; /* SCAN_CONFIG_DB_CMD_API_S_2 */ + +/** + * struct iwl_scan_config + * @enable_cam_mode: whether to enable CAM mode. + * @enable_promiscouos_mode: whether to enable promiscouos mode + * @bcast_sta_id: the index of the station in the fw. Deprecated starting with + * API version 5. + * @reserved: reserved + * @tx_chains: valid_tx antenna - ANT_* definitions + * @rx_chains: valid_rx antenna - ANT_* definitions + */ +struct iwl_scan_config { + u8 enable_cam_mode; + u8 enable_promiscouos_mode; + u8 bcast_sta_id; + u8 reserved; + __le32 tx_chains; + __le32 rx_chains; +} __packed; /* SCAN_CONFIG_DB_CMD_API_S_5 */ + +/** + * enum iwl_umac_scan_flags - UMAC scan flags + * @IWL_UMAC_SCAN_FLAG_PREEMPTIVE: scan process triggered by this scan request + * can be preempted by other scan requests with higher priority. + * The low priority scan will be resumed when the higher proirity scan is + * completed. + * @IWL_UMAC_SCAN_FLAG_START_NOTIF: notification will be sent to the driver + * when scan starts. + */ +enum iwl_umac_scan_flags { + IWL_UMAC_SCAN_FLAG_PREEMPTIVE = BIT(0), + IWL_UMAC_SCAN_FLAG_START_NOTIF = BIT(1), +}; + +enum iwl_umac_scan_uid_offsets { + IWL_UMAC_SCAN_UID_TYPE_OFFSET = 0, + IWL_UMAC_SCAN_UID_SEQ_OFFSET = 8, +}; + +enum iwl_umac_scan_general_flags { + IWL_UMAC_SCAN_GEN_FLAGS_PERIODIC = BIT(0), + IWL_UMAC_SCAN_GEN_FLAGS_OVER_BT = BIT(1), + IWL_UMAC_SCAN_GEN_FLAGS_PASS_ALL = BIT(2), + IWL_UMAC_SCAN_GEN_FLAGS_PASSIVE = BIT(3), + IWL_UMAC_SCAN_GEN_FLAGS_PRE_CONNECT = BIT(4), + IWL_UMAC_SCAN_GEN_FLAGS_ITER_COMPLETE = BIT(5), + IWL_UMAC_SCAN_GEN_FLAGS_MULTIPLE_SSID = BIT(6), + IWL_UMAC_SCAN_GEN_FLAGS_FRAGMENTED = BIT(7), + IWL_UMAC_SCAN_GEN_FLAGS_RRM_ENABLED = BIT(8), + IWL_UMAC_SCAN_GEN_FLAGS_MATCH = BIT(9), + IWL_UMAC_SCAN_GEN_FLAGS_EXTENDED_DWELL = BIT(10), + /* Extended dwell is obselete when adaptive dwell is used, making this + * bit reusable. Hence, probe request defer is used only when adaptive + * dwell is supported. */ + IWL_UMAC_SCAN_GEN_FLAGS_PROB_REQ_DEFER_SUPP = BIT(10), + IWL_UMAC_SCAN_GEN_FLAGS_LMAC2_FRAGMENTED = BIT(11), + IWL_UMAC_SCAN_GEN_FLAGS_ADAPTIVE_DWELL = BIT(13), + IWL_UMAC_SCAN_GEN_FLAGS_MAX_CHNL_TIME = BIT(14), + IWL_UMAC_SCAN_GEN_FLAGS_PROB_REQ_HIGH_TX_RATE = BIT(15), +}; + +/** + * enum iwl_umac_scan_general_flags2 - UMAC scan general flags #2 + * @IWL_UMAC_SCAN_GEN_FLAGS2_NOTIF_PER_CHNL: Whether to send a complete + * notification per channel or not. + * @IWL_UMAC_SCAN_GEN_FLAGS2_ALLOW_CHNL_REORDER: Whether to allow channel + * reorder optimization or not. + */ +enum iwl_umac_scan_general_flags2 { + IWL_UMAC_SCAN_GEN_FLAGS2_NOTIF_PER_CHNL = BIT(0), + IWL_UMAC_SCAN_GEN_FLAGS2_ALLOW_CHNL_REORDER = BIT(1), +}; + +/** + * enum iwl_umac_scan_general_flags_v2 - UMAC scan general flags version 2 + * + * The FW flags were reordered and hence the driver introduce version 2 + * + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_PERIODIC: periodic or scheduled + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_PASS_ALL: pass all probe responses and beacons + * during scan iterations + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_NTFY_ITER_COMPLETE: send complete notification + * on every iteration instead of only once after the last iteration + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC1: fragmented scan LMAC1 + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC2: fragmented scan LMAC2 + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_MATCH: does this scan check for profile matching + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_USE_ALL_RX_CHAINS: use all valid chains for RX + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_ADAPTIVE_DWELL: works with adaptive dwell + * for active channel + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_PREEMPTIVE: can be preempted by other requests + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_NTF_START: send notification of scan start + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_MULTI_SSID: matching on multiple SSIDs + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE: all the channels scanned + * as passive + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_TRIGGER_UHB_SCAN: at the end of 2.4GHz and + * 5.2Ghz bands scan, trigger scan on 6GHz band to discover + * the reported collocated APs + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN: at the end of 2.4GHz and 5GHz + * bands scan, if not APs were discovered, allow scan to conitnue and scan + * 6GHz PSC channels in order to discover country information. + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN_FILTER_IN: in case + * &IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN is enabled and scan is + * activated over 6GHz PSC channels, filter in beacons and probe responses. + */ +enum iwl_umac_scan_general_flags_v2 { + IWL_UMAC_SCAN_GEN_FLAGS_V2_PERIODIC = BIT(0), + IWL_UMAC_SCAN_GEN_FLAGS_V2_PASS_ALL = BIT(1), + IWL_UMAC_SCAN_GEN_FLAGS_V2_NTFY_ITER_COMPLETE = BIT(2), + IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC1 = BIT(3), + IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC2 = BIT(4), + IWL_UMAC_SCAN_GEN_FLAGS_V2_MATCH = BIT(5), + IWL_UMAC_SCAN_GEN_FLAGS_V2_USE_ALL_RX_CHAINS = BIT(6), + IWL_UMAC_SCAN_GEN_FLAGS_V2_ADAPTIVE_DWELL = BIT(7), + IWL_UMAC_SCAN_GEN_FLAGS_V2_PREEMPTIVE = BIT(8), + IWL_UMAC_SCAN_GEN_FLAGS_V2_NTF_START = BIT(9), + IWL_UMAC_SCAN_GEN_FLAGS_V2_MULTI_SSID = BIT(10), + IWL_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE = BIT(11), + IWL_UMAC_SCAN_GEN_FLAGS_V2_TRIGGER_UHB_SCAN = BIT(12), + IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN = BIT(13), + IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN_FILTER_IN = BIT(14), +}; + +/** + * struct iwl_scan_channel_cfg_umac + * @flags: bitmap - 0-19: directed scan to i'th ssid. + * @channel_num: channel number 1-13 etc. + * @band: band of channel: 0 for 2GHz, 1 for 5GHz + * @iter_count: repetition count for the channel. + * @iter_interval: interval between two scan iterations on one channel. + */ +struct iwl_scan_channel_cfg_umac { + __le32 flags; + /* Both versions are of the same size, so use a union without adjusting + * the command size later + */ + union { + struct { + u8 channel_num; + u8 iter_count; + __le16 iter_interval; + } v1; /* SCAN_CHANNEL_CONFIG_API_S_VER_1 */ + struct { + u8 channel_num; + u8 band; + u8 iter_count; + u8 iter_interval; + } v2; /* SCAN_CHANNEL_CONFIG_API_S_VER_2 + * SCAN_CHANNEL_CONFIG_API_S_VER_3 + * SCAN_CHANNEL_CONFIG_API_S_VER_4 + */ + }; +} __packed; + +/** + * struct iwl_scan_umac_schedule + * @interval: interval in seconds between scan iterations + * @iter_count: num of scan iterations for schedule plan, 0xff for infinite loop + * @reserved: for alignment and future use + */ +struct iwl_scan_umac_schedule { + __le16 interval; + u8 iter_count; + u8 reserved; +} __packed; /* SCAN_SCHED_PARAM_API_S_VER_1 */ + +struct iwl_scan_req_umac_tail_v1 { + /* SCAN_PERIODIC_PARAMS_API_S_VER_1 */ + struct iwl_scan_umac_schedule schedule[IWL_MAX_SCHED_SCAN_PLANS]; + __le16 delay; + __le16 reserved; + /* SCAN_PROBE_PARAMS_API_S_VER_1 */ + struct iwl_scan_probe_req_v1 preq; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; +} __packed; + +/** + * struct iwl_scan_req_umac_tail - the rest of the UMAC scan request command + * parameters following channels configuration array. + * @schedule: two scheduling plans. + * @delay: delay in TUs before starting the first scan iteration + * @reserved: for future use and alignment + * @preq: probe request with IEs blocks + * @direct_scan: list of SSIDs for directed active scan + */ +struct iwl_scan_req_umac_tail_v2 { + /* SCAN_PERIODIC_PARAMS_API_S_VER_1 */ + struct iwl_scan_umac_schedule schedule[IWL_MAX_SCHED_SCAN_PLANS]; + __le16 delay; + __le16 reserved; + /* SCAN_PROBE_PARAMS_API_S_VER_2 */ + struct iwl_scan_probe_req preq; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; +} __packed; + +/** + * struct iwl_scan_umac_chan_param + * @flags: channel flags &enum iwl_scan_channel_flags + * @count: num of channels in scan request + * @reserved: for future use and alignment + */ +struct iwl_scan_umac_chan_param { + u8 flags; + u8 count; + __le16 reserved; +} __packed; /*SCAN_CHANNEL_PARAMS_API_S_VER_1 */ + +/** + * struct iwl_scan_req_umac + * @flags: &enum iwl_umac_scan_flags + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @ooc_priority: out of channel priority - &enum iwl_scan_priority + * @general_flags: &enum iwl_umac_scan_general_flags + * @scan_start_mac_id: report the scan start TSF time according to this mac TSF + * @extended_dwell: dwell time for channels 1, 6 and 11 + * @active_dwell: dwell time for active scan per LMAC + * @passive_dwell: dwell time for passive scan per LMAC + * @fragmented_dwell: dwell time for fragmented passive scan + * @adwell_default_n_aps: for adaptive dwell the default number of APs + * per channel + * @adwell_default_n_aps_social: for adaptive dwell the default + * number of APs per social (1,6,11) channel + * @general_flags2: &enum iwl_umac_scan_general_flags2 + * @adwell_max_budget: for adaptive dwell the maximal budget of TU to be added + * to total scan time + * @max_out_time: max out of serving channel time, per LMAC - for CDB there + * are 2 LMACs + * @suspend_time: max suspend time, per LMAC - for CDB there are 2 LMACs + * @scan_priority: scan internal prioritization &enum iwl_scan_priority + * @num_of_fragments: Number of fragments needed for full coverage per band. + * Relevant only for fragmented scan. + * @channel: &struct iwl_scan_umac_chan_param + * @reserved: for future use and alignment + * @reserved3: for future use and alignment + * @data: &struct iwl_scan_channel_cfg_umac and + * &struct iwl_scan_req_umac_tail + */ +struct iwl_scan_req_umac { + __le32 flags; + __le32 uid; + __le32 ooc_priority; + __le16 general_flags; + u8 reserved; + u8 scan_start_mac_id; + union { + struct { + u8 extended_dwell; + u8 active_dwell; + u8 passive_dwell; + u8 fragmented_dwell; + __le32 max_out_time; + __le32 suspend_time; + __le32 scan_priority; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v1; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_1 */ + struct { + u8 extended_dwell; + u8 active_dwell; + u8 passive_dwell; + u8 fragmented_dwell; + __le32 max_out_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v6; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_6 */ + struct { + u8 active_dwell; + u8 passive_dwell; + u8 fragmented_dwell; + u8 adwell_default_n_aps; + u8 adwell_default_n_aps_social; + u8 reserved3; + __le16 adwell_max_budget; + __le32 max_out_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v7; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_7 */ + struct { + u8 active_dwell[SCAN_TWO_LMACS]; + u8 reserved2; + u8 adwell_default_n_aps; + u8 adwell_default_n_aps_social; + u8 general_flags2; + __le16 adwell_max_budget; + __le32 max_out_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + u8 passive_dwell[SCAN_TWO_LMACS]; + u8 num_of_fragments[SCAN_TWO_LMACS]; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v8; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_8 */ + struct { + u8 active_dwell[SCAN_TWO_LMACS]; + u8 adwell_default_hb_n_aps; + u8 adwell_default_lb_n_aps; + u8 adwell_default_n_aps_social; + u8 general_flags2; + __le16 adwell_max_budget; + __le32 max_out_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + u8 passive_dwell[SCAN_TWO_LMACS]; + u8 num_of_fragments[SCAN_TWO_LMACS]; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v9; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_9 */ + }; +} __packed; + +#define IWL_SCAN_REQ_UMAC_SIZE_V8 sizeof(struct iwl_scan_req_umac) +#define IWL_SCAN_REQ_UMAC_SIZE_V7 48 +#define IWL_SCAN_REQ_UMAC_SIZE_V6 44 +#define IWL_SCAN_REQ_UMAC_SIZE_V1 36 + +/** + * struct iwl_scan_probe_params_v3 + * @preq: scan probe request params + * @ssid_num: number of valid SSIDs in direct scan array + * @short_ssid_num: number of valid short SSIDs in short ssid array + * @bssid_num: number of valid bssid in bssids array + * @reserved: reserved + * @direct_scan: list of ssids + * @short_ssid: array of short ssids + * @bssid_array: array of bssids + */ +struct iwl_scan_probe_params_v3 { + struct iwl_scan_probe_req preq; + u8 ssid_num; + u8 short_ssid_num; + u8 bssid_num; + u8 reserved; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + __le32 short_ssid[SCAN_SHORT_SSID_MAX_SIZE]; + u8 bssid_array[SCAN_BSSID_MAX_SIZE][ETH_ALEN]; +} __packed; /* SCAN_PROBE_PARAMS_API_S_VER_3 */ + +/** + * struct iwl_scan_probe_params_v4 + * @preq: scan probe request params + * @short_ssid_num: number of valid short SSIDs in short ssid array + * @bssid_num: number of valid bssid in bssids array + * @reserved: reserved + * @direct_scan: list of ssids + * @short_ssid: array of short ssids + * @bssid_array: array of bssids + */ +struct iwl_scan_probe_params_v4 { + struct iwl_scan_probe_req preq; + u8 short_ssid_num; + u8 bssid_num; + __le16 reserved; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + __le32 short_ssid[SCAN_SHORT_SSID_MAX_SIZE]; + u8 bssid_array[SCAN_BSSID_MAX_SIZE][ETH_ALEN]; +} __packed; /* SCAN_PROBE_PARAMS_API_S_VER_4 */ + +#define SCAN_MAX_NUM_CHANS_V3 67 + +/** + * struct iwl_scan_channel_params_v4 + * @flags: channel flags &enum iwl_scan_channel_flags + * @count: num of channels in scan request + * @num_of_aps_override: override the number of APs the FW uses to calculate + * dwell time when adaptive dwell is used + * @reserved: for future use and alignment + * @channel_config: array of explicit channel configurations + * for 2.4Ghz and 5.2Ghz bands + * @adwell_ch_override_bitmap: when using adaptive dwell, override the number + * of APs value with &num_of_aps_override for the channel. + * To cast channel to index, use &iwl_mvm_scan_ch_and_band_to_idx + */ +struct iwl_scan_channel_params_v4 { + u8 flags; + u8 count; + u8 num_of_aps_override; + u8 reserved; + struct iwl_scan_channel_cfg_umac channel_config[SCAN_MAX_NUM_CHANS_V3]; + u8 adwell_ch_override_bitmap[16]; +} __packed; /* SCAN_CHANNEL_PARAMS_API_S_VER_4 also + SCAN_CHANNEL_PARAMS_API_S_VER_5 */ + +/** + * struct iwl_scan_channel_params_v6 + * @flags: channel flags &enum iwl_scan_channel_flags + * @count: num of channels in scan request + * @n_aps_override: override the number of APs the FW uses to calculate dwell + * time when adaptive dwell is used. + * Channel k will use n_aps_override[i] when BIT(20 + i) is set in + * channel_config[k].flags + * @channel_config: array of explicit channel configurations + * for 2.4Ghz and 5.2Ghz bands + */ +struct iwl_scan_channel_params_v6 { + u8 flags; + u8 count; + u8 n_aps_override[2]; + struct iwl_scan_channel_cfg_umac channel_config[SCAN_MAX_NUM_CHANS_V3]; +} __packed; /* SCAN_CHANNEL_PARAMS_API_S_VER_6 */ + +/** + * struct iwl_scan_general_params_v10 + * @flags: &enum iwl_umac_scan_flags + * @reserved: reserved for future + * @scan_start_mac_id: report the scan start TSF time according to this mac TSF + * @active_dwell: dwell time for active scan per LMAC + * @adwell_default_2g: adaptive dwell default number of APs + * for 2.4GHz channel + * @adwell_default_5g: adaptive dwell default number of APs + * for 5GHz channels + * @adwell_default_social_chn: adaptive dwell default number of + * APs per social channel + * @reserved1: reserved for future + * @adwell_max_budget: the maximal number of TUs that adaptive dwell + * can add to the total scan time + * @max_out_of_time: max out of serving channel time, per LMAC + * @suspend_time: max suspend time, per LMAC + * @scan_priority: priority of the request + * @passive_dwell: continues dwell time for passive channel + * (without adaptive dwell) + * @num_of_fragments: number of fragments needed for full fragmented + * scan coverage. + */ +struct iwl_scan_general_params_v10 { + __le16 flags; + u8 reserved; + u8 scan_start_mac_id; + u8 active_dwell[SCAN_TWO_LMACS]; + u8 adwell_default_2g; + u8 adwell_default_5g; + u8 adwell_default_social_chn; + u8 reserved1; + __le16 adwell_max_budget; + __le32 max_out_of_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + u8 passive_dwell[SCAN_TWO_LMACS]; + u8 num_of_fragments[SCAN_TWO_LMACS]; +} __packed; /* SCAN_GENERAL_PARAMS_API_S_VER_10 */ + +/** + * struct iwl_scan_periodic_parms_v1 + * @schedule: can scheduling parameter + * @delay: initial delay of the periodic scan in seconds + * @reserved: reserved for future + */ +struct iwl_scan_periodic_parms_v1 { + struct iwl_scan_umac_schedule schedule[IWL_MAX_SCHED_SCAN_PLANS]; + __le16 delay; + __le16 reserved; +} __packed; /* SCAN_PERIODIC_PARAMS_API_S_VER_1 */ + +/** + * struct iwl_scan_req_params_v12 + * @general_params: &struct iwl_scan_general_params_v10 + * @channel_params: &struct iwl_scan_channel_params_v4 + * @periodic_params: &struct iwl_scan_periodic_parms_v1 + * @probe_params: &struct iwl_scan_probe_params_v3 + */ +struct iwl_scan_req_params_v12 { + struct iwl_scan_general_params_v10 general_params; + struct iwl_scan_channel_params_v4 channel_params; + struct iwl_scan_periodic_parms_v1 periodic_params; + struct iwl_scan_probe_params_v3 probe_params; +} __packed; /* SCAN_REQUEST_PARAMS_API_S_VER_12 */ + +/** + * struct iwl_scan_req_params_v14 + * @general_params: &struct iwl_scan_general_params_v10 + * @channel_params: &struct iwl_scan_channel_params_v6 + * @periodic_params: &struct iwl_scan_periodic_parms_v1 + * @probe_params: &struct iwl_scan_probe_params_v4 + */ +struct iwl_scan_req_params_v14 { + struct iwl_scan_general_params_v10 general_params; + struct iwl_scan_channel_params_v6 channel_params; + struct iwl_scan_periodic_parms_v1 periodic_params; + struct iwl_scan_probe_params_v4 probe_params; +} __packed; /* SCAN_REQUEST_PARAMS_API_S_VER_14 */ + +/** + * struct iwl_scan_req_umac_v12 + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @ooc_priority: out of channel priority - &enum iwl_scan_priority + * @scan_params: scan parameters + */ +struct iwl_scan_req_umac_v12 { + __le32 uid; + __le32 ooc_priority; + struct iwl_scan_req_params_v12 scan_params; +} __packed; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_12 */ + +/** + * struct iwl_scan_req_umac_v14 + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @ooc_priority: out of channel priority - &enum iwl_scan_priority + * @scan_params: scan parameters + */ +struct iwl_scan_req_umac_v14 { + __le32 uid; + __le32 ooc_priority; + struct iwl_scan_req_params_v14 scan_params; +} __packed; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_14 */ + +/** + * struct iwl_umac_scan_abort + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @flags: reserved + */ +struct iwl_umac_scan_abort { + __le32 uid; + __le32 flags; +} __packed; /* SCAN_ABORT_CMD_UMAC_API_S_VER_1 */ + +/** + * struct iwl_umac_scan_complete + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @last_schedule: last scheduling line + * @last_iter: last scan iteration number + * @status: &enum iwl_scan_offload_complete_status + * @ebs_status: &enum iwl_scan_ebs_status + * @time_from_last_iter: time elapsed from last iteration + * @reserved: for future use + */ +struct iwl_umac_scan_complete { + __le32 uid; + u8 last_schedule; + u8 last_iter; + u8 status; + u8 ebs_status; + __le32 time_from_last_iter; + __le32 reserved; +} __packed; /* SCAN_COMPLETE_NTF_UMAC_API_S_VER_1 */ + +#define SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1 5 +#define SCAN_OFFLOAD_MATCHING_CHANNELS_LEN 7 + +/** + * struct iwl_scan_offload_profile_match_v1 - match information + * @bssid: matched bssid + * @reserved: reserved + * @channel: channel where the match occurred + * @energy: energy + * @matching_feature: feature matches + * @matching_channels: bitmap of channels that matched, referencing + * the channels passed in the scan offload request. + */ +struct iwl_scan_offload_profile_match_v1 { + u8 bssid[ETH_ALEN]; + __le16 reserved; + u8 channel; + u8 energy; + u8 matching_feature; + u8 matching_channels[SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1]; +} __packed; /* SCAN_OFFLOAD_PROFILE_MATCH_RESULTS_S_VER_1 */ + +/** + * struct iwl_scan_offload_profiles_query_v1 - match results query response + * @matched_profiles: bitmap of matched profiles, referencing the + * matches passed in the scan offload request + * @last_scan_age: age of the last offloaded scan + * @n_scans_done: number of offloaded scans done + * @gp2_d0u: GP2 when D0U occurred + * @gp2_invoked: GP2 when scan offload was invoked + * @resume_while_scanning: not used + * @self_recovery: obsolete + * @reserved: reserved + * @matches: array of match information, one for each match + */ +struct iwl_scan_offload_profiles_query_v1 { + __le32 matched_profiles; + __le32 last_scan_age; + __le32 n_scans_done; + __le32 gp2_d0u; + __le32 gp2_invoked; + u8 resume_while_scanning; + u8 self_recovery; + __le16 reserved; + struct iwl_scan_offload_profile_match_v1 matches[0]; +} __packed; /* SCAN_OFFLOAD_PROFILES_QUERY_RSP_S_VER_2 */ + +/** + * struct iwl_scan_offload_profile_match - match information + * @bssid: matched bssid + * @reserved: reserved + * @channel: channel where the match occurred + * @energy: energy + * @matching_feature: feature matches + * @matching_channels: bitmap of channels that matched, referencing + * the channels passed in the scan offload request. + */ +struct iwl_scan_offload_profile_match { + u8 bssid[ETH_ALEN]; + __le16 reserved; + u8 channel; + u8 energy; + u8 matching_feature; + u8 matching_channels[SCAN_OFFLOAD_MATCHING_CHANNELS_LEN]; +} __packed; /* SCAN_OFFLOAD_PROFILE_MATCH_RESULTS_S_VER_2 */ + +/** + * struct iwl_scan_offload_profiles_query - match results query response + * @matched_profiles: bitmap of matched profiles, referencing the + * matches passed in the scan offload request + * @last_scan_age: age of the last offloaded scan + * @n_scans_done: number of offloaded scans done + * @gp2_d0u: GP2 when D0U occurred + * @gp2_invoked: GP2 when scan offload was invoked + * @resume_while_scanning: not used + * @self_recovery: obsolete + * @reserved: reserved + * @matches: array of match information, one for each match + */ +struct iwl_scan_offload_profiles_query { + __le32 matched_profiles; + __le32 last_scan_age; + __le32 n_scans_done; + __le32 gp2_d0u; + __le32 gp2_invoked; + u8 resume_while_scanning; + u8 self_recovery; + __le16 reserved; + struct iwl_scan_offload_profile_match matches[0]; +} __packed; /* SCAN_OFFLOAD_PROFILES_QUERY_RSP_S_VER_3 */ + +/** + * struct iwl_umac_scan_iter_complete_notif - notifies end of scanning iteration + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @scanned_channels: number of channels scanned and number of valid elements in + * results array + * @status: one of SCAN_COMP_STATUS_* + * @bt_status: BT on/off status + * @last_channel: last channel that was scanned + * @start_tsf: TSF timer in usecs of the scan start time for the mac specified + * in &struct iwl_scan_req_umac. + * @results: array of scan results, length in @scanned_channels + */ +struct iwl_umac_scan_iter_complete_notif { + __le32 uid; + u8 scanned_channels; + u8 status; + u8 bt_status; + u8 last_channel; + __le64 start_tsf; + struct iwl_scan_results_notif results[]; +} __packed; /* SCAN_ITER_COMPLETE_NTF_UMAC_API_S_VER_2 */ + +#endif /* __iwl_fw_api_scan_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/sf.h b/sys/contrib/dev/iwlwifi/fw/api/sf.h new file mode 100644 index 00000000000..be4ff79fa1b --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/sf.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_sf_h__ +#define __iwl_fw_api_sf_h__ + +/* Smart Fifo state */ +enum iwl_sf_state { + SF_LONG_DELAY_ON = 0, /* should never be called by driver */ + SF_FULL_ON, + SF_UNINIT, + SF_INIT_OFF, + SF_HW_NUM_STATES +}; + +/* Smart Fifo possible scenario */ +enum iwl_sf_scenario { + SF_SCENARIO_SINGLE_UNICAST, + SF_SCENARIO_AGG_UNICAST, + SF_SCENARIO_MULTICAST, + SF_SCENARIO_BA_RESP, + SF_SCENARIO_TX_RESP, + SF_NUM_SCENARIO +}; + +#define SF_TRANSIENT_STATES_NUMBER 2 /* SF_LONG_DELAY_ON and SF_FULL_ON */ +#define SF_NUM_TIMEOUT_TYPES 2 /* Aging timer and Idle timer */ + +/* smart FIFO default values */ +#define SF_W_MARK_SISO 6144 +#define SF_W_MARK_MIMO2 8192 +#define SF_W_MARK_MIMO3 6144 +#define SF_W_MARK_LEGACY 4096 +#define SF_W_MARK_SCAN 4096 + +/* SF Scenarios timers for default configuration (aligned to 32 uSec) */ +#define SF_SINGLE_UNICAST_IDLE_TIMER_DEF 160 /* 150 uSec */ +#define SF_SINGLE_UNICAST_AGING_TIMER_DEF 400 /* 0.4 mSec */ +#define SF_AGG_UNICAST_IDLE_TIMER_DEF 160 /* 150 uSec */ +#define SF_AGG_UNICAST_AGING_TIMER_DEF 400 /* 0.4 mSec */ +#define SF_MCAST_IDLE_TIMER_DEF 160 /* 150 mSec */ +#define SF_MCAST_AGING_TIMER_DEF 400 /* 0.4 mSec */ +#define SF_BA_IDLE_TIMER_DEF 160 /* 150 uSec */ +#define SF_BA_AGING_TIMER_DEF 400 /* 0.4 mSec */ +#define SF_TX_RE_IDLE_TIMER_DEF 160 /* 150 uSec */ +#define SF_TX_RE_AGING_TIMER_DEF 400 /* 0.4 mSec */ + +/* SF Scenarios timers for BSS MAC configuration (aligned to 32 uSec) */ +#define SF_SINGLE_UNICAST_IDLE_TIMER 320 /* 300 uSec */ +#define SF_SINGLE_UNICAST_AGING_TIMER 2016 /* 2 mSec */ +#define SF_AGG_UNICAST_IDLE_TIMER 320 /* 300 uSec */ +#define SF_AGG_UNICAST_AGING_TIMER 2016 /* 2 mSec */ +#define SF_MCAST_IDLE_TIMER 2016 /* 2 mSec */ +#define SF_MCAST_AGING_TIMER 10016 /* 10 mSec */ +#define SF_BA_IDLE_TIMER 320 /* 300 uSec */ +#define SF_BA_AGING_TIMER 2016 /* 2 mSec */ +#define SF_TX_RE_IDLE_TIMER 320 /* 300 uSec */ +#define SF_TX_RE_AGING_TIMER 2016 /* 2 mSec */ + +#define SF_LONG_DELAY_AGING_TIMER 1000000 /* 1 Sec */ + +#define SF_CFG_DUMMY_NOTIF_OFF BIT(16) + +/** + * struct iwl_sf_cfg_cmd - Smart Fifo configuration command. + * @state: smart fifo state, types listed in &enum iwl_sf_state. + * @watermark: Minimum allowed available free space in RXF for transient state. + * @long_delay_timeouts: aging and idle timer values for each scenario + * in long delay state. + * @full_on_timeouts: timer values for each scenario in full on state. + */ +struct iwl_sf_cfg_cmd { + __le32 state; + __le32 watermark[SF_TRANSIENT_STATES_NUMBER]; + __le32 long_delay_timeouts[SF_NUM_SCENARIO][SF_NUM_TIMEOUT_TYPES]; + __le32 full_on_timeouts[SF_NUM_SCENARIO][SF_NUM_TIMEOUT_TYPES]; +} __packed; /* SF_CFG_API_S_VER_2 */ + +#endif /* __iwl_fw_api_sf_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/soc.h b/sys/contrib/dev/iwlwifi/fw/api/soc.h new file mode 100644 index 00000000000..c5df1171462 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/soc.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2019-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_soc_h__ +#define __iwl_fw_api_soc_h__ + +#define SOC_CONFIG_CMD_FLAGS_DISCRETE BIT(0) +#define SOC_CONFIG_CMD_FLAGS_LOW_LATENCY BIT(1) + +#define SOC_FLAGS_LTR_APPLY_DELAY_MASK 0xc +#define SOC_FLAGS_LTR_APPLY_DELAY_NONE 0 +#define SOC_FLAGS_LTR_APPLY_DELAY_200 1 +#define SOC_FLAGS_LTR_APPLY_DELAY_2500 2 +#define SOC_FLAGS_LTR_APPLY_DELAY_1820 3 + +/** + * struct iwl_soc_configuration_cmd - Set device stabilization latency + * + * @flags: soc settings flags. In VER_1, we can only set the DISCRETE + * flag, because the FW treats the whole value as an integer. In + * VER_2, we can set the bits independently. + * @latency: time for SOC to ensure stable power & XTAL + */ +struct iwl_soc_configuration_cmd { + __le32 flags; + __le32 latency; +} __packed; /* + * SOC_CONFIGURATION_CMD_S_VER_1 (see description above) + * SOC_CONFIGURATION_CMD_S_VER_2 + */ + +#endif /* __iwl_fw_api_soc_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/sta.h b/sys/contrib/dev/iwlwifi/fw/api/sta.h new file mode 100644 index 00000000000..5edbe27c092 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/sta.h @@ -0,0 +1,494 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_sta_h__ +#define __iwl_fw_api_sta_h__ + +/** + * enum iwl_sta_flags - flags for the ADD_STA host command + * @STA_FLG_REDUCED_TX_PWR_CTRL: reduced TX power (control frames) + * @STA_FLG_REDUCED_TX_PWR_DATA: reduced TX power (data frames) + * @STA_FLG_DISABLE_TX: set if TX should be disabled + * @STA_FLG_PS: set if STA is in Power Save + * @STA_FLG_DRAIN_FLOW: drain flow + * @STA_FLG_PAN: STA is for PAN interface + * @STA_FLG_CLASS_AUTH: station is authenticated + * @STA_FLG_CLASS_ASSOC: station is associated + * @STA_FLG_RTS_MIMO_PROT: station requires RTS MIMO protection (dynamic SMPS) + * @STA_FLG_MAX_AGG_SIZE_MSK: maximal size for A-MPDU (mask) + * @STA_FLG_MAX_AGG_SIZE_SHIFT: maximal size for A-MPDU (bit shift) + * @STA_FLG_MAX_AGG_SIZE_8K: maximal size for A-MPDU (8k supported) + * @STA_FLG_MAX_AGG_SIZE_16K: maximal size for A-MPDU (16k supported) + * @STA_FLG_MAX_AGG_SIZE_32K: maximal size for A-MPDU (32k supported) + * @STA_FLG_MAX_AGG_SIZE_64K: maximal size for A-MPDU (64k supported) + * @STA_FLG_MAX_AGG_SIZE_128K: maximal size for A-MPDU (128k supported) + * @STA_FLG_MAX_AGG_SIZE_256K: maximal size for A-MPDU (256k supported) + * @STA_FLG_MAX_AGG_SIZE_512K: maximal size for A-MPDU (512k supported) + * @STA_FLG_MAX_AGG_SIZE_1024K: maximal size for A-MPDU (1024k supported) + * @STA_FLG_MAX_AGG_SIZE_2M: maximal size for A-MPDU (2M supported) + * @STA_FLG_MAX_AGG_SIZE_4M: maximal size for A-MPDU (4M supported) + * @STA_FLG_AGG_MPDU_DENS_MSK: maximal MPDU density for Tx aggregation + * @STA_FLG_FAT_EN_MSK: support for channel width (for Tx). This flag is + * initialised by driver and can be updated by fw upon reception of + * action frames that can change the channel width. When cleared the fw + * will send all the frames in 20MHz even when FAT channel is requested. + * @STA_FLG_FAT_EN_20MHZ: no wide channels are supported, only 20 MHz + * @STA_FLG_FAT_EN_40MHZ: wide channels up to 40 MHz supported + * @STA_FLG_FAT_EN_80MHZ: wide channels up to 80 MHz supported + * @STA_FLG_FAT_EN_160MHZ: wide channels up to 160 MHz supported + * @STA_FLG_MIMO_EN_MSK: support for MIMO. This flag is initialised by the + * driver and can be updated by fw upon reception of action frames. + * @STA_FLG_MIMO_EN_SISO: no support for MIMO + * @STA_FLG_MIMO_EN_MIMO2: 2 streams supported + * @STA_FLG_MIMO_EN_MIMO3: 3 streams supported + * @STA_FLG_AGG_MPDU_DENS_MSK: A-MPDU density (mask) + * @STA_FLG_AGG_MPDU_DENS_SHIFT: A-MPDU density (bit shift) + * @STA_FLG_AGG_MPDU_DENS_2US: A-MPDU density (2 usec gap) + * @STA_FLG_AGG_MPDU_DENS_4US: A-MPDU density (4 usec gap) + * @STA_FLG_AGG_MPDU_DENS_8US: A-MPDU density (8 usec gap) + * @STA_FLG_AGG_MPDU_DENS_16US: A-MPDU density (16 usec gap) + */ +enum iwl_sta_flags { + STA_FLG_REDUCED_TX_PWR_CTRL = BIT(3), + STA_FLG_REDUCED_TX_PWR_DATA = BIT(6), + + STA_FLG_DISABLE_TX = BIT(4), + + STA_FLG_PS = BIT(8), + STA_FLG_DRAIN_FLOW = BIT(12), + STA_FLG_PAN = BIT(13), + STA_FLG_CLASS_AUTH = BIT(14), + STA_FLG_CLASS_ASSOC = BIT(15), + STA_FLG_RTS_MIMO_PROT = BIT(17), + + STA_FLG_MAX_AGG_SIZE_SHIFT = 19, + STA_FLG_MAX_AGG_SIZE_8K = (0 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_16K = (1 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_32K = (2 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_64K = (3 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_128K = (4 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_256K = (5 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_512K = (6 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_1024K = (7 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_2M = (8 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_4M = (9 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_MSK = (0xf << STA_FLG_MAX_AGG_SIZE_SHIFT), + + STA_FLG_AGG_MPDU_DENS_SHIFT = 23, + STA_FLG_AGG_MPDU_DENS_2US = (4 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_4US = (5 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_8US = (6 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_16US = (7 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_MSK = (7 << STA_FLG_AGG_MPDU_DENS_SHIFT), + + STA_FLG_FAT_EN_20MHZ = (0 << 26), + STA_FLG_FAT_EN_40MHZ = (1 << 26), + STA_FLG_FAT_EN_80MHZ = (2 << 26), + STA_FLG_FAT_EN_160MHZ = (3 << 26), + STA_FLG_FAT_EN_MSK = (3 << 26), + + STA_FLG_MIMO_EN_SISO = (0 << 28), + STA_FLG_MIMO_EN_MIMO2 = (1 << 28), + STA_FLG_MIMO_EN_MIMO3 = (2 << 28), + STA_FLG_MIMO_EN_MSK = (3 << 28), +}; + +/** + * enum iwl_sta_key_flag - key flags for the ADD_STA host command + * @STA_KEY_FLG_NO_ENC: no encryption + * @STA_KEY_FLG_WEP: WEP encryption algorithm + * @STA_KEY_FLG_CCM: CCMP encryption algorithm + * @STA_KEY_FLG_TKIP: TKIP encryption algorithm + * @STA_KEY_FLG_EXT: extended cipher algorithm (depends on the FW support) + * @STA_KEY_FLG_GCMP: GCMP encryption algorithm + * @STA_KEY_FLG_CMAC: CMAC encryption algorithm + * @STA_KEY_FLG_ENC_UNKNOWN: unknown encryption algorithm + * @STA_KEY_FLG_EN_MSK: mask for encryption algorithmi value + * @STA_KEY_FLG_WEP_KEY_MAP: wep is either a group key (0 - legacy WEP) or from + * station info array (1 - n 1X mode) + * @STA_KEY_FLG_KEYID_MSK: the index of the key + * @STA_KEY_FLG_KEYID_POS: key index bit position + * @STA_KEY_NOT_VALID: key is invalid + * @STA_KEY_FLG_WEP_13BYTES: set for 13 bytes WEP key + * @STA_KEY_FLG_KEY_32BYTES: for non-wep key set for 32 bytes key + * @STA_KEY_MULTICAST: set for multical key + * @STA_KEY_MFP: key is used for Management Frame Protection + */ +enum iwl_sta_key_flag { + STA_KEY_FLG_NO_ENC = (0 << 0), + STA_KEY_FLG_WEP = (1 << 0), + STA_KEY_FLG_CCM = (2 << 0), + STA_KEY_FLG_TKIP = (3 << 0), + STA_KEY_FLG_EXT = (4 << 0), + STA_KEY_FLG_GCMP = (5 << 0), + STA_KEY_FLG_CMAC = (6 << 0), + STA_KEY_FLG_ENC_UNKNOWN = (7 << 0), + STA_KEY_FLG_EN_MSK = (7 << 0), + + STA_KEY_FLG_WEP_KEY_MAP = BIT(3), + STA_KEY_FLG_KEYID_POS = 8, + STA_KEY_FLG_KEYID_MSK = (3 << STA_KEY_FLG_KEYID_POS), + STA_KEY_NOT_VALID = BIT(11), + STA_KEY_FLG_WEP_13BYTES = BIT(12), + STA_KEY_FLG_KEY_32BYTES = BIT(12), + STA_KEY_MULTICAST = BIT(14), + STA_KEY_MFP = BIT(15), +}; + +/** + * enum iwl_sta_modify_flag - indicate to the fw what flag are being changed + * @STA_MODIFY_QUEUE_REMOVAL: this command removes a queue + * @STA_MODIFY_TID_DISABLE_TX: this command modifies %tid_disable_tx + * @STA_MODIFY_UAPSD_ACS: this command modifies %uapsd_acs + * @STA_MODIFY_ADD_BA_TID: this command modifies %add_immediate_ba_tid + * @STA_MODIFY_REMOVE_BA_TID: this command modifies %remove_immediate_ba_tid + * @STA_MODIFY_SLEEPING_STA_TX_COUNT: this command modifies %sleep_tx_count + * @STA_MODIFY_PROT_TH: modify RTS threshold + * @STA_MODIFY_QUEUES: modify the queues used by this station + */ +enum iwl_sta_modify_flag { + STA_MODIFY_QUEUE_REMOVAL = BIT(0), + STA_MODIFY_TID_DISABLE_TX = BIT(1), + STA_MODIFY_UAPSD_ACS = BIT(2), + STA_MODIFY_ADD_BA_TID = BIT(3), + STA_MODIFY_REMOVE_BA_TID = BIT(4), + STA_MODIFY_SLEEPING_STA_TX_COUNT = BIT(5), + STA_MODIFY_PROT_TH = BIT(6), + STA_MODIFY_QUEUES = BIT(7), +}; + +/** + * enum iwl_sta_mode - station command mode + * @STA_MODE_ADD: add new station + * @STA_MODE_MODIFY: modify the station + */ +enum iwl_sta_mode { + STA_MODE_ADD = 0, + STA_MODE_MODIFY = 1, +}; + +/** + * enum iwl_sta_sleep_flag - type of sleep of the station + * @STA_SLEEP_STATE_AWAKE: station is awake + * @STA_SLEEP_STATE_PS_POLL: station is PS-polling + * @STA_SLEEP_STATE_UAPSD: station uses U-APSD + * @STA_SLEEP_STATE_MOREDATA: set more-data bit on + * (last) released frame + */ +enum iwl_sta_sleep_flag { + STA_SLEEP_STATE_AWAKE = 0, + STA_SLEEP_STATE_PS_POLL = BIT(0), + STA_SLEEP_STATE_UAPSD = BIT(1), + STA_SLEEP_STATE_MOREDATA = BIT(2), +}; + +#define STA_KEY_MAX_NUM (16) +#define STA_KEY_IDX_INVALID (0xff) +#define STA_KEY_MAX_DATA_KEY_NUM (4) +#define IWL_MAX_GLOBAL_KEYS (4) +#define STA_KEY_LEN_WEP40 (5) +#define STA_KEY_LEN_WEP104 (13) + +#define IWL_ADD_STA_STATUS_MASK 0xFF +#define IWL_ADD_STA_BAID_VALID_MASK 0x8000 +#define IWL_ADD_STA_BAID_MASK 0x7F00 +#define IWL_ADD_STA_BAID_SHIFT 8 + +/** + * struct iwl_mvm_add_sta_cmd_v7 - Add/modify a station in the fw's sta table. + * ( REPLY_ADD_STA = 0x18 ) + * @add_modify: see &enum iwl_sta_mode + * @awake_acs: ACs to transmit data on while station is sleeping (for U-APSD) + * @tid_disable_tx: is tid BIT(tid) enabled for Tx. Clear BIT(x) to enable + * AMPDU for tid x. Set %STA_MODIFY_TID_DISABLE_TX to change this field. + * @mac_id_n_color: the Mac context this station belongs to, + * see &enum iwl_ctxt_id_and_color + * @addr: station's MAC address + * @reserved2: reserved + * @sta_id: index of station in uCode's station table + * @modify_mask: from &enum iwl_sta_modify_flag, selects what to change + * @reserved3: reserved + * @station_flags: look at &enum iwl_sta_flags + * @station_flags_msk: what of %station_flags have changed, + * also &enum iwl_sta_flags + * @add_immediate_ba_tid: tid for which to add block-ack support (Rx) + * Set %STA_MODIFY_ADD_BA_TID to use this field, and also set + * add_immediate_ba_ssn. + * @remove_immediate_ba_tid: tid for which to remove block-ack support (Rx) + * Set %STA_MODIFY_REMOVE_BA_TID to use this field + * @add_immediate_ba_ssn: ssn for the Rx block-ack session. Used together with + * add_immediate_ba_tid. + * @sleep_tx_count: number of packets to transmit to station even though it is + * asleep. Used to synchronise PS-poll and u-APSD responses while ucode + * keeps track of STA sleep state. + * @sleep_state_flags: Look at &enum iwl_sta_sleep_flag. + * @assoc_id: assoc_id to be sent in VHT PLCP (9-bit), for grp use 0, for AP + * mac-addr. + * @beamform_flags: beam forming controls + * @tfd_queue_msk: tfd queues used by this station + * + * The device contains an internal table of per-station information, with info + * on security keys, aggregation parameters, and Tx rates for initial Tx + * attempt and any retries (set by REPLY_TX_LINK_QUALITY_CMD). + * + * ADD_STA sets up the table entry for one station, either creating a new + * entry, or modifying a pre-existing one. + */ +struct iwl_mvm_add_sta_cmd_v7 { + u8 add_modify; + u8 awake_acs; + __le16 tid_disable_tx; + __le32 mac_id_n_color; + u8 addr[ETH_ALEN]; /* _STA_ID_MODIFY_INFO_API_S_VER_1 */ + __le16 reserved2; + u8 sta_id; + u8 modify_mask; + __le16 reserved3; + __le32 station_flags; + __le32 station_flags_msk; + u8 add_immediate_ba_tid; + u8 remove_immediate_ba_tid; + __le16 add_immediate_ba_ssn; + __le16 sleep_tx_count; + __le16 sleep_state_flags; + __le16 assoc_id; + __le16 beamform_flags; + __le32 tfd_queue_msk; +} __packed; /* ADD_STA_CMD_API_S_VER_7 */ + +/** + * enum iwl_sta_type - FW station types + * ( REPLY_ADD_STA = 0x18 ) + * @IWL_STA_LINK: Link station - normal RX and TX traffic. + * @IWL_STA_GENERAL_PURPOSE: General purpose. In AP mode used for beacons + * and probe responses. + * @IWL_STA_MULTICAST: multicast traffic, + * @IWL_STA_TDLS_LINK: TDLS link station + * @IWL_STA_AUX_ACTIVITY: auxilary station (scan, ROC and so on). + */ +enum iwl_sta_type { + IWL_STA_LINK, + IWL_STA_GENERAL_PURPOSE, + IWL_STA_MULTICAST, + IWL_STA_TDLS_LINK, + IWL_STA_AUX_ACTIVITY, +}; + +/** + * struct iwl_mvm_add_sta_cmd - Add/modify a station in the fw's sta table. + * ( REPLY_ADD_STA = 0x18 ) + * @add_modify: see &enum iwl_sta_mode + * @awake_acs: ACs to transmit data on while station is sleeping (for U-APSD) + * @tid_disable_tx: is tid BIT(tid) enabled for Tx. Clear BIT(x) to enable + * AMPDU for tid x. Set %STA_MODIFY_TID_DISABLE_TX to change this field. + * @mac_id_n_color: the Mac context this station belongs to, + * see &enum iwl_ctxt_id_and_color + * @addr: station's MAC address + * @reserved2: reserved + * @sta_id: index of station in uCode's station table + * @modify_mask: from &enum iwl_sta_modify_flag, selects what to change + * @reserved3: reserved + * @station_flags: look at &enum iwl_sta_flags + * @station_flags_msk: what of %station_flags have changed, + * also &enum iwl_sta_flags + * @add_immediate_ba_tid: tid for which to add block-ack support (Rx) + * Set %STA_MODIFY_ADD_BA_TID to use this field, and also set + * add_immediate_ba_ssn. + * @remove_immediate_ba_tid: tid for which to remove block-ack support (Rx) + * Set %STA_MODIFY_REMOVE_BA_TID to use this field + * @add_immediate_ba_ssn: ssn for the Rx block-ack session. Used together with + * add_immediate_ba_tid. + * @sleep_tx_count: number of packets to transmit to station even though it is + * asleep. Used to synchronise PS-poll and u-APSD responses while ucode + * keeps track of STA sleep state. + * @station_type: type of this station. See &enum iwl_sta_type. + * @sleep_state_flags: Look at &enum iwl_sta_sleep_flag. + * @assoc_id: assoc_id to be sent in VHT PLCP (9-bit), for grp use 0, for AP + * mac-addr. + * @beamform_flags: beam forming controls + * @tfd_queue_msk: tfd queues used by this station. + * Obselete for new TX API (9 and above). + * @rx_ba_window: aggregation window size + * @sp_length: the size of the SP in actual number of frames + * @uapsd_acs: 4 LS bits are trigger enabled ACs, 4 MS bits are the deliver + * enabled ACs. + * + * The device contains an internal table of per-station information, with info + * on security keys, aggregation parameters, and Tx rates for initial Tx + * attempt and any retries (set by REPLY_TX_LINK_QUALITY_CMD). + * + * ADD_STA sets up the table entry for one station, either creating a new + * entry, or modifying a pre-existing one. + */ +struct iwl_mvm_add_sta_cmd { + u8 add_modify; + u8 awake_acs; + __le16 tid_disable_tx; + __le32 mac_id_n_color; /* can be used for lmac id when using cmd v12 */ + u8 addr[ETH_ALEN]; /* _STA_ID_MODIFY_INFO_API_S_VER_1 */ + __le16 reserved2; + u8 sta_id; + u8 modify_mask; + __le16 reserved3; + __le32 station_flags; + __le32 station_flags_msk; + u8 add_immediate_ba_tid; + u8 remove_immediate_ba_tid; + __le16 add_immediate_ba_ssn; + __le16 sleep_tx_count; + u8 sleep_state_flags; + u8 station_type; + __le16 assoc_id; + __le16 beamform_flags; + __le32 tfd_queue_msk; + __le16 rx_ba_window; + u8 sp_length; + u8 uapsd_acs; +} __packed; /* ADD_STA_CMD_API_S_VER_10 */ + +/** + * struct iwl_mvm_add_sta_key_common - add/modify sta key common part + * ( REPLY_ADD_STA_KEY = 0x17 ) + * @sta_id: index of station in uCode's station table + * @key_offset: key offset in key storage + * @key_flags: type &enum iwl_sta_key_flag + * @key: key material data + * @rx_secur_seq_cnt: RX security sequence counter for the key + */ +struct iwl_mvm_add_sta_key_common { + u8 sta_id; + u8 key_offset; + __le16 key_flags; + u8 key[32]; + u8 rx_secur_seq_cnt[16]; +} __packed; + +/** + * struct iwl_mvm_add_sta_key_cmd_v1 - add/modify sta key + * @common: see &struct iwl_mvm_add_sta_key_common + * @tkip_rx_tsc_byte2: TSC[2] for key mix ph1 detection + * @reserved: reserved + * @tkip_rx_ttak: 10-byte unicast TKIP TTAK for Rx + */ +struct iwl_mvm_add_sta_key_cmd_v1 { + struct iwl_mvm_add_sta_key_common common; + u8 tkip_rx_tsc_byte2; + u8 reserved; + __le16 tkip_rx_ttak[5]; +} __packed; /* ADD_MODIFY_STA_KEY_API_S_VER_1 */ + +/** + * struct iwl_mvm_add_sta_key_cmd - add/modify sta key + * @common: see &struct iwl_mvm_add_sta_key_common + * @rx_mic_key: TKIP RX unicast or multicast key + * @tx_mic_key: TKIP TX key + * @transmit_seq_cnt: TSC, transmit packet number + * + * Note: This is used for both v2 and v3, the difference being + * in the way the common.rx_secur_seq_cnt is used, in v2 that's + * the strange hole format, in v3 it's just a u64. + */ +struct iwl_mvm_add_sta_key_cmd { + struct iwl_mvm_add_sta_key_common common; + __le64 rx_mic_key; + __le64 tx_mic_key; + __le64 transmit_seq_cnt; +} __packed; /* ADD_MODIFY_STA_KEY_API_S_VER_2, ADD_MODIFY_STA_KEY_API_S_VER_3 */ + +/** + * enum iwl_mvm_add_sta_rsp_status - status in the response to ADD_STA command + * @ADD_STA_SUCCESS: operation was executed successfully + * @ADD_STA_STATIONS_OVERLOAD: no room left in the fw's station table + * @ADD_STA_IMMEDIATE_BA_FAILURE: can't add Rx block ack session + * @ADD_STA_MODIFY_NON_EXISTING_STA: driver requested to modify a station that + * doesn't exist. + */ +enum iwl_mvm_add_sta_rsp_status { + ADD_STA_SUCCESS = 0x1, + ADD_STA_STATIONS_OVERLOAD = 0x2, + ADD_STA_IMMEDIATE_BA_FAILURE = 0x4, + ADD_STA_MODIFY_NON_EXISTING_STA = 0x8, +}; + +/** + * struct iwl_mvm_rm_sta_cmd - Add / modify a station in the fw's station table + * ( REMOVE_STA = 0x19 ) + * @sta_id: the station id of the station to be removed + * @reserved: reserved + */ +struct iwl_mvm_rm_sta_cmd { + u8 sta_id; + u8 reserved[3]; +} __packed; /* REMOVE_STA_CMD_API_S_VER_2 */ + +/** + * struct iwl_mvm_mgmt_mcast_key_cmd_v1 + * ( MGMT_MCAST_KEY = 0x1f ) + * @ctrl_flags: &enum iwl_sta_key_flag + * @igtk: IGTK key material + * @k1: unused + * @k2: unused + * @sta_id: station ID that support IGTK + * @key_id: key ID + * @receive_seq_cnt: initial RSC/PN needed for replay check + */ +struct iwl_mvm_mgmt_mcast_key_cmd_v1 { + __le32 ctrl_flags; + u8 igtk[16]; + u8 k1[16]; + u8 k2[16]; + __le32 key_id; + __le32 sta_id; + __le64 receive_seq_cnt; +} __packed; /* SEC_MGMT_MULTICAST_KEY_CMD_API_S_VER_1 */ + +/** + * struct iwl_mvm_mgmt_mcast_key_cmd + * ( MGMT_MCAST_KEY = 0x1f ) + * @ctrl_flags: &enum iwl_sta_key_flag + * @igtk: IGTK master key + * @sta_id: station ID that support IGTK + * @key_id: key ID + * @receive_seq_cnt: initial RSC/PN needed for replay check + */ +struct iwl_mvm_mgmt_mcast_key_cmd { + __le32 ctrl_flags; + u8 igtk[32]; + __le32 key_id; + __le32 sta_id; + __le64 receive_seq_cnt; +} __packed; /* SEC_MGMT_MULTICAST_KEY_CMD_API_S_VER_2 */ + +struct iwl_mvm_wep_key { + u8 key_index; + u8 key_offset; + __le16 reserved1; + u8 key_size; + u8 reserved2[3]; + u8 key[16]; +} __packed; + +struct iwl_mvm_wep_key_cmd { + __le32 mac_id_n_color; + u8 num_keys; + u8 decryption_type; + u8 flags; + u8 reserved; + struct iwl_mvm_wep_key wep_key[0]; +} __packed; /* SEC_CURR_WEP_KEY_CMD_API_S_VER_2 */ + +/** + * struct iwl_mvm_eosp_notification - EOSP notification from firmware + * @remain_frame_count: # of frames remaining, non-zero if SP was cut + * short by GO absence + * @sta_id: station ID + */ +struct iwl_mvm_eosp_notification { + __le32 remain_frame_count; + __le32 sta_id; +} __packed; /* UAPSD_EOSP_NTFY_API_S_VER_1 */ + +#endif /* __iwl_fw_api_sta_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/stats.h b/sys/contrib/dev/iwlwifi/fw/api/stats.h new file mode 100644 index 00000000000..18cca15caa3 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/stats.h @@ -0,0 +1,873 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018, 2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_stats_h__ +#define __iwl_fw_api_stats_h__ +#include "mac.h" + +struct mvm_statistics_dbg { + __le32 burst_check; + __le32 burst_count; + __le32 wait_for_silence_timeout_cnt; + u8 reserved[12]; +} __packed; /* STATISTICS_DEBUG_API_S_VER_2 */ + +struct mvm_statistics_div { + __le32 tx_on_a; + __le32 tx_on_b; + __le32 exec_time; + __le32 probe_time; + __le32 rssi_ant; + __le32 reserved2; +} __packed; /* STATISTICS_SLOW_DIV_API_S_VER_2 */ + +/** + * struct mvm_statistics_rx_non_phy + * @bogus_cts: CTS received when not expecting CTS + * @bogus_ack: ACK received when not expecting ACK + * @non_channel_beacons: beacons with our bss id but not on our serving channel + * @channel_beacons: beacons with our bss id and in our serving channel + * @num_missed_bcon: number of missed beacons + * @adc_rx_saturation_time: count in 0.8us units the time the ADC was in + * saturation + * @ina_detection_search_time: total time (in 0.8us) searched for INA + * @beacon_silence_rssi_a: RSSI silence after beacon frame + * @beacon_silence_rssi_b: RSSI silence after beacon frame + * @beacon_silence_rssi_c: RSSI silence after beacon frame + * @interference_data_flag: flag for interference data availability. 1 when data + * is available. + * @channel_load: counts RX Enable time in uSec + * @beacon_rssi_a: beacon RSSI on antenna A + * @beacon_rssi_b: beacon RSSI on antenna B + * @beacon_rssi_c: beacon RSSI on antenna C + * @beacon_energy_a: beacon energy on antenna A + * @beacon_energy_b: beacon energy on antenna B + * @beacon_energy_c: beacon energy on antenna C + * @num_bt_kills: number of BT "kills" (frame TX aborts) + * @mac_id: mac ID + */ +struct mvm_statistics_rx_non_phy { + __le32 bogus_cts; + __le32 bogus_ack; + __le32 non_channel_beacons; + __le32 channel_beacons; + __le32 num_missed_bcon; + __le32 adc_rx_saturation_time; + __le32 ina_detection_search_time; + __le32 beacon_silence_rssi_a; + __le32 beacon_silence_rssi_b; + __le32 beacon_silence_rssi_c; + __le32 interference_data_flag; + __le32 channel_load; + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 beacon_rssi_c; + __le32 beacon_energy_a; + __le32 beacon_energy_b; + __le32 beacon_energy_c; + __le32 num_bt_kills; + __le32 mac_id; +} __packed; /* STATISTICS_RX_NON_PHY_API_S_VER_4 */ + +struct mvm_statistics_rx_non_phy_v3 { + __le32 bogus_cts; /* CTS received when not expecting CTS */ + __le32 bogus_ack; /* ACK received when not expecting ACK */ + __le32 non_bssid_frames; /* number of frames with BSSID that + * doesn't belong to the STA BSSID */ + __le32 filtered_frames; /* count frames that were dumped in the + * filtering process */ + __le32 non_channel_beacons; /* beacons with our bss id but not on + * our serving channel */ + __le32 channel_beacons; /* beacons with our bss id and in our + * serving channel */ + __le32 num_missed_bcon; /* number of missed beacons */ + __le32 adc_rx_saturation_time; /* count in 0.8us units the time the + * ADC was in saturation */ + __le32 ina_detection_search_time;/* total time (in 0.8us) searched + * for INA */ + __le32 beacon_silence_rssi_a; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_b; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_c; /* RSSI silence after beacon frame */ + __le32 interference_data_flag; /* flag for interference data + * availability. 1 when data is + * available. */ + __le32 channel_load; /* counts RX Enable time in uSec */ + __le32 dsp_false_alarms; /* DSP false alarm (both OFDM + * and CCK) counter */ + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 beacon_rssi_c; + __le32 beacon_energy_a; + __le32 beacon_energy_b; + __le32 beacon_energy_c; + __le32 num_bt_kills; + __le32 mac_id; + __le32 directed_data_mpdu; +} __packed; /* STATISTICS_RX_NON_PHY_API_S_VER_3 */ + +struct mvm_statistics_rx_phy { + __le32 unresponded_rts; + __le32 rxe_frame_lmt_overrun; + __le32 sent_ba_rsp_cnt; + __le32 dsp_self_kill; + __le32 reserved; +} __packed; /* STATISTICS_RX_PHY_API_S_VER_3 */ + +struct mvm_statistics_rx_phy_v2 { + __le32 ina_cnt; + __le32 fina_cnt; + __le32 plcp_err; + __le32 crc32_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 false_alarm_cnt; + __le32 fina_sync_err_cnt; + __le32 sfd_timeout; + __le32 fina_timeout; + __le32 unresponded_rts; + __le32 rxe_frame_lmt_overrun; + __le32 sent_ack_cnt; + __le32 sent_cts_cnt; + __le32 sent_ba_rsp_cnt; + __le32 dsp_self_kill; + __le32 mh_format_err; + __le32 re_acq_main_rssi_sum; + __le32 reserved; +} __packed; /* STATISTICS_RX_PHY_API_S_VER_2 */ + +struct mvm_statistics_rx_ht_phy_v1 { + __le32 plcp_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 crc32_err; + __le32 mh_format_err; + __le32 agg_crc32_good; + __le32 agg_mpdu_cnt; + __le32 agg_cnt; + __le32 unsupport_mcs; +} __packed; /* STATISTICS_HT_RX_PHY_API_S_VER_1 */ + +struct mvm_statistics_rx_ht_phy { + __le32 mh_format_err; + __le32 agg_mpdu_cnt; + __le32 agg_cnt; + __le32 unsupport_mcs; +} __packed; /* STATISTICS_HT_RX_PHY_API_S_VER_2 */ + +struct mvm_statistics_tx_non_phy_v3 { + __le32 preamble_cnt; + __le32 rx_detected_cnt; + __le32 bt_prio_defer_cnt; + __le32 bt_prio_kill_cnt; + __le32 few_bytes_cnt; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 expected_ack_cnt; + __le32 actual_ack_cnt; + __le32 dump_msdu_cnt; + __le32 burst_abort_next_frame_mismatch_cnt; + __le32 burst_abort_missing_next_frame_cnt; + __le32 cts_timeout_collision; + __le32 ack_or_ba_timeout_collision; +} __packed; /* STATISTICS_TX_NON_PHY_API_S_VER_3 */ + +struct mvm_statistics_tx_non_phy { + __le32 bt_prio_defer_cnt; + __le32 bt_prio_kill_cnt; + __le32 few_bytes_cnt; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 dump_msdu_cnt; + __le32 burst_abort_next_frame_mismatch_cnt; + __le32 burst_abort_missing_next_frame_cnt; + __le32 cts_timeout_collision; + __le32 ack_or_ba_timeout_collision; +} __packed; /* STATISTICS_TX_NON_PHY_API_S_VER_4 */ + +#define MAX_CHAINS 3 + +struct mvm_statistics_tx_non_phy_agg { + __le32 ba_timeout; + __le32 ba_reschedule_frames; + __le32 scd_query_agg_frame_cnt; + __le32 scd_query_no_agg; + __le32 scd_query_agg; + __le32 scd_query_mismatch; + __le32 frame_not_ready; + __le32 underrun; + __le32 bt_prio_kill; + __le32 rx_ba_rsp_cnt; + __s8 txpower[MAX_CHAINS]; + __s8 reserved; + __le32 reserved2; +} __packed; /* STATISTICS_TX_NON_PHY_AGG_API_S_VER_1 */ + +struct mvm_statistics_tx_channel_width { + __le32 ext_cca_narrow_ch20[1]; + __le32 ext_cca_narrow_ch40[2]; + __le32 ext_cca_narrow_ch80[3]; + __le32 ext_cca_narrow_ch160[4]; + __le32 last_tx_ch_width_indx; + __le32 rx_detected_per_ch_width[4]; + __le32 success_per_ch_width[4]; + __le32 fail_per_ch_width[4]; +}; /* STATISTICS_TX_CHANNEL_WIDTH_API_S_VER_1 */ + +struct mvm_statistics_tx_v4 { + struct mvm_statistics_tx_non_phy_v3 general; + struct mvm_statistics_tx_non_phy_agg agg; + struct mvm_statistics_tx_channel_width channel_width; +} __packed; /* STATISTICS_TX_API_S_VER_4 */ + +struct mvm_statistics_tx { + struct mvm_statistics_tx_non_phy general; + struct mvm_statistics_tx_non_phy_agg agg; + struct mvm_statistics_tx_channel_width channel_width; +} __packed; /* STATISTICS_TX_API_S_VER_5 */ + + +struct mvm_statistics_bt_activity { + __le32 hi_priority_tx_req_cnt; + __le32 hi_priority_tx_denied_cnt; + __le32 lo_priority_tx_req_cnt; + __le32 lo_priority_tx_denied_cnt; + __le32 hi_priority_rx_req_cnt; + __le32 hi_priority_rx_denied_cnt; + __le32 lo_priority_rx_req_cnt; + __le32 lo_priority_rx_denied_cnt; +} __packed; /* STATISTICS_BT_ACTIVITY_API_S_VER_1 */ + +struct mvm_statistics_general_common_v19 { + __le32 radio_temperature; + __le32 radio_voltage; + struct mvm_statistics_dbg dbg; + __le32 sleep_time; + __le32 slots_out; + __le32 slots_idle; + __le32 ttl_timestamp; + struct mvm_statistics_div slow_div; + __le32 rx_enable_counter; + /* + * num_of_sos_states: + * count the number of times we have to re-tune + * in order to get out of bad PHY status + */ + __le32 num_of_sos_states; + __le32 beacon_filtered; + __le32 missed_beacons; + u8 beacon_filter_average_energy; + u8 beacon_filter_reason; + u8 beacon_filter_current_energy; + u8 beacon_filter_reserved; + __le32 beacon_filter_delta_time; + struct mvm_statistics_bt_activity bt_activity; + __le64 rx_time; + __le64 on_time_rf; + __le64 on_time_scan; + __le64 tx_time; +} __packed; + +struct mvm_statistics_general_common { + __le32 radio_temperature; + struct mvm_statistics_dbg dbg; + __le32 sleep_time; + __le32 slots_out; + __le32 slots_idle; + __le32 ttl_timestamp; + struct mvm_statistics_div slow_div; + __le32 rx_enable_counter; + /* + * num_of_sos_states: + * count the number of times we have to re-tune + * in order to get out of bad PHY status + */ + __le32 num_of_sos_states; + __le32 beacon_filtered; + __le32 missed_beacons; + u8 beacon_filter_average_energy; + u8 beacon_filter_reason; + u8 beacon_filter_current_energy; + u8 beacon_filter_reserved; + __le32 beacon_filter_delta_time; + struct mvm_statistics_bt_activity bt_activity; + __le64 rx_time; + __le64 on_time_rf; + __le64 on_time_scan; + __le64 tx_time; +} __packed; /* STATISTICS_GENERAL_API_S_VER_10 */ + +struct mvm_statistics_general_v8 { + struct mvm_statistics_general_common_v19 common; + __le32 beacon_counter[NUM_MAC_INDEX]; + u8 beacon_average_energy[NUM_MAC_INDEX]; + u8 reserved[4 - (NUM_MAC_INDEX % 4)]; +} __packed; /* STATISTICS_GENERAL_API_S_VER_8 */ + +struct mvm_statistics_general { + struct mvm_statistics_general_common common; + __le32 beacon_counter[MAC_INDEX_AUX]; + u8 beacon_average_energy[MAC_INDEX_AUX]; + u8 reserved[8 - MAC_INDEX_AUX]; +} __packed; /* STATISTICS_GENERAL_API_S_VER_10 */ + +/** + * struct mvm_statistics_load - RX statistics for multi-queue devices + * @air_time: accumulated air time, per mac + * @byte_count: accumulated byte count, per mac + * @pkt_count: accumulated packet count, per mac + * @avg_energy: average RSSI, per station + */ +struct mvm_statistics_load { + __le32 air_time[MAC_INDEX_AUX]; + __le32 byte_count[MAC_INDEX_AUX]; + __le32 pkt_count[MAC_INDEX_AUX]; + u8 avg_energy[IWL_MVM_STATION_COUNT_MAX]; +} __packed; /* STATISTICS_RX_MAC_STATION_S_VER_3 */ + +struct mvm_statistics_load_v1 { + __le32 air_time[NUM_MAC_INDEX]; + __le32 byte_count[NUM_MAC_INDEX]; + __le32 pkt_count[NUM_MAC_INDEX]; + u8 avg_energy[IWL_MVM_STATION_COUNT_MAX]; +} __packed; /* STATISTICS_RX_MAC_STATION_S_VER_1 */ + +struct mvm_statistics_rx { + struct mvm_statistics_rx_phy ofdm; + struct mvm_statistics_rx_phy cck; + struct mvm_statistics_rx_non_phy general; + struct mvm_statistics_rx_ht_phy ofdm_ht; +} __packed; /* STATISTICS_RX_API_S_VER_4 */ + +struct mvm_statistics_rx_v3 { + struct mvm_statistics_rx_phy_v2 ofdm; + struct mvm_statistics_rx_phy_v2 cck; + struct mvm_statistics_rx_non_phy_v3 general; + struct mvm_statistics_rx_ht_phy_v1 ofdm_ht; +} __packed; /* STATISTICS_RX_API_S_VER_3 */ + +/* + * STATISTICS_NOTIFICATION = 0x9d (notification only, not a command) + * + * By default, uCode issues this notification after receiving a beacon + * while associated. To disable this behavior, set DISABLE_NOTIF flag in the + * STATISTICS_CMD (0x9c), below. + */ + +struct iwl_notif_statistics_v10 { + __le32 flag; + struct mvm_statistics_rx_v3 rx; + struct mvm_statistics_tx_v4 tx; + struct mvm_statistics_general_v8 general; +} __packed; /* STATISTICS_NTFY_API_S_VER_10 */ + +struct iwl_notif_statistics_v11 { + __le32 flag; + struct mvm_statistics_rx_v3 rx; + struct mvm_statistics_tx_v4 tx; + struct mvm_statistics_general_v8 general; + struct mvm_statistics_load_v1 load_stats; +} __packed; /* STATISTICS_NTFY_API_S_VER_11 */ + +struct iwl_notif_statistics { + __le32 flag; + struct mvm_statistics_rx rx; + struct mvm_statistics_tx tx; + struct mvm_statistics_general general; + struct mvm_statistics_load load_stats; +} __packed; /* STATISTICS_NTFY_API_S_VER_13 */ + +/** + * enum iwl_statistics_notif_flags - flags used in statistics notification + * @IWL_STATISTICS_REPLY_FLG_CLEAR: statistics were cleared after this report + */ +enum iwl_statistics_notif_flags { + IWL_STATISTICS_REPLY_FLG_CLEAR = 0x1, +}; + +/** + * enum iwl_statistics_cmd_flags - flags used in statistics command + * @IWL_STATISTICS_FLG_CLEAR: request to clear statistics after the report + * that's sent after this command + * @IWL_STATISTICS_FLG_DISABLE_NOTIF: disable unilateral statistics + * notifications + */ +enum iwl_statistics_cmd_flags { + IWL_STATISTICS_FLG_CLEAR = 0x1, + IWL_STATISTICS_FLG_DISABLE_NOTIF = 0x2, +}; + +/** + * struct iwl_statistics_cmd - statistics config command + * @flags: flags from &enum iwl_statistics_cmd_flags + */ +struct iwl_statistics_cmd { + __le32 flags; +} __packed; /* STATISTICS_CMD_API_S_VER_1 */ + +#define MAX_BCAST_FILTER_NUM 8 + +/** + * enum iwl_fw_statistics_type + * + * @FW_STATISTICS_OPERATIONAL: operational statistics + * @FW_STATISTICS_PHY: phy statistics + * @FW_STATISTICS_MAC: mac statistics + * @FW_STATISTICS_RX: rx statistics + * @FW_STATISTICS_TX: tx statistics + * @FW_STATISTICS_DURATION: duration statistics + * @FW_STATISTICS_HE: he statistics + */ +enum iwl_fw_statistics_type { + FW_STATISTICS_OPERATIONAL, + FW_STATISTICS_PHY, + FW_STATISTICS_MAC, + FW_STATISTICS_RX, + FW_STATISTICS_TX, + FW_STATISTICS_DURATION, + FW_STATISTICS_HE, +}; /* FW_STATISTICS_TYPE_API_E_VER_1 */ + +/** + * struct iwl_statistics_ntfy_hdr + * + * @type: struct type + * @version: version of the struct + * @size: size in bytes + */ +struct iwl_statistics_ntfy_hdr { + u8 type; + u8 version; + __le16 size; +}; /* STATISTICS_NTFY_HDR_API_S_VER_1 */ + +/** + * struct iwl_statistics_operational_ntfy + * + * @hdr: general statistics header + * @flags: bitmap of possible notification structures + * @mac_id: mac on which the beacon was received + * @beacon_filter_average_energy: Average energy [-dBm] of the 2 + * antennas. + * @beacon_filter_reason: beacon filter reason + * @radio_temperature: radio temperature + * @air_time: air time + * @beacon_counter: all beacons (both filtered and not filtered) + * @beacon_average_energy: all beacons (both filtered and not + * filtered) + * @beacon_rssi_a: beacon RSSI on antenna A + * @beacon_rssi_b: beacon RSSI on antenna B + * @rx_bytes: per MAC RX byte count + * @rx_time: rx time + * @tx_time: usec the radio is transmitting. + * @on_time_rf: The total time in usec the RF is awake. + * @on_time_scan: usec the radio is awake due to scan. + * @average_energy: in fact it is minus the energy.. + * @reserved: reserved + */ +struct iwl_statistics_operational_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 flags; + __le32 mac_id; + __le32 beacon_filter_average_energy; + __le32 beacon_filter_reason; + __le32 radio_temperature; + __le32 air_time[MAC_INDEX_AUX]; + __le32 beacon_counter[MAC_INDEX_AUX]; + __le32 beacon_average_energy[MAC_INDEX_AUX]; + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 rx_bytes[MAC_INDEX_AUX]; + __le64 rx_time; + __le64 tx_time; + __le64 on_time_rf; + __le64 on_time_scan; + __le32 average_energy[IWL_MVM_STATION_COUNT_MAX]; + __le32 reserved; +} __packed; /* STATISTICS_OPERATIONAL_NTFY_API_S_VER_14 */ + +/** + * struct iwl_statistics_phy_ntfy + * + * @hdr: general statistics header + * RX PHY related statistics + * @energy_and_config: ??? + * @rssi_band: @31:24 rssiAllBand_B, 23:16 rssiInBand_B, 15:8 + * rssiAllBand_A, 7:0 rssiInBand_A + * @agc_word: @31:16 agcWord_B, 15:0 agcWord_A + * @agc_gain: @19:10 agcGain_B, 9:0 agcGain_A + * @dfe_gain: @19:10 dfeGain_B, 9:0 dfeGain_A + * @snr_calc_main: @18:0 snrCalcMain + * @energy_calc_main: @18:0 energyCalcMain + * @snr_calc_aux: @18:0 snrCalcAux + * @dsp_dc_estim_a: @27:14 dspDcEstimQA, 13:0 dspDcEstimIA + * @dsp_dc_estim_b: @27:14 dspDcEstimQB, 13:0 dspDcEstimIB + * @ina_detec_type_and_ofdm_corr_comb: @31:31 inaDetectCckMrc, + * 30:27 inaDetectType, 26:0 ofdmCorrComb + * @cw_corr_comb: @26:0 cwCorrComb + * @rssi_comb: @25:0 rssiComb + * @auto_corr_cck: @23:12 autoCck, 11:00 crossCck + * @ofdm_fine_freq_and_pina_freq_err: @18:7 ofdmFineFreq, 6:0 + * ofdmPinaFreqErr + * @snrm_evm_main: @31:0 snrmEvmMain + * @snrm_evm_aux: @31:0 snrmEvmAux + * @rx_rate: @31:0 rate + * TX PHY related statistics + * @per_chain_enums_and_dsp_atten_a: @perChainEnumsAndDspAtten + * (per version) + * @target_power_and_power_meas_a: @31:16 targetPower_A, 15:0 + * powerMeasuredCalc_A + * @tx_config_as_i_and_ac_a: @31:16 txConfigAsI_A, 15:0 + * txConfigAc_A + * @predist_dcq_and_dci_a: @31:16 predist_dci_A, 15:0 + * predist_dcq_A + * @per_chain_enums_and_dsp_atten_b: @perChainEnumsAndDspAtten + * (per version) + * @target_power_and_power_meas_b: @31:16 targetPower_B, 15:0 + * powerMeasuredCalc_B + * @tx_config_as_i_and_ac_b: @31:16 txConfigAsI_B, 15:0 + * txConfigAc_B + * @predist_dcq_and_dci_b: @31:16 predist_dci_B, 15:0 + * predist_dcq_B + * @tx_rate: @31:0 rate + * @tlc_backoff: @31:0 tlcBackoff + * @mpapd_calib_mode_mpapd_calib_type_a: @31:16 + * mpapdCalibMode_A, 15:0 mpapdCalibType_A + * @psat_and_phy_power_limit_a: @31:16 psat_A, 15:0 + * phyPowerLimit_A + * @sar_and_regulatory_power_limit_a: @31:16 sarPowerLimit_A, + * 15:0 regulatoryPowerLimit_A + * @mpapd_calib_mode_mpapd_calib_type_b: @31:16 + * mpapdCalibMode_B, 15:0 mpapdCalibType_B + * @psat_and_phy_power_limit_b: @31:16 psat_B, 15:0 + * phyPowerLimit_B + * @sar_and_regulatory_power_limit_b: @31:16 sarPowerLimit_B, + * 15:0 regulatoryPowerLimit_B + * @srd_and_driver_power_limits: @31:16 srdPowerLimit, 15:0 + * driverPowerLimit + * @reserved: reserved + */ +struct iwl_statistics_phy_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 energy_and_config; + __le32 rssi_band; + __le32 agc_word; + __le32 agc_gain; + __le32 dfe_gain; + __le32 snr_calc_main; + __le32 energy_calc_main; + __le32 snr_calc_aux; + __le32 dsp_dc_estim_a; + __le32 dsp_dc_estim_b; + __le32 ina_detec_type_and_ofdm_corr_comb; + __le32 cw_corr_comb; + __le32 rssi_comb; + __le32 auto_corr_cck; + __le32 ofdm_fine_freq_and_pina_freq_err; + __le32 snrm_evm_main; + __le32 snrm_evm_aux; + __le32 rx_rate; + __le32 per_chain_enums_and_dsp_atten_a; + __le32 target_power_and_power_meas_a; + __le32 tx_config_as_i_and_ac_a; + __le32 predist_dcq_and_dci_a; + __le32 per_chain_enums_and_dsp_atten_b; + __le32 target_power_and_power_meas_b; + __le32 tx_config_as_i_and_ac_b; + __le32 predist_dcq_and_dci_b; + __le32 tx_rate; + __le32 tlc_backoff; + __le32 mpapd_calib_mode_mpapd_calib_type_a; + __le32 psat_and_phy_power_limit_a; + __le32 sar_and_regulatory_power_limit_a; + __le32 mpapd_calib_mode_mpapd_calib_type_b; + __le32 psat_and_phy_power_limit_b; + __le32 sar_and_regulatory_power_limit_b; + __le32 srd_and_driver_power_limits; + __le32 reserved; +} __packed; /* STATISTICS_PHY_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_mac_ntfy + * + * @hdr: general statistics header + * @bcast_filter_passed_per_mac: bcast filter passed per mac + * @bcast_filter_dropped_per_mac: bcast filter dropped per mac + * @bcast_filter_passed_per_filter: bcast filter passed per filter + * @bcast_filter_dropped_per_filter: bcast filter dropped per filter + * @reserved: reserved + */ +struct iwl_statistics_mac_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 bcast_filter_passed_per_mac[NUM_MAC_INDEX_CDB]; + __le32 bcast_filter_dropped_per_mac[NUM_MAC_INDEX_CDB]; + __le32 bcast_filter_passed_per_filter[MAX_BCAST_FILTER_NUM]; + __le32 bcast_filter_dropped_per_filter[MAX_BCAST_FILTER_NUM]; + __le32 reserved; +} __packed; /* STATISTICS_MAC_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_rx_ntfy + * + * @hdr: general statistics header + * @rx_agg_mpdu_cnt: aggregation frame count (number of + * delimiters) + * @rx_agg_cnt: number of RX Aggregations + * @unsupported_mcs: number of PLCP headers that have rate which + * is unsupported by DSP + * @bogus_cts: CTS received when not expecting CTS + * @bogus_ack: ACK received when not expecting ACK + * @rx_byte_count: ??? + * @rx_packet_count: ??? + * @missed_beacons: ??? + * @unresponded_rts: un-responded RTS, due to NAV not zero + * @rxe_frame_limit_overrun: RXE got frame limit overrun + * @sent_ba_rsp_cnt: BA response TX count + * @late_rx_handle: count the number of times the RX path was + * aborted due to late entry + * @num_bt_kills: ??? + * @reserved: reserved + */ +struct iwl_statistics_rx_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 rx_agg_mpdu_cnt; + __le32 rx_agg_cnt; + __le32 unsupported_mcs; + __le32 bogus_cts; + __le32 bogus_ack; + __le32 rx_byte_count[MAC_INDEX_AUX]; + __le32 rx_packet_count[MAC_INDEX_AUX]; + __le32 missed_beacons; + __le32 unresponded_rts; + __le32 rxe_frame_limit_overrun; + __le32 sent_ba_rsp_cnt; + __le32 late_rx_handle; + __le32 num_bt_kills; + __le32 reserved; +} __packed; /* STATISTICS_RX_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_tx_ntfy + * + * @hdr: general statistics header + * @cts_timeout: timeout when waiting for CTS + * @ack_timeout: timeout when waiting for ACK + * @dump_msdu_cnt: number of MSDUs that were dumped due to any + * reason + * @burst_abort_missing_next_frame_cnt: number of times a burst + * was aborted due to missing next frame bytes in txfifo + * number of times got timeout when waiting for CTS/ACK/BA and energy was + * detected just after sending the RTS/DATA. this statistics may help getting + * interesting indicators, like the likelihood of collision (so the benefit of + * protection may be estimated Vs. its cost). Or how many of the failures are + * due to collision and how many due to SNR. + * For Link-quality the CTS collision indication is more reliable then the ACK + * collision indication as the RTS frame is short and has more chance that the + * frame/s which caused the collision continue after the RTS was sent. + * @cts_timeout_collision: ??? + * ACK/BA failed and energy as detected after DATA + * Note: to get the collision ratio need to: + * ackOrBaTimeoutCollision / (ack_timeout + ba_timeout) + * @ack_or_ba_timeout_collision: ??? + * @ba_timeout: timeout when waiting for immediate BA response + * @ba_reschedule_frames: failed to get BA response and + * rescheduled all the non-ACKed frames + * gives the avarage number of frames inside aggregation + * @scd_query_agg_frame_cnt: ??? + * @scd_query_no_agg: scheduler query prevented aggregation + * @scd_query_agg: scheduler query allowed aggregation + * @scd_query_mismatch: scheduler query inaccurate, either too + * short or too long + * @agg_terminated_underrun: aggregation was terminated due to + * underrun + * @agg_terminated_bt_prio_kill: aggregation was terminated due + * to BT + * @tx_kill_on_long_retry: count the tx frames dropped due to + * long retry limit (DATA frame failed) + * @tx_kill_on_short_retry: count the tx frames dropped due to + * short retry limit (RTS frame failed) + * TX deffer on energy. This counter is reset on each successful transmit. + * When timer exceed TX deffer limit than will be uCode assert. + * @tx_deffer_counter: ??? + * @tx_deffer_base_time: Keep the time of the last successful + * transmit + * @tx_underrun: TX killed due to underrun + * @bt_defer: TX deferred due to BT priority, so probably TX was + * not started. + * @tx_kill_on_dsp_timeout: TX killed on DSP problem detected + * @tx_kill_on_immediate_quiet: TX killed due to immediate quiet + * @kill_ba_cnt: number of times sending BA failed + * @kill_ack_cnt: number of times sending ACK failed + * @kill_cts_cnt: number of times sending CTS failed + * @burst_terminated: Count burst or fragmentation termination + * occurrence + * @late_tx_vec_wr_cnt: ??? + * TX is not sent because ucode failed to notify the TRM in SIFS-delta from + * ON_AIR deassertion. + * @late_rx2_tx_cnt: ??? + * @scd_query_cnt: count the times SCD query was done to check + * for TX AGG + * @tx_frames_acked_in_agg: count the number of frames + * transmitted inside AGG and were successful + * @last_tx_ch_width_indx: ??? + * number of deferred TX per channel width, 0 - 20, 1/2/3 - 40/80/160 + * @rx_detected_per_ch_width: ??? + * @success_per_ch_width: ??? + * @fail_per_ch_width: ??? + * @reserved: reserved + */ +struct iwl_statistics_tx_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 dump_msdu_cnt; + __le32 burst_abort_missing_next_frame_cnt; + __le32 cts_timeout_collision; + __le32 ack_or_ba_timeout_collision; + __le32 ba_timeout; + __le32 ba_reschedule_frames; + __le32 scd_query_agg_frame_cnt; + __le32 scd_query_no_agg; + __le32 scd_query_agg; + __le32 scd_query_mismatch; + __le32 agg_terminated_underrun; + __le32 agg_terminated_bt_prio_kill; + __le32 tx_kill_on_long_retry; + __le32 tx_kill_on_short_retry; + __le32 tx_deffer_counter; + __le32 tx_deffer_base_time; + __le32 tx_underrun; + __le32 bt_defer; + __le32 tx_kill_on_dsp_timeout; + __le32 tx_kill_on_immediate_quiet; + __le32 kill_ba_cnt; + __le32 kill_ack_cnt; + __le32 kill_cts_cnt; + __le32 burst_terminated; + __le32 late_tx_vec_wr_cnt; + __le32 late_rx2_tx_cnt; + __le32 scd_query_cnt; + __le32 tx_frames_acked_in_agg; + __le32 last_tx_ch_width_indx; + __le32 rx_detected_per_ch_width[4]; + __le32 success_per_ch_width[4]; + __le32 fail_per_ch_width[4]; + __le32 reserved; +} __packed; /* STATISTICS_TX_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_duration_ntfy + * + * @hdr: general statistics header + * @cont_burst_chk_cnt: number of times continuation or + * fragmentation or bursting was checked + * @cont_burst_cnt: number of times continuation or fragmentation + * or bursting was successful + * @wait_for_silence_timeout_cnt: ??? + * @reserved: reserved + */ +struct iwl_statistics_duration_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 cont_burst_chk_cnt; + __le32 cont_burst_cnt; + __le32 wait_for_silence_timeout_cnt; + __le32 reserved; +} __packed; /* STATISTICS_DURATION_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_he_ntfy + * + * @hdr: general statistics header + * received HE frames + * @rx_siga_valid_cnt: rx HE SIG-A valid + * @rx_siga_invalid_cnt: rx HE SIG-A invalid + * received HE frames w/ valid Sig-A + * @rx_trig_based_frame_cnt: rx HE-TB (trig-based) + * @rx_su_frame_cnt: rx HE-SU + * @rx_sigb_invalid_cnt: rx (suspected) HE-MU w/ bad SIG-B + * @rx_our_bss_color_cnt: rx valid HE SIG-A w/ our BSS color + * @rx_other_bss_color_cnt: rx valid HE SIG-A w/ other BSS color + * @rx_zero_bss_color_cnt: ??? + * received HE-MU frames w/ good Sig-B + * @rx_mu_for_us_cnt: match AID + * @rx_mu_not_for_us_cnt: no matched AID + * received HE-MU frames for us (w/ our AID) + * @rx_mu_nss_ar: 0 - SISO, 1 - MIMO2 + * @rx_mu_mimo_cnt: full BW RU, compressed SIG-B + * @rx_mu_ru_bw_ar: MU alloc, MHz: 0 - 2, 1 - 5, 2 - 10, 3 - 20, + * 4 - 40, 5 - 80, 6 - 160 + * received trigger frames + * @rx_trig_for_us_cnt: ??? + * @rx_trig_not_for_us_cnt: ??? + * trigger for us + * @rx_trig_with_cs_req_cnt: ??? + * @rx_trig_type_ar: ??? + * @rx_trig_in_agg_cnt: ??? + * basic trigger for us allocations + * @rx_basic_trig_alloc_nss_ar: ??? + * @rx_basic_trig_alloc_mu_mimo_cnt: ??? + * @rx_basic_trig_alloc_ru_bw_ar: ??? + * @rx_basic_trig_total_byte_cnt: ??? + * trig-based TX + * @tx_trig_based_cs_req_fail_cnt: ??? + * @tx_trig_based_sifs_ok_cnt: ??? + * @tx_trig_based_sifs_fail_cnt: ??? + * @tx_trig_based_byte_cnt: ??? + * @tx_trig_based_pad_byte_cnt: ??? + * @tx_trig_based_frame_cnt: ??? + * @tx_trig_based_acked_frame_cnt: ??? + * @tx_trig_based_ack_timeout_cnt: ??? + * HE-SU TX + * @tx_su_frame_cnt: ??? + * EDCA <--> MU-EDCA transitions + * @tx_edca_to_mu_edca_cnt: ??? + * @tx_mu_edca_to_edca_by_timeout_cnt: ??? + * @tx_mu_edca_to_edca_by_ack_fail_cnt: ??? + * @tx_mu_edca_to_edca_by_small_alloc_cnt: ??? + * @reserved: reserved + */ +struct iwl_statistics_he_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 rx_siga_valid_cnt; + __le32 rx_siga_invalid_cnt; + __le32 rx_trig_based_frame_cnt; + __le32 rx_su_frame_cnt; + __le32 rx_sigb_invalid_cnt; + __le32 rx_our_bss_color_cnt; + __le32 rx_other_bss_color_cnt; + __le32 rx_zero_bss_color_cnt; + __le32 rx_mu_for_us_cnt; + __le32 rx_mu_not_for_us_cnt; + __le32 rx_mu_nss_ar[2]; + __le32 rx_mu_mimo_cnt; + __le32 rx_mu_ru_bw_ar[7]; + __le32 rx_trig_for_us_cnt; + __le32 rx_trig_not_for_us_cnt; + __le32 rx_trig_with_cs_req_cnt; + __le32 rx_trig_type_ar[8 + 1]; + __le32 rx_trig_in_agg_cnt; + __le32 rx_basic_trig_alloc_nss_ar[2]; + __le32 rx_basic_trig_alloc_mu_mimo_cnt; + __le32 rx_basic_trig_alloc_ru_bw_ar[7]; + __le32 rx_basic_trig_total_byte_cnt; + __le32 tx_trig_based_cs_req_fail_cnt; + __le32 tx_trig_based_sifs_ok_cnt; + __le32 tx_trig_based_sifs_fail_cnt; + __le32 tx_trig_based_byte_cnt; + __le32 tx_trig_based_pad_byte_cnt; + __le32 tx_trig_based_frame_cnt; + __le32 tx_trig_based_acked_frame_cnt; + __le32 tx_trig_based_ack_timeout_cnt; + __le32 tx_su_frame_cnt; + __le32 tx_edca_to_mu_edca_cnt; + __le32 tx_mu_edca_to_edca_by_timeout_cnt; + __le32 tx_mu_edca_to_edca_by_ack_fail_cnt; + __le32 tx_mu_edca_to_edca_by_small_alloc_cnt; + __le32 reserved; +} __packed; /* STATISTICS_HE_NTFY_API_S_VER_1 */ + +#endif /* __iwl_fw_api_stats_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/tdls.h b/sys/contrib/dev/iwlwifi/fw/api/tdls.h new file mode 100644 index 00000000000..14d35000abe --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/tdls.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_tdls_h__ +#define __iwl_fw_api_tdls_h__ + +#include "fw/api/tx.h" +#include "fw/api/phy-ctxt.h" + +#define IWL_MVM_TDLS_STA_COUNT 4 + +/* Type of TDLS request */ +enum iwl_tdls_channel_switch_type { + TDLS_SEND_CHAN_SW_REQ = 0, + TDLS_SEND_CHAN_SW_RESP_AND_MOVE_CH, + TDLS_MOVE_CH, +}; /* TDLS_STA_CHANNEL_SWITCH_CMD_TYPE_API_E_VER_1 */ + +/** + * struct iwl_tdls_channel_switch_timing - Switch timing in TDLS channel-switch + * @frame_timestamp: GP2 timestamp of channel-switch request/response packet + * received from peer + * @max_offchan_duration: What amount of microseconds out of a DTIM is given + * to the TDLS off-channel communication. For instance if the DTIM is + * 200TU and the TDLS peer is to be given 25% of the time, the value + * given will be 50TU, or 50 * 1024 if translated into microseconds. + * @switch_time: switch time the peer sent in its channel switch timing IE + * @switch_timeout: switch timeout the peer sent in its channel switch timing IE + */ +struct iwl_tdls_channel_switch_timing { + __le32 frame_timestamp; /* GP2 time of peer packet Rx */ + __le32 max_offchan_duration; /* given in micro-seconds */ + __le32 switch_time; /* given in micro-seconds */ + __le32 switch_timeout; /* given in micro-seconds */ +} __packed; /* TDLS_STA_CHANNEL_SWITCH_TIMING_DATA_API_S_VER_1 */ + +#define IWL_TDLS_CH_SW_FRAME_MAX_SIZE 200 + +/** + * struct iwl_tdls_channel_switch_frame - TDLS channel switch frame template + * + * A template representing a TDLS channel-switch request or response frame + * + * @switch_time_offset: offset to the channel switch timing IE in the template + * @tx_cmd: Tx parameters for the frame + * @data: frame data + */ +struct iwl_tdls_channel_switch_frame { + __le32 switch_time_offset; + struct iwl_tx_cmd tx_cmd; + u8 data[IWL_TDLS_CH_SW_FRAME_MAX_SIZE]; +} __packed; /* TDLS_STA_CHANNEL_SWITCH_FRAME_API_S_VER_1 */ + +/** + * struct iwl_tdls_channel_switch_cmd_tail - tail of iwl_tdls_channel_switch_cmd + * + * @timing: timing related data for command + * @frame: channel-switch request/response template, depending to switch_type + */ +struct iwl_tdls_channel_switch_cmd_tail { + struct iwl_tdls_channel_switch_timing timing; + struct iwl_tdls_channel_switch_frame frame; +} __packed; + +/** + * struct iwl_tdls_channel_switch_cmd - TDLS channel switch command + * + * The command is sent to initiate a channel switch and also in response to + * incoming TDLS channel-switch request/response packets from remote peers. + * + * @switch_type: see &enum iwl_tdls_channel_switch_type + * @peer_sta_id: station id of TDLS peer + * @ci: channel we switch to + * @tail: command tail + */ +struct iwl_tdls_channel_switch_cmd { + u8 switch_type; + __le32 peer_sta_id; + struct iwl_fw_channel_info ci; + struct iwl_tdls_channel_switch_cmd_tail tail; +} __packed; /* TDLS_STA_CHANNEL_SWITCH_CMD_API_S_VER_1 */ + +/** + * struct iwl_tdls_channel_switch_notif - TDLS channel switch start notification + * + * @status: non-zero on success + * @offchannel_duration: duration given in microseconds + * @sta_id: peer currently performing the channel-switch with + */ +struct iwl_tdls_channel_switch_notif { + __le32 status; + __le32 offchannel_duration; + __le32 sta_id; +} __packed; /* TDLS_STA_CHANNEL_SWITCH_NTFY_API_S_VER_1 */ + +/** + * struct iwl_tdls_sta_info - TDLS station info + * + * @sta_id: station id of the TDLS peer + * @tx_to_peer_tid: TID reserved vs. the peer for FW based Tx + * @tx_to_peer_ssn: initial SSN the FW should use for Tx on its TID vs the peer + * @is_initiator: 1 if the peer is the TDLS link initiator, 0 otherwise + */ +struct iwl_tdls_sta_info { + u8 sta_id; + u8 tx_to_peer_tid; + __le16 tx_to_peer_ssn; + __le32 is_initiator; +} __packed; /* TDLS_STA_INFO_VER_1 */ + +/** + * struct iwl_tdls_config_cmd - TDLS basic config command + * + * @id_and_color: MAC id and color being configured + * @tdls_peer_count: amount of currently connected TDLS peers + * @tx_to_ap_tid: TID reverved vs. the AP for FW based Tx + * @tx_to_ap_ssn: initial SSN the FW should use for Tx on its TID vs. the AP + * @sta_info: per-station info. Only the first tdls_peer_count entries are set + * @pti_req_data_offset: offset of network-level data for the PTI template + * @pti_req_tx_cmd: Tx parameters for PTI request template + * @pti_req_template: PTI request template data + */ +struct iwl_tdls_config_cmd { + __le32 id_and_color; /* mac id and color */ + u8 tdls_peer_count; + u8 tx_to_ap_tid; + __le16 tx_to_ap_ssn; + struct iwl_tdls_sta_info sta_info[IWL_MVM_TDLS_STA_COUNT]; + + __le32 pti_req_data_offset; + struct iwl_tx_cmd pti_req_tx_cmd; + u8 pti_req_template[0]; +} __packed; /* TDLS_CONFIG_CMD_API_S_VER_1 */ + +/** + * struct iwl_tdls_config_sta_info_res - TDLS per-station config information + * + * @sta_id: station id of the TDLS peer + * @tx_to_peer_last_seq: last sequence number used by FW during FW-based Tx to + * the peer + */ +struct iwl_tdls_config_sta_info_res { + __le16 sta_id; + __le16 tx_to_peer_last_seq; +} __packed; /* TDLS_STA_INFO_RSP_VER_1 */ + +/** + * struct iwl_tdls_config_res - TDLS config information from FW + * + * @tx_to_ap_last_seq: last sequence number used by FW during FW-based Tx to AP + * @sta_info: per-station TDLS config information + */ +struct iwl_tdls_config_res { + __le32 tx_to_ap_last_seq; + struct iwl_tdls_config_sta_info_res sta_info[IWL_MVM_TDLS_STA_COUNT]; +} __packed; /* TDLS_CONFIG_RSP_API_S_VER_1 */ + +#endif /* __iwl_fw_api_tdls_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/time-event.h b/sys/contrib/dev/iwlwifi/fw/api/time-event.h new file mode 100644 index 00000000000..904cd78a9fa --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/time-event.h @@ -0,0 +1,418 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_time_event_h__ +#define __iwl_fw_api_time_event_h__ + +#include "fw/api/phy-ctxt.h" + +/* Time Event types, according to MAC type */ +enum iwl_time_event_type { + /* BSS Station Events */ + TE_BSS_STA_AGGRESSIVE_ASSOC, + TE_BSS_STA_ASSOC, + TE_BSS_EAP_DHCP_PROT, + TE_BSS_QUIET_PERIOD, + + /* P2P Device Events */ + TE_P2P_DEVICE_DISCOVERABLE, + TE_P2P_DEVICE_LISTEN, + TE_P2P_DEVICE_ACTION_SCAN, + TE_P2P_DEVICE_FULL_SCAN, + + /* P2P Client Events */ + TE_P2P_CLIENT_AGGRESSIVE_ASSOC, + TE_P2P_CLIENT_ASSOC, + TE_P2P_CLIENT_QUIET_PERIOD, + + /* P2P GO Events */ + TE_P2P_GO_ASSOC_PROT, + TE_P2P_GO_REPETITIVET_NOA, + TE_P2P_GO_CT_WINDOW, + + /* WiDi Sync Events */ + TE_WIDI_TX_SYNC, + + /* Channel Switch NoA */ + TE_CHANNEL_SWITCH_PERIOD, + + TE_MAX +}; /* MAC_EVENT_TYPE_API_E_VER_1 */ + +/* Time event - defines for command API v1 */ + +/* + * @TE_V1_FRAG_NONE: fragmentation of the time event is NOT allowed. + * @TE_V1_FRAG_SINGLE: fragmentation of the time event is allowed, but only + * the first fragment is scheduled. + * @TE_V1_FRAG_DUAL: fragmentation of the time event is allowed, but only + * the first 2 fragments are scheduled. + * @TE_V1_FRAG_ENDLESS: fragmentation of the time event is allowed, and any + * number of fragments are valid. + * + * Other than the constant defined above, specifying a fragmentation value 'x' + * means that the event can be fragmented but only the first 'x' will be + * scheduled. + */ +enum { + TE_V1_FRAG_NONE = 0, + TE_V1_FRAG_SINGLE = 1, + TE_V1_FRAG_DUAL = 2, + TE_V1_FRAG_ENDLESS = 0xffffffff +}; + +/* If a Time Event can be fragmented, this is the max number of fragments */ +#define TE_V1_FRAG_MAX_MSK 0x0fffffff +/* Repeat the time event endlessly (until removed) */ +#define TE_V1_REPEAT_ENDLESS 0xffffffff +/* If a Time Event has bounded repetitions, this is the maximal value */ +#define TE_V1_REPEAT_MAX_MSK_V1 0x0fffffff + +/* Time Event dependencies: none, on another TE, or in a specific time */ +enum { + TE_V1_INDEPENDENT = 0, + TE_V1_DEP_OTHER = BIT(0), + TE_V1_DEP_TSF = BIT(1), + TE_V1_EVENT_SOCIOPATHIC = BIT(2), +}; /* MAC_EVENT_DEPENDENCY_POLICY_API_E_VER_2 */ + +/* + * @TE_V1_NOTIF_NONE: no notifications + * @TE_V1_NOTIF_HOST_EVENT_START: request/receive notification on event start + * @TE_V1_NOTIF_HOST_EVENT_END:request/receive notification on event end + * @TE_V1_NOTIF_INTERNAL_EVENT_START: internal FW use + * @TE_V1_NOTIF_INTERNAL_EVENT_END: internal FW use. + * @TE_V1_NOTIF_HOST_FRAG_START: request/receive notification on frag start + * @TE_V1_NOTIF_HOST_FRAG_END:request/receive notification on frag end + * @TE_V1_NOTIF_INTERNAL_FRAG_START: internal FW use. + * @TE_V1_NOTIF_INTERNAL_FRAG_END: internal FW use. + * + * Supported Time event notifications configuration. + * A notification (both event and fragment) includes a status indicating weather + * the FW was able to schedule the event or not. For fragment start/end + * notification the status is always success. There is no start/end fragment + * notification for monolithic events. + */ +enum { + TE_V1_NOTIF_NONE = 0, + TE_V1_NOTIF_HOST_EVENT_START = BIT(0), + TE_V1_NOTIF_HOST_EVENT_END = BIT(1), + TE_V1_NOTIF_INTERNAL_EVENT_START = BIT(2), + TE_V1_NOTIF_INTERNAL_EVENT_END = BIT(3), + TE_V1_NOTIF_HOST_FRAG_START = BIT(4), + TE_V1_NOTIF_HOST_FRAG_END = BIT(5), + TE_V1_NOTIF_INTERNAL_FRAG_START = BIT(6), + TE_V1_NOTIF_INTERNAL_FRAG_END = BIT(7), +}; /* MAC_EVENT_ACTION_API_E_VER_2 */ + +/* Time event - defines for command API */ + +/* + * @TE_V2_FRAG_NONE: fragmentation of the time event is NOT allowed. + * @TE_V2_FRAG_SINGLE: fragmentation of the time event is allowed, but only + * the first fragment is scheduled. + * @TE_V2_FRAG_DUAL: fragmentation of the time event is allowed, but only + * the first 2 fragments are scheduled. + * @TE_V2_FRAG_ENDLESS: fragmentation of the time event is allowed, and any + * number of fragments are valid. + * + * Other than the constant defined above, specifying a fragmentation value 'x' + * means that the event can be fragmented but only the first 'x' will be + * scheduled. + */ +enum { + TE_V2_FRAG_NONE = 0, + TE_V2_FRAG_SINGLE = 1, + TE_V2_FRAG_DUAL = 2, + TE_V2_FRAG_MAX = 0xfe, + TE_V2_FRAG_ENDLESS = 0xff +}; + +/* Repeat the time event endlessly (until removed) */ +#define TE_V2_REPEAT_ENDLESS 0xff +/* If a Time Event has bounded repetitions, this is the maximal value */ +#define TE_V2_REPEAT_MAX 0xfe + +#define TE_V2_PLACEMENT_POS 12 +#define TE_V2_ABSENCE_POS 15 + +/** + * enum iwl_time_event_policy - Time event policy values + * A notification (both event and fragment) includes a status indicating weather + * the FW was able to schedule the event or not. For fragment start/end + * notification the status is always success. There is no start/end fragment + * notification for monolithic events. + * + * @TE_V2_DEFAULT_POLICY: independent, social, present, unoticable + * @TE_V2_NOTIF_HOST_EVENT_START: request/receive notification on event start + * @TE_V2_NOTIF_HOST_EVENT_END:request/receive notification on event end + * @TE_V2_NOTIF_INTERNAL_EVENT_START: internal FW use + * @TE_V2_NOTIF_INTERNAL_EVENT_END: internal FW use. + * @TE_V2_NOTIF_HOST_FRAG_START: request/receive notification on frag start + * @TE_V2_NOTIF_HOST_FRAG_END:request/receive notification on frag end + * @TE_V2_NOTIF_INTERNAL_FRAG_START: internal FW use. + * @TE_V2_NOTIF_INTERNAL_FRAG_END: internal FW use. + * @TE_V2_START_IMMEDIATELY: start time event immediately + * @TE_V2_DEP_OTHER: depends on another time event + * @TE_V2_DEP_TSF: depends on a specific time + * @TE_V2_EVENT_SOCIOPATHIC: can't co-exist with other events of tha same MAC + * @TE_V2_ABSENCE: are we present or absent during the Time Event. + */ +enum iwl_time_event_policy { + TE_V2_DEFAULT_POLICY = 0x0, + + /* notifications (event start/stop, fragment start/stop) */ + TE_V2_NOTIF_HOST_EVENT_START = BIT(0), + TE_V2_NOTIF_HOST_EVENT_END = BIT(1), + TE_V2_NOTIF_INTERNAL_EVENT_START = BIT(2), + TE_V2_NOTIF_INTERNAL_EVENT_END = BIT(3), + + TE_V2_NOTIF_HOST_FRAG_START = BIT(4), + TE_V2_NOTIF_HOST_FRAG_END = BIT(5), + TE_V2_NOTIF_INTERNAL_FRAG_START = BIT(6), + TE_V2_NOTIF_INTERNAL_FRAG_END = BIT(7), + TE_V2_START_IMMEDIATELY = BIT(11), + + /* placement characteristics */ + TE_V2_DEP_OTHER = BIT(TE_V2_PLACEMENT_POS), + TE_V2_DEP_TSF = BIT(TE_V2_PLACEMENT_POS + 1), + TE_V2_EVENT_SOCIOPATHIC = BIT(TE_V2_PLACEMENT_POS + 2), + + /* are we present or absent during the Time Event. */ + TE_V2_ABSENCE = BIT(TE_V2_ABSENCE_POS), +}; + +/** + * struct iwl_time_event_cmd - configuring Time Events + * with struct MAC_TIME_EVENT_DATA_API_S_VER_2 (see also + * with version 1. determined by IWL_UCODE_TLV_FLAGS) + * ( TIME_EVENT_CMD = 0x29 ) + * @id_and_color: ID and color of the relevant MAC, + * &enum iwl_ctxt_id_and_color + * @action: action to perform, one of &enum iwl_ctxt_action + * @id: this field has two meanings, depending on the action: + * If the action is ADD, then it means the type of event to add. + * For all other actions it is the unique event ID assigned when the + * event was added by the FW. + * @apply_time: When to start the Time Event (in GP2) + * @max_delay: maximum delay to event's start (apply time), in TU + * @depends_on: the unique ID of the event we depend on (if any) + * @interval: interval between repetitions, in TU + * @duration: duration of event in TU + * @repeat: how many repetitions to do, can be TE_REPEAT_ENDLESS + * @max_frags: maximal number of fragments the Time Event can be divided to + * @policy: defines whether uCode shall notify the host or other uCode modules + * on event and/or fragment start and/or end + * using one of TE_INDEPENDENT, TE_DEP_OTHER, TE_DEP_TSF + * TE_EVENT_SOCIOPATHIC + * using TE_ABSENCE and using TE_NOTIF_*, + * &enum iwl_time_event_policy + */ +struct iwl_time_event_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + __le32 id; + /* MAC_TIME_EVENT_DATA_API_S_VER_2 */ + __le32 apply_time; + __le32 max_delay; + __le32 depends_on; + __le32 interval; + __le32 duration; + u8 repeat; + u8 max_frags; + __le16 policy; +} __packed; /* MAC_TIME_EVENT_CMD_API_S_VER_2 */ + +/** + * struct iwl_time_event_resp - response structure to iwl_time_event_cmd + * @status: bit 0 indicates success, all others specify errors + * @id: the Time Event type + * @unique_id: the unique ID assigned (in ADD) or given (others) to the TE + * @id_and_color: ID and color of the relevant MAC, + * &enum iwl_ctxt_id_and_color + */ +struct iwl_time_event_resp { + __le32 status; + __le32 id; + __le32 unique_id; + __le32 id_and_color; +} __packed; /* MAC_TIME_EVENT_RSP_API_S_VER_1 */ + +/** + * struct iwl_time_event_notif - notifications of time event start/stop + * ( TIME_EVENT_NOTIFICATION = 0x2a ) + * @timestamp: action timestamp in GP2 + * @session_id: session's unique id + * @unique_id: unique id of the Time Event itself + * @id_and_color: ID and color of the relevant MAC + * @action: &enum iwl_time_event_policy + * @status: true if scheduled, false otherwise (not executed) + */ +struct iwl_time_event_notif { + __le32 timestamp; + __le32 session_id; + __le32 unique_id; + __le32 id_and_color; + __le32 action; + __le32 status; +} __packed; /* MAC_TIME_EVENT_NTFY_API_S_VER_1 */ + +/* + * struct iwl_hs20_roc_req_tail - tail of iwl_hs20_roc_req + * + * @node_addr: Our MAC Address + * @reserved: reserved for alignment + * @apply_time: GP2 value to start (should always be the current GP2 value) + * @apply_time_max_delay: Maximum apply time delay value in TU. Defines max + * time by which start of the event is allowed to be postponed. + * @duration: event duration in TU To calculate event duration: + * timeEventDuration = min(duration, remainingQuota) + */ +struct iwl_hs20_roc_req_tail { + u8 node_addr[ETH_ALEN]; + __le16 reserved; + __le32 apply_time; + __le32 apply_time_max_delay; + __le32 duration; +} __packed; + +/* + * Aux ROC command + * + * Command requests the firmware to create a time event for a certain duration + * and remain on the given channel. This is done by using the Aux framework in + * the FW. + * The command was first used for Hot Spot issues - but can be used regardless + * to Hot Spot. + * + * ( HOT_SPOT_CMD 0x53 ) + * + * @id_and_color: ID and color of the MAC + * @action: action to perform, one of FW_CTXT_ACTION_* + * @event_unique_id: If the action FW_CTXT_ACTION_REMOVE then the + * event_unique_id should be the id of the time event assigned by ucode. + * Otherwise ignore the event_unique_id. + * @sta_id_and_color: station id and color, resumed during "Remain On Channel" + * activity. + * @channel_info: channel info + */ +struct iwl_hs20_roc_req { + /* COMMON_INDEX_HDR_API_S_VER_1 hdr */ + __le32 id_and_color; + __le32 action; + __le32 event_unique_id; + __le32 sta_id_and_color; + struct iwl_fw_channel_info channel_info; + struct iwl_hs20_roc_req_tail tail; +} __packed; /* HOT_SPOT_CMD_API_S_VER_1 */ + +/* + * values for AUX ROC result values + */ +enum iwl_mvm_hot_spot { + HOT_SPOT_RSP_STATUS_OK, + HOT_SPOT_RSP_STATUS_TOO_MANY_EVENTS, + HOT_SPOT_MAX_NUM_OF_SESSIONS, +}; + +/* + * Aux ROC command response + * + * In response to iwl_hs20_roc_req the FW sends this command to notify the + * driver the uid of the timevent. + * + * ( HOT_SPOT_CMD 0x53 ) + * + * @event_unique_id: Unique ID of time event assigned by ucode + * @status: Return status 0 is success, all the rest used for specific errors + */ +struct iwl_hs20_roc_res { + __le32 event_unique_id; + __le32 status; +} __packed; /* HOT_SPOT_RSP_API_S_VER_1 */ + +/** + * enum iwl_mvm_session_prot_conf_id - session protection's configurations + * @SESSION_PROTECT_CONF_ASSOC: Start a session protection for association. + * The firmware will allocate two events. + * Valid for BSS_STA and P2P_STA. + * * A rather short event that can't be fragmented and with a very + * high priority. If every goes well (99% of the cases) the + * association should complete within this first event. During + * that event, no other activity will happen in the firmware, + * which is why it can't be too long. + * The length of this event is hard-coded in the firmware: 300TUs. + * * Another event which can be much longer (it's duration is + * configurable by the driver) which has a slightly lower + * priority and that can be fragmented allowing other activities + * to run while this event is running. + * The firmware will automatically remove both events once the driver sets + * the BSS MAC as associated. Neither of the events will be removed + * for the P2P_STA MAC. + * Only the duration is configurable for this protection. + * @SESSION_PROTECT_CONF_GO_CLIENT_ASSOC: not used + * @SESSION_PROTECT_CONF_P2P_DEVICE_DISCOV: Schedule the P2P Device to be in + * listen mode. Will be fragmented. Valid only on the P2P Device MAC. + * Valid only on the P2P Device MAC. The firmware will take into account + * the duration, the interval and the repetition count. + * @SESSION_PROTECT_CONF_P2P_GO_NEGOTIATION: Schedule the P2P Device to be be + * able to run the GO Negotiation. Will not be fragmented and not + * repetitive. Valid only on the P2P Device MAC. Only the duration will + * be taken into account. + * @SESSION_PROTECT_CONF_MAX_ID: not used + */ +enum iwl_mvm_session_prot_conf_id { + SESSION_PROTECT_CONF_ASSOC, + SESSION_PROTECT_CONF_GO_CLIENT_ASSOC, + SESSION_PROTECT_CONF_P2P_DEVICE_DISCOV, + SESSION_PROTECT_CONF_P2P_GO_NEGOTIATION, + SESSION_PROTECT_CONF_MAX_ID, +}; /* SESSION_PROTECTION_CONF_ID_E_VER_1 */ + +/** + * struct iwl_mvm_session_prot_cmd - configure a session protection + * @id_and_color: the id and color of the mac for which this session protection + * is sent + * @action: can be either FW_CTXT_ACTION_ADD or FW_CTXT_ACTION_REMOVE + * @conf_id: see &enum iwl_mvm_session_prot_conf_id + * @duration_tu: the duration of the whole protection in TUs. + * @repetition_count: not used + * @interval: not used + * + * Note: the session protection will always be scheduled to start as + * early as possible, but the maximum delay is configuration dependent. + * The firmware supports only one concurrent session protection per vif. + * Adding a new session protection will remove any currently running session. + */ +struct iwl_mvm_session_prot_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 hdr */ + __le32 id_and_color; + __le32 action; + __le32 conf_id; + __le32 duration_tu; + __le32 repetition_count; + __le32 interval; +} __packed; /* SESSION_PROTECTION_CMD_API_S_VER_1 */ + +/** + * struct iwl_mvm_session_prot_notif - session protection started / ended + * @mac_id: the mac id for which the session protection started / ended + * @status: 1 means success, 0 means failure + * @start: 1 means the session protection started, 0 means it ended + * @conf_id: see &enum iwl_mvm_session_prot_conf_id + * + * Note that any session protection will always get two notifications: start + * and end even the firmware could not schedule it. + */ +struct iwl_mvm_session_prot_notif { + __le32 mac_id; + __le32 status; + __le32 start; + __le32 conf_id; +} __packed; /* SESSION_PROTECTION_NOTIFICATION_API_S_VER_2 */ + +#endif /* __iwl_fw_api_time_event_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/tx.h b/sys/contrib/dev/iwlwifi/fw/api/tx.h new file mode 100644 index 00000000000..9b3bce83efb --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/tx.h @@ -0,0 +1,952 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_tx_h__ +#define __iwl_fw_api_tx_h__ +#include + +/** + * enum iwl_tx_flags - bitmasks for tx_flags in TX command + * @TX_CMD_FLG_PROT_REQUIRE: use RTS or CTS-to-self to protect the frame + * @TX_CMD_FLG_WRITE_TX_POWER: update current tx power value in the mgmt frame + * @TX_CMD_FLG_ACK: expect ACK from receiving station + * @TX_CMD_FLG_STA_RATE: use RS table with initial index from the TX command. + * Otherwise, use rate_n_flags from the TX command + * @TX_CMD_FLG_BAR: this frame is a BA request, immediate BAR is expected + * Must set TX_CMD_FLG_ACK with this flag. + * @TX_CMD_FLG_TXOP_PROT: TXOP protection requested + * @TX_CMD_FLG_VHT_NDPA: mark frame is NDPA for VHT beamformer sequence + * @TX_CMD_FLG_HT_NDPA: mark frame is NDPA for HT beamformer sequence + * @TX_CMD_FLG_CSI_FDBK2HOST: mark to send feedback to host (only if good CRC) + * @TX_CMD_FLG_BT_PRIO_MASK: BT priority value + * @TX_CMD_FLG_BT_PRIO_POS: the position of the BT priority (bit 11 is ignored + * on old firmwares). + * @TX_CMD_FLG_BT_DIS: disable BT priority for this frame + * @TX_CMD_FLG_SEQ_CTL: set if FW should override the sequence control. + * Should be set for mgmt, non-QOS data, mcast, bcast and in scan command + * @TX_CMD_FLG_MORE_FRAG: this frame is non-last MPDU + * @TX_CMD_FLG_TSF: FW should calculate and insert TSF in the frame + * Should be set for beacons and probe responses + * @TX_CMD_FLG_CALIB: activate PA TX power calibrations + * @TX_CMD_FLG_KEEP_SEQ_CTL: if seq_ctl is set, don't increase inner seq count + * @TX_CMD_FLG_MH_PAD: driver inserted 2 byte padding after MAC header. + * Should be set for 26/30 length MAC headers + * @TX_CMD_FLG_RESP_TO_DRV: zero this if the response should go only to FW + * @TX_CMD_FLG_TKIP_MIC_DONE: FW already performed TKIP MIC calculation + * @TX_CMD_FLG_DUR: disable duration overwriting used in PS-Poll Assoc-id + * @TX_CMD_FLG_FW_DROP: FW should mark frame to be dropped + * @TX_CMD_FLG_EXEC_PAPD: execute PAPD + * @TX_CMD_FLG_PAPD_TYPE: 0 for reference power, 1 for nominal power + * @TX_CMD_FLG_HCCA_CHUNK: mark start of TSPEC chunk + */ +enum iwl_tx_flags { + TX_CMD_FLG_PROT_REQUIRE = BIT(0), + TX_CMD_FLG_WRITE_TX_POWER = BIT(1), + TX_CMD_FLG_ACK = BIT(3), + TX_CMD_FLG_STA_RATE = BIT(4), + TX_CMD_FLG_BAR = BIT(6), + TX_CMD_FLG_TXOP_PROT = BIT(7), + TX_CMD_FLG_VHT_NDPA = BIT(8), + TX_CMD_FLG_HT_NDPA = BIT(9), + TX_CMD_FLG_CSI_FDBK2HOST = BIT(10), + TX_CMD_FLG_BT_PRIO_POS = 11, + TX_CMD_FLG_BT_PRIO_MASK = BIT(11) | BIT(12), + TX_CMD_FLG_BT_DIS = BIT(12), + TX_CMD_FLG_SEQ_CTL = BIT(13), + TX_CMD_FLG_MORE_FRAG = BIT(14), + TX_CMD_FLG_TSF = BIT(16), + TX_CMD_FLG_CALIB = BIT(17), + TX_CMD_FLG_KEEP_SEQ_CTL = BIT(18), + TX_CMD_FLG_MH_PAD = BIT(20), + TX_CMD_FLG_RESP_TO_DRV = BIT(21), + TX_CMD_FLG_TKIP_MIC_DONE = BIT(23), + TX_CMD_FLG_DUR = BIT(25), + TX_CMD_FLG_FW_DROP = BIT(26), + TX_CMD_FLG_EXEC_PAPD = BIT(27), + TX_CMD_FLG_PAPD_TYPE = BIT(28), + TX_CMD_FLG_HCCA_CHUNK = BIT(31) +}; /* TX_FLAGS_BITS_API_S_VER_1 */ + +/** + * enum iwl_tx_cmd_flags - bitmasks for tx_flags in TX command for 22000 + * @IWL_TX_FLAGS_CMD_RATE: use rate from the TX command + * @IWL_TX_FLAGS_ENCRYPT_DIS: frame should not be encrypted, even if it belongs + * to a secured STA + * @IWL_TX_FLAGS_HIGH_PRI: high priority frame (like EAPOL) - can affect rate + * selection, retry limits and BT kill + */ +enum iwl_tx_cmd_flags { + IWL_TX_FLAGS_CMD_RATE = BIT(0), + IWL_TX_FLAGS_ENCRYPT_DIS = BIT(1), + IWL_TX_FLAGS_HIGH_PRI = BIT(2), + /* Use these flags only from + * TX_FLAGS_BITS_API_S_VER_4 and above */ + IWL_TX_FLAGS_RTS = BIT(3), + IWL_TX_FLAGS_CTS = BIT(4), +}; /* TX_FLAGS_BITS_API_S_VER_3 */ + +/** + * enum iwl_tx_pm_timeouts - pm timeout values in TX command + * @PM_FRAME_NONE: no need to suspend sleep mode + * @PM_FRAME_MGMT: fw suspend sleep mode for 100TU + * @PM_FRAME_ASSOC: fw suspend sleep mode for 10sec + */ +enum iwl_tx_pm_timeouts { + PM_FRAME_NONE = 0, + PM_FRAME_MGMT = 2, + PM_FRAME_ASSOC = 3, +}; + +#define TX_CMD_SEC_MSK 0x07 +#define TX_CMD_SEC_WEP_KEY_IDX_POS 6 +#define TX_CMD_SEC_WEP_KEY_IDX_MSK 0xc0 + +/** + * enum iwl_tx_cmd_sec_ctrl - bitmasks for security control in TX command + * @TX_CMD_SEC_WEP: WEP encryption algorithm. + * @TX_CMD_SEC_CCM: CCM encryption algorithm. + * @TX_CMD_SEC_TKIP: TKIP encryption algorithm. + * @TX_CMD_SEC_EXT: extended cipher algorithm. + * @TX_CMD_SEC_GCMP: GCMP encryption algorithm. + * @TX_CMD_SEC_KEY128: set for 104 bits WEP key. + * @TX_CMD_SEC_KEY_FROM_TABLE: for a non-WEP key, set if the key should be taken + * from the table instead of from the TX command. + * If the key is taken from the key table its index should be given by the + * first byte of the TX command key field. + */ +enum iwl_tx_cmd_sec_ctrl { + TX_CMD_SEC_WEP = 0x01, + TX_CMD_SEC_CCM = 0x02, + TX_CMD_SEC_TKIP = 0x03, + TX_CMD_SEC_EXT = 0x04, + TX_CMD_SEC_GCMP = 0x05, + TX_CMD_SEC_KEY128 = 0x08, + TX_CMD_SEC_KEY_FROM_TABLE = 0x10, +}; + +/* + * TX command Frame life time in us - to be written in pm_frame_timeout + */ +#define TX_CMD_LIFE_TIME_INFINITE 0xFFFFFFFF +#define TX_CMD_LIFE_TIME_DEFAULT 2000000 /* 2000 ms*/ +#define TX_CMD_LIFE_TIME_PROBE_RESP 40000 /* 40 ms */ +#define TX_CMD_LIFE_TIME_EXPIRED_FRAME 0 + +/* + * TID for non QoS frames - to be written in tid_tspec + */ +#define IWL_TID_NON_QOS 0 + +/* + * Limits on the retransmissions - to be written in {data,rts}_retry_limit + */ +#define IWL_DEFAULT_TX_RETRY 15 +#define IWL_MGMT_DFAULT_RETRY_LIMIT 3 +#define IWL_RTS_DFAULT_RETRY_LIMIT 60 +#define IWL_BAR_DFAULT_RETRY_LIMIT 60 +#define IWL_LOW_RETRY_LIMIT 7 + +/** + * enum iwl_tx_offload_assist_flags_pos - set %iwl_tx_cmd offload_assist values + * @TX_CMD_OFFLD_IP_HDR: offset to start of IP header (in words) + * from mac header end. For normal case it is 4 words for SNAP. + * note: tx_cmd, mac header and pad are not counted in the offset. + * This is used to help the offload in case there is tunneling such as + * IPv6 in IPv4, in such case the ip header offset should point to the + * inner ip header and IPv4 checksum of the external header should be + * calculated by driver. + * @TX_CMD_OFFLD_L4_EN: enable TCP/UDP checksum + * @TX_CMD_OFFLD_L3_EN: enable IP header checksum + * @TX_CMD_OFFLD_MH_SIZE: size of the mac header in words. Includes the IV + * field. Doesn't include the pad. + * @TX_CMD_OFFLD_PAD: mark 2-byte pad was inserted after the mac header for + * alignment + * @TX_CMD_OFFLD_AMSDU: mark TX command is A-MSDU + */ +enum iwl_tx_offload_assist_flags_pos { + TX_CMD_OFFLD_IP_HDR = 0, + TX_CMD_OFFLD_L4_EN = 6, + TX_CMD_OFFLD_L3_EN = 7, + TX_CMD_OFFLD_MH_SIZE = 8, + TX_CMD_OFFLD_PAD = 13, + TX_CMD_OFFLD_AMSDU = 14, +}; + +#define IWL_TX_CMD_OFFLD_MH_MASK 0x1f +#define IWL_TX_CMD_OFFLD_IP_HDR_MASK 0x3f + +/* TODO: complete documentation for try_cnt and btkill_cnt */ +/** + * struct iwl_tx_cmd - TX command struct to FW + * ( TX_CMD = 0x1c ) + * @len: in bytes of the payload, see below for details + * @offload_assist: TX offload configuration + * @tx_flags: combination of TX_CMD_FLG_*, see &enum iwl_tx_flags + * @scratch: scratch buffer used by the device + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + * cleared. Combination of RATE_MCS_* + * @sta_id: index of destination station in FW station table + * @sec_ctl: security control, TX_CMD_SEC_* + * @initial_rate_index: index into the the rate table for initial TX attempt. + * Applied if TX_CMD_FLG_STA_RATE_MSK is set, normally 0 for data frames. + * @reserved2: reserved + * @key: security key + * @reserved3: reserved + * @life_time: frame life time (usecs??) + * @dram_lsb_ptr: Physical address of scratch area in the command (try_cnt + + * btkill_cnd + reserved), first 32 bits. "0" disables usage. + * @dram_msb_ptr: upper bits of the scratch physical address + * @rts_retry_limit: max attempts for RTS + * @data_retry_limit: max attempts to send the data packet + * @tid_tspec: TID/tspec + * @pm_frame_timeout: PM TX frame timeout + * @reserved4: reserved + * @payload: payload (same as @hdr) + * @hdr: 802.11 header (same as @payload) + * + * The byte count (both len and next_frame_len) includes MAC header + * (24/26/30/32 bytes) + * + 2 bytes pad if 26/30 header size + * + 8 byte IV for CCM or TKIP (not used for WEP) + * + Data payload + * + 8-byte MIC (not used for CCM/WEP) + * It does not include post-MAC padding, i.e., + * MIC (CCM) 8 bytes, ICV (WEP/TKIP/CKIP) 4 bytes, CRC 4 bytes. + * Range of len: 14-2342 bytes. + * + * After the struct fields the MAC header is placed, plus any padding, + * and then the actial payload. + */ +struct iwl_tx_cmd { + __le16 len; + __le16 offload_assist; + __le32 tx_flags; + struct { + u8 try_cnt; + u8 btkill_cnt; + __le16 reserved; + } scratch; /* DRAM_SCRATCH_API_U_VER_1 */ + __le32 rate_n_flags; + u8 sta_id; + u8 sec_ctl; + u8 initial_rate_index; + u8 reserved2; + u8 key[16]; + __le32 reserved3; + __le32 life_time; + __le32 dram_lsb_ptr; + u8 dram_msb_ptr; + u8 rts_retry_limit; + u8 data_retry_limit; + u8 tid_tspec; + __le16 pm_frame_timeout; + __le16 reserved4; + u8 payload[0]; + struct ieee80211_hdr hdr[0]; +} __packed; /* TX_CMD_API_S_VER_6 */ + +struct iwl_dram_sec_info { + __le32 pn_low; + __le16 pn_high; + __le16 aux_info; +} __packed; /* DRAM_SEC_INFO_API_S_VER_1 */ + +/** + * struct iwl_tx_cmd_gen2 - TX command struct to FW for 22000 devices + * ( TX_CMD = 0x1c ) + * @len: in bytes of the payload, see below for details + * @offload_assist: TX offload configuration + * @flags: combination of &enum iwl_tx_cmd_flags + * @dram_info: FW internal DRAM storage + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + * cleared. Combination of RATE_MCS_* + * @hdr: 802.11 header + */ +struct iwl_tx_cmd_gen2 { + __le16 len; + __le16 offload_assist; + __le32 flags; + struct iwl_dram_sec_info dram_info; + __le32 rate_n_flags; + struct ieee80211_hdr hdr[]; +} __packed; /* TX_CMD_API_S_VER_7, + TX_CMD_API_S_VER_9 */ + +/** + * struct iwl_tx_cmd_gen3 - TX command struct to FW for AX210+ devices + * ( TX_CMD = 0x1c ) + * @len: in bytes of the payload, see below for details + * @flags: combination of &enum iwl_tx_cmd_flags + * @offload_assist: TX offload configuration + * @dram_info: FW internal DRAM storage + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + * cleared. Combination of RATE_MCS_* + * @ttl: time to live - packet lifetime limit. The FW should drop if + * passed. + * @hdr: 802.11 header + */ +struct iwl_tx_cmd_gen3 { + __le16 len; + __le16 flags; + __le32 offload_assist; + struct iwl_dram_sec_info dram_info; + __le32 rate_n_flags; + __le64 ttl; + struct ieee80211_hdr hdr[]; +} __packed; /* TX_CMD_API_S_VER_8, + TX_CMD_API_S_VER_10 */ + +/* + * TX response related data + */ + +/* + * enum iwl_tx_status - status that is returned by the fw after attempts to Tx + * @TX_STATUS_SUCCESS: + * @TX_STATUS_DIRECT_DONE: + * @TX_STATUS_POSTPONE_DELAY: + * @TX_STATUS_POSTPONE_FEW_BYTES: + * @TX_STATUS_POSTPONE_BT_PRIO: + * @TX_STATUS_POSTPONE_QUIET_PERIOD: + * @TX_STATUS_POSTPONE_CALC_TTAK: + * @TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY: + * @TX_STATUS_FAIL_SHORT_LIMIT: + * @TX_STATUS_FAIL_LONG_LIMIT: + * @TX_STATUS_FAIL_UNDERRUN: + * @TX_STATUS_FAIL_DRAIN_FLOW: + * @TX_STATUS_FAIL_RFKILL_FLUSH: + * @TX_STATUS_FAIL_LIFE_EXPIRE: + * @TX_STATUS_FAIL_DEST_PS: + * @TX_STATUS_FAIL_HOST_ABORTED: + * @TX_STATUS_FAIL_BT_RETRY: + * @TX_STATUS_FAIL_STA_INVALID: + * @TX_TATUS_FAIL_FRAG_DROPPED: + * @TX_STATUS_FAIL_TID_DISABLE: + * @TX_STATUS_FAIL_FIFO_FLUSHED: + * @TX_STATUS_FAIL_SMALL_CF_POLL: + * @TX_STATUS_FAIL_FW_DROP: + * @TX_STATUS_FAIL_STA_COLOR_MISMATCH: mismatch between color of Tx cmd and + * STA table + * @TX_FRAME_STATUS_INTERNAL_ABORT: + * @TX_MODE_MSK: + * @TX_MODE_NO_BURST: + * @TX_MODE_IN_BURST_SEQ: + * @TX_MODE_FIRST_IN_BURST: + * @TX_QUEUE_NUM_MSK: + * + * Valid only if frame_count =1 + * TODO: complete documentation + */ +enum iwl_tx_status { + TX_STATUS_MSK = 0x000000ff, + TX_STATUS_SUCCESS = 0x01, + TX_STATUS_DIRECT_DONE = 0x02, + /* postpone TX */ + TX_STATUS_POSTPONE_DELAY = 0x40, + TX_STATUS_POSTPONE_FEW_BYTES = 0x41, + TX_STATUS_POSTPONE_BT_PRIO = 0x42, + TX_STATUS_POSTPONE_QUIET_PERIOD = 0x43, + TX_STATUS_POSTPONE_CALC_TTAK = 0x44, + /* abort TX */ + TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY = 0x81, + TX_STATUS_FAIL_SHORT_LIMIT = 0x82, + TX_STATUS_FAIL_LONG_LIMIT = 0x83, + TX_STATUS_FAIL_UNDERRUN = 0x84, + TX_STATUS_FAIL_DRAIN_FLOW = 0x85, + TX_STATUS_FAIL_RFKILL_FLUSH = 0x86, + TX_STATUS_FAIL_LIFE_EXPIRE = 0x87, + TX_STATUS_FAIL_DEST_PS = 0x88, + TX_STATUS_FAIL_HOST_ABORTED = 0x89, + TX_STATUS_FAIL_BT_RETRY = 0x8a, + TX_STATUS_FAIL_STA_INVALID = 0x8b, + TX_STATUS_FAIL_FRAG_DROPPED = 0x8c, + TX_STATUS_FAIL_TID_DISABLE = 0x8d, + TX_STATUS_FAIL_FIFO_FLUSHED = 0x8e, + TX_STATUS_FAIL_SMALL_CF_POLL = 0x8f, + TX_STATUS_FAIL_FW_DROP = 0x90, + TX_STATUS_FAIL_STA_COLOR_MISMATCH = 0x91, + TX_STATUS_INTERNAL_ABORT = 0x92, + TX_MODE_MSK = 0x00000f00, + TX_MODE_NO_BURST = 0x00000000, + TX_MODE_IN_BURST_SEQ = 0x00000100, + TX_MODE_FIRST_IN_BURST = 0x00000200, + TX_QUEUE_NUM_MSK = 0x0001f000, + TX_NARROW_BW_MSK = 0x00060000, + TX_NARROW_BW_1DIV2 = 0x00020000, + TX_NARROW_BW_1DIV4 = 0x00040000, + TX_NARROW_BW_1DIV8 = 0x00060000, +}; + +/* + * enum iwl_tx_agg_status - TX aggregation status + * @AGG_TX_STATE_STATUS_MSK: + * @AGG_TX_STATE_TRANSMITTED: + * @AGG_TX_STATE_UNDERRUN: + * @AGG_TX_STATE_BT_PRIO: + * @AGG_TX_STATE_FEW_BYTES: + * @AGG_TX_STATE_ABORT: + * @AGG_TX_STATE_TX_ON_AIR_DROP: TX_ON_AIR signal drop without underrun or + * BT detection + * @AGG_TX_STATE_LAST_SENT_TRY_CNT: + * @AGG_TX_STATE_LAST_SENT_BT_KILL: + * @AGG_TX_STATE_SCD_QUERY: + * @AGG_TX_STATE_TEST_BAD_CRC32: + * @AGG_TX_STATE_RESPONSE: + * @AGG_TX_STATE_DUMP_TX: + * @AGG_TX_STATE_DELAY_TX: + * @AGG_TX_STATE_TRY_CNT_MSK: Retry count for 1st frame in aggregation (retries + * occur if tx failed for this frame when it was a member of a previous + * aggregation block). If rate scaling is used, retry count indicates the + * rate table entry used for all frames in the new agg. + * @AGG_TX_STATE_SEQ_NUM_MSK: Command ID and sequence number of Tx command for + * this frame + * + * TODO: complete documentation + */ +enum iwl_tx_agg_status { + AGG_TX_STATE_STATUS_MSK = 0x00fff, + AGG_TX_STATE_TRANSMITTED = 0x000, + AGG_TX_STATE_UNDERRUN = 0x001, + AGG_TX_STATE_BT_PRIO = 0x002, + AGG_TX_STATE_FEW_BYTES = 0x004, + AGG_TX_STATE_ABORT = 0x008, + AGG_TX_STATE_TX_ON_AIR_DROP = 0x010, + AGG_TX_STATE_LAST_SENT_TRY_CNT = 0x020, + AGG_TX_STATE_LAST_SENT_BT_KILL = 0x040, + AGG_TX_STATE_SCD_QUERY = 0x080, + AGG_TX_STATE_TEST_BAD_CRC32 = 0x0100, + AGG_TX_STATE_RESPONSE = 0x1ff, + AGG_TX_STATE_DUMP_TX = 0x200, + AGG_TX_STATE_DELAY_TX = 0x400, + AGG_TX_STATE_TRY_CNT_POS = 12, + AGG_TX_STATE_TRY_CNT_MSK = 0xf << AGG_TX_STATE_TRY_CNT_POS, +}; + +/* + * The mask below describes a status where we are absolutely sure that the MPDU + * wasn't sent. For BA/Underrun we cannot be that sure. All we know that we've + * written the bytes to the TXE, but we know nothing about what the DSP did. + */ +#define AGG_TX_STAT_FRAME_NOT_SENT (AGG_TX_STATE_FEW_BYTES | \ + AGG_TX_STATE_ABORT | \ + AGG_TX_STATE_SCD_QUERY) + +/* + * REPLY_TX = 0x1c (response) + * + * This response may be in one of two slightly different formats, indicated + * by the frame_count field: + * + * 1) No aggregation (frame_count == 1). This reports Tx results for a single + * frame. Multiple attempts, at various bit rates, may have been made for + * this frame. + * + * 2) Aggregation (frame_count > 1). This reports Tx results for two or more + * frames that used block-acknowledge. All frames were transmitted at + * same rate. Rate scaling may have been used if first frame in this new + * agg block failed in previous agg block(s). + * + * Note that, for aggregation, ACK (block-ack) status is not delivered + * here; block-ack has not been received by the time the device records + * this status. + * This status relates to reasons the tx might have been blocked or aborted + * within the device, rather than whether it was received successfully by + * the destination station. + */ + +/** + * struct agg_tx_status - per packet TX aggregation status + * @status: See &enum iwl_tx_agg_status + * @sequence: Sequence # for this frame's Tx cmd (not SSN!) + */ +struct agg_tx_status { + __le16 status; + __le16 sequence; +} __packed; + +/* + * definitions for initial rate index field + * bits [3:0] initial rate index + * bits [6:4] rate table color, used for the initial rate + * bit-7 invalid rate indication + */ +#define TX_RES_INIT_RATE_INDEX_MSK 0x0f +#define TX_RES_RATE_TABLE_COLOR_POS 4 +#define TX_RES_RATE_TABLE_COLOR_MSK 0x70 +#define TX_RES_INV_RATE_INDEX_MSK 0x80 +#define TX_RES_RATE_TABLE_COL_GET(_f) (((_f) & TX_RES_RATE_TABLE_COLOR_MSK) >>\ + TX_RES_RATE_TABLE_COLOR_POS) + +#define IWL_MVM_TX_RES_GET_TID(_ra_tid) ((_ra_tid) & 0x0f) +#define IWL_MVM_TX_RES_GET_RA(_ra_tid) ((_ra_tid) >> 4) + +/** + * struct iwl_mvm_tx_resp_v3 - notifies that fw is TXing a packet + * ( REPLY_TX = 0x1c ) + * @frame_count: 1 no aggregation, >1 aggregation + * @bt_kill_count: num of times blocked by bluetooth (unused for agg) + * @failure_rts: num of failures due to unsuccessful RTS + * @failure_frame: num failures due to no ACK (unused for agg) + * @initial_rate: for non-agg: rate of the successful Tx. For agg: rate of the + * Tx of all the batch. RATE_MCS_* + * @wireless_media_time: for non-agg: RTS + CTS + frame tx attempts time + ACK. + * for agg: RTS + CTS + aggregation tx time + block-ack time. + * in usec. + * @pa_status: tx power info + * @pa_integ_res_a: tx power info + * @pa_integ_res_b: tx power info + * @pa_integ_res_c: tx power info + * @measurement_req_id: tx power info + * @reduced_tpc: transmit power reduction used + * @reserved: reserved + * @tfd_info: TFD information set by the FH + * @seq_ctl: sequence control from the Tx cmd + * @byte_cnt: byte count from the Tx cmd + * @tlc_info: TLC rate info + * @ra_tid: bits [3:0] = ra, bits [7:4] = tid + * @frame_ctrl: frame control + * @status: for non-agg: frame status TX_STATUS_* + * for agg: status of 1st frame, AGG_TX_STATE_*; other frame status fields + * follow this one, up to frame_count. Length in @frame_count. + * + * After the array of statuses comes the SSN of the SCD. Look at + * %iwl_mvm_get_scd_ssn for more details. + */ +struct iwl_mvm_tx_resp_v3 { + u8 frame_count; + u8 bt_kill_count; + u8 failure_rts; + u8 failure_frame; + __le32 initial_rate; + __le16 wireless_media_time; + + u8 pa_status; + u8 pa_integ_res_a[3]; + u8 pa_integ_res_b[3]; + u8 pa_integ_res_c[3]; + __le16 measurement_req_id; + u8 reduced_tpc; + u8 reserved; + + __le32 tfd_info; + __le16 seq_ctl; + __le16 byte_cnt; + u8 tlc_info; + u8 ra_tid; + __le16 frame_ctrl; + struct agg_tx_status status[]; +} __packed; /* TX_RSP_API_S_VER_3 */ + +/** + * struct iwl_mvm_tx_resp - notifies that fw is TXing a packet + * ( REPLY_TX = 0x1c ) + * @frame_count: 1 no aggregation, >1 aggregation + * @bt_kill_count: num of times blocked by bluetooth (unused for agg) + * @failure_rts: num of failures due to unsuccessful RTS + * @failure_frame: num failures due to no ACK (unused for agg) + * @initial_rate: for non-agg: rate of the successful Tx. For agg: rate of the + * Tx of all the batch. RATE_MCS_* + * @wireless_media_time: for non-agg: RTS + CTS + frame tx attempts time + ACK. + * for agg: RTS + CTS + aggregation tx time + block-ack time. + * in usec. + * @pa_status: tx power info + * @pa_integ_res_a: tx power info + * @pa_integ_res_b: tx power info + * @pa_integ_res_c: tx power info + * @measurement_req_id: tx power info + * @reduced_tpc: transmit power reduction used + * @reserved: reserved + * @tfd_info: TFD information set by the FH + * @seq_ctl: sequence control from the Tx cmd + * @byte_cnt: byte count from the Tx cmd + * @tlc_info: TLC rate info + * @ra_tid: bits [3:0] = ra, bits [7:4] = tid + * @frame_ctrl: frame control + * @tx_queue: TX queue for this response + * @reserved2: reserved for padding/alignment + * @status: for non-agg: frame status TX_STATUS_* + * For version 6 TX response isn't received for aggregation at all. + * + * After the array of statuses comes the SSN of the SCD. Look at + * %iwl_mvm_get_scd_ssn for more details. + */ +struct iwl_mvm_tx_resp { + u8 frame_count; + u8 bt_kill_count; + u8 failure_rts; + u8 failure_frame; + __le32 initial_rate; + __le16 wireless_media_time; + + u8 pa_status; + u8 pa_integ_res_a[3]; + u8 pa_integ_res_b[3]; + u8 pa_integ_res_c[3]; + __le16 measurement_req_id; + u8 reduced_tpc; + u8 reserved; + + __le32 tfd_info; + __le16 seq_ctl; + __le16 byte_cnt; + u8 tlc_info; + u8 ra_tid; + __le16 frame_ctrl; + __le16 tx_queue; + __le16 reserved2; + struct agg_tx_status status; +} __packed; /* TX_RSP_API_S_VER_6, + TX_RSP_API_S_VER_7 */ + +/** + * struct iwl_mvm_ba_notif - notifies about reception of BA + * ( BA_NOTIF = 0xc5 ) + * @sta_addr: MAC address + * @reserved: reserved + * @sta_id: Index of recipient (BA-sending) station in fw's station table + * @tid: tid of the session + * @seq_ctl: sequence control field + * @bitmap: the bitmap of the BA notification as seen in the air + * @scd_flow: the tx queue this BA relates to + * @scd_ssn: the index of the last contiguously sent packet + * @txed: number of Txed frames in this batch + * @txed_2_done: number of Acked frames in this batch + * @reduced_txp: power reduced according to TPC. This is the actual value and + * not a copy from the LQ command. Thus, if not the first rate was used + * for Tx-ing then this value will be set to 0 by FW. + * @reserved1: reserved + */ +struct iwl_mvm_ba_notif { + u8 sta_addr[ETH_ALEN]; + __le16 reserved; + + u8 sta_id; + u8 tid; + __le16 seq_ctl; + __le64 bitmap; + __le16 scd_flow; + __le16 scd_ssn; + u8 txed; + u8 txed_2_done; + u8 reduced_txp; + u8 reserved1; +} __packed; + +/** + * struct iwl_mvm_compressed_ba_tfd - progress of a TFD queue + * @q_num: TFD queue number + * @tfd_index: Index of first un-acked frame in the TFD queue + * @scd_queue: For debug only - the physical queue the TFD queue is bound to + * @tid: TID of the queue (0-7) + * @reserved: reserved for alignment + */ +struct iwl_mvm_compressed_ba_tfd { + __le16 q_num; + __le16 tfd_index; + u8 scd_queue; + u8 tid; + u8 reserved[2]; +} __packed; /* COMPRESSED_BA_TFD_API_S_VER_1 */ + +/** + * struct iwl_mvm_compressed_ba_ratid - progress of a RA TID queue + * @q_num: RA TID queue number + * @tid: TID of the queue + * @ssn: BA window current SSN + */ +struct iwl_mvm_compressed_ba_ratid { + u8 q_num; + u8 tid; + __le16 ssn; +} __packed; /* COMPRESSED_BA_RATID_API_S_VER_1 */ + +/* + * enum iwl_mvm_ba_resp_flags - TX aggregation status + * @IWL_MVM_BA_RESP_TX_AGG: generated due to BA + * @IWL_MVM_BA_RESP_TX_BAR: generated due to BA after BAR + * @IWL_MVM_BA_RESP_TX_AGG_FAIL: aggregation didn't receive BA + * @IWL_MVM_BA_RESP_TX_UNDERRUN: aggregation got underrun + * @IWL_MVM_BA_RESP_TX_BT_KILL: aggregation got BT-kill + * @IWL_MVM_BA_RESP_TX_DSP_TIMEOUT: aggregation didn't finish within the + * expected time + */ +enum iwl_mvm_ba_resp_flags { + IWL_MVM_BA_RESP_TX_AGG, + IWL_MVM_BA_RESP_TX_BAR, + IWL_MVM_BA_RESP_TX_AGG_FAIL, + IWL_MVM_BA_RESP_TX_UNDERRUN, + IWL_MVM_BA_RESP_TX_BT_KILL, + IWL_MVM_BA_RESP_TX_DSP_TIMEOUT +}; + +/** + * struct iwl_mvm_compressed_ba_notif - notifies about reception of BA + * ( BA_NOTIF = 0xc5 ) + * @flags: status flag, see the &iwl_mvm_ba_resp_flags + * @sta_id: Index of recipient (BA-sending) station in fw's station table + * @reduced_txp: power reduced according to TPC. This is the actual value and + * not a copy from the LQ command. Thus, if not the first rate was used + * for Tx-ing then this value will be set to 0 by FW. + * @tlc_rate_info: TLC rate info, initial rate index, TLC table color + * @retry_cnt: retry count + * @query_byte_cnt: SCD query byte count + * @query_frame_cnt: SCD query frame count + * @txed: number of frames sent in the aggregation (all-TIDs) + * @done: number of frames that were Acked by the BA (all-TIDs) + * @reserved: reserved (for alignment) + * @wireless_time: Wireless-media time + * @tx_rate: the rate the aggregation was sent at + * @tfd_cnt: number of TFD-Q elements + * @ra_tid_cnt: number of RATID-Q elements + * @tfd: array of TFD queue status updates. See &iwl_mvm_compressed_ba_tfd + * for details. Length in @tfd_cnt. + * @ra_tid: array of RA-TID queue status updates. For debug purposes only. See + * &iwl_mvm_compressed_ba_ratid for more details. Length in @ra_tid_cnt. + */ +struct iwl_mvm_compressed_ba_notif { + __le32 flags; + u8 sta_id; + u8 reduced_txp; + u8 tlc_rate_info; + u8 retry_cnt; + __le32 query_byte_cnt; + __le16 query_frame_cnt; + __le16 txed; + __le16 done; + __le16 reserved; + __le32 wireless_time; + __le32 tx_rate; + __le16 tfd_cnt; + __le16 ra_tid_cnt; + struct iwl_mvm_compressed_ba_ratid ra_tid[0]; + struct iwl_mvm_compressed_ba_tfd tfd[]; +} __packed; /* COMPRESSED_BA_RES_API_S_VER_4, + COMPRESSED_BA_RES_API_S_VER_5 */ + +/** + * struct iwl_mac_beacon_cmd_v6 - beacon template command + * @tx: the tx commands associated with the beacon frame + * @template_id: currently equal to the mac context id of the coresponding + * mac. + * @tim_idx: the offset of the tim IE in the beacon + * @tim_size: the length of the tim IE + * @frame: the template of the beacon frame + */ +struct iwl_mac_beacon_cmd_v6 { + struct iwl_tx_cmd tx; + __le32 template_id; + __le32 tim_idx; + __le32 tim_size; + struct ieee80211_hdr frame[]; +} __packed; /* BEACON_TEMPLATE_CMD_API_S_VER_6 */ + +/** + * struct iwl_mac_beacon_cmd_v7 - beacon template command with offloaded CSA + * @tx: the tx commands associated with the beacon frame + * @template_id: currently equal to the mac context id of the coresponding + * mac. + * @tim_idx: the offset of the tim IE in the beacon + * @tim_size: the length of the tim IE + * @ecsa_offset: offset to the ECSA IE if present + * @csa_offset: offset to the CSA IE if present + * @frame: the template of the beacon frame + */ +struct iwl_mac_beacon_cmd_v7 { + struct iwl_tx_cmd tx; + __le32 template_id; + __le32 tim_idx; + __le32 tim_size; + __le32 ecsa_offset; + __le32 csa_offset; + struct ieee80211_hdr frame[]; +} __packed; /* BEACON_TEMPLATE_CMD_API_S_VER_7 */ + +/* Bit flags for BEACON_TEMPLATE_CMD_API until version 10 */ +enum iwl_mac_beacon_flags_v1 { + IWL_MAC_BEACON_CCK_V1 = BIT(8), + IWL_MAC_BEACON_ANT_A_V1 = BIT(9), + IWL_MAC_BEACON_ANT_B_V1 = BIT(10), + IWL_MAC_BEACON_FILS_V1 = BIT(12), +}; + +/* Bit flags for BEACON_TEMPLATE_CMD_API version 11 and above */ +enum iwl_mac_beacon_flags { + IWL_MAC_BEACON_CCK = BIT(5), + IWL_MAC_BEACON_ANT_A = BIT(6), + IWL_MAC_BEACON_ANT_B = BIT(7), + IWL_MAC_BEACON_FILS = BIT(8), +}; + +/** + * struct iwl_mac_beacon_cmd - beacon template command with offloaded CSA + * @byte_cnt: byte count of the beacon frame. + * @flags: least significant byte for rate code. The most significant byte + * is &enum iwl_mac_beacon_flags. + * @short_ssid: Short SSID + * @reserved: reserved + * @template_id: currently equal to the mac context id of the coresponding mac. + * @tim_idx: the offset of the tim IE in the beacon + * @tim_size: the length of the tim IE + * @ecsa_offset: offset to the ECSA IE if present + * @csa_offset: offset to the CSA IE if present + * @frame: the template of the beacon frame + */ +struct iwl_mac_beacon_cmd { + __le16 byte_cnt; + __le16 flags; + __le32 short_ssid; + __le32 reserved; + __le32 template_id; + __le32 tim_idx; + __le32 tim_size; + __le32 ecsa_offset; + __le32 csa_offset; + struct ieee80211_hdr frame[]; +} __packed; /* BEACON_TEMPLATE_CMD_API_S_VER_10, + BEACON_TEMPLATE_CMD_API_S_VER_11, + BEACON_TEMPLATE_CMD_API_S_VER_12 */ + +struct iwl_beacon_notif { + struct iwl_mvm_tx_resp beacon_notify_hdr; + __le64 tsf; + __le32 ibss_mgr_status; +} __packed; + +/** + * struct iwl_extended_beacon_notif_v5 - notifies about beacon transmission + * @beacon_notify_hdr: tx response command associated with the beacon + * @tsf: last beacon tsf + * @ibss_mgr_status: whether IBSS is manager + * @gp2: last beacon time in gp2 + */ +struct iwl_extended_beacon_notif_v5 { + struct iwl_mvm_tx_resp beacon_notify_hdr; + __le64 tsf; + __le32 ibss_mgr_status; + __le32 gp2; +} __packed; /* BEACON_NTFY_API_S_VER_5 */ + +/** + * struct iwl_extended_beacon_notif - notifies about beacon transmission + * @status: the status of the Tx response of the beacon + * @tsf: last beacon tsf + * @ibss_mgr_status: whether IBSS is manager + * @gp2: last beacon time in gp2 + */ +struct iwl_extended_beacon_notif { + __le32 status; + __le64 tsf; + __le32 ibss_mgr_status; + __le32 gp2; +} __packed; /* BEACON_NTFY_API_S_VER_6_ */ + +/** + * enum iwl_dump_control - dump (flush) control flags + * @DUMP_TX_FIFO_FLUSH: Dump MSDUs until the the FIFO is empty + * and the TFD queues are empty. + */ +enum iwl_dump_control { + DUMP_TX_FIFO_FLUSH = BIT(1), +}; + +/** + * struct iwl_tx_path_flush_cmd -- queue/FIFO flush command + * @queues_ctl: bitmap of queues to flush + * @flush_ctl: control flags + * @reserved: reserved + */ +struct iwl_tx_path_flush_cmd_v1 { + __le32 queues_ctl; + __le16 flush_ctl; + __le16 reserved; +} __packed; /* TX_PATH_FLUSH_CMD_API_S_VER_1 */ + +/** + * struct iwl_tx_path_flush_cmd -- queue/FIFO flush command + * @sta_id: station ID to flush + * @tid_mask: TID mask to flush + * @reserved: reserved + */ +struct iwl_tx_path_flush_cmd { + __le32 sta_id; + __le16 tid_mask; + __le16 reserved; +} __packed; /* TX_PATH_FLUSH_CMD_API_S_VER_2 */ + +#define IWL_TX_FLUSH_QUEUE_RSP 16 + +/** + * struct iwl_flush_queue_info - virtual flush queue info + * @queue_num: virtual queue id + * @read_before_flush: read pointer before flush + * @read_after_flush: read pointer after flush + */ +struct iwl_flush_queue_info { + __le16 tid; + __le16 queue_num; + __le16 read_before_flush; + __le16 read_after_flush; +} __packed; /* TFDQ_FLUSH_INFO_API_S_VER_1 */ + +/** + * struct iwl_tx_path_flush_cmd_rsp -- queue/FIFO flush command response + * @num_flushed_queues: number of queues in queues array + * @queues: all flushed queues + */ +struct iwl_tx_path_flush_cmd_rsp { + __le16 sta_id; + __le16 num_flushed_queues; + struct iwl_flush_queue_info queues[IWL_TX_FLUSH_QUEUE_RSP]; +} __packed; /* TX_PATH_FLUSH_CMD_RSP_API_S_VER_1 */ + +/* Available options for the SCD_QUEUE_CFG HCMD */ +enum iwl_scd_cfg_actions { + SCD_CFG_DISABLE_QUEUE = 0x0, + SCD_CFG_ENABLE_QUEUE = 0x1, + SCD_CFG_UPDATE_QUEUE_TID = 0x2, +}; + +/** + * struct iwl_scd_txq_cfg_cmd - New txq hw scheduler config command + * @token: unused + * @sta_id: station id + * @tid: TID + * @scd_queue: scheduler queue to confiug + * @action: 1 queue enable, 0 queue disable, 2 change txq's tid owner + * Value is one of &enum iwl_scd_cfg_actions options + * @aggregate: 1 aggregated queue, 0 otherwise + * @tx_fifo: &enum iwl_mvm_tx_fifo + * @window: BA window size + * @ssn: SSN for the BA agreement + * @reserved: reserved + */ +struct iwl_scd_txq_cfg_cmd { + u8 token; + u8 sta_id; + u8 tid; + u8 scd_queue; + u8 action; + u8 aggregate; + u8 tx_fifo; + u8 window; + __le16 ssn; + __le16 reserved; +} __packed; /* SCD_QUEUE_CFG_CMD_API_S_VER_1 */ + +/** + * struct iwl_scd_txq_cfg_rsp + * @token: taken from the command + * @sta_id: station id from the command + * @tid: tid from the command + * @scd_queue: scd_queue from the command + */ +struct iwl_scd_txq_cfg_rsp { + u8 token; + u8 sta_id; + u8 tid; + u8 scd_queue; +} __packed; /* SCD_QUEUE_CFG_RSP_API_S_VER_1 */ + +#endif /* __iwl_fw_api_tx_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/api/txq.h b/sys/contrib/dev/iwlwifi/fw/api/txq.h new file mode 100644 index 00000000000..8b3a00df41d --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/api/txq.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2019-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_txq_h__ +#define __iwl_fw_api_txq_h__ + +/* + * DQA queue numbers + * + * @IWL_MVM_DQA_CMD_QUEUE: a queue reserved for sending HCMDs to the FW + * @IWL_MVM_DQA_AUX_QUEUE: a queue reserved for aux frames + * @IWL_MVM_DQA_P2P_DEVICE_QUEUE: a queue reserved for P2P device frames + * @IWL_MVM_DQA_INJECT_MONITOR_QUEUE: a queue reserved for injection using + * monitor mode. Note this queue is the same as the queue for P2P device + * but we can't have active monitor mode along with P2P device anyway. + * @IWL_MVM_DQA_GCAST_QUEUE: a queue reserved for P2P GO/SoftAP GCAST frames + * @IWL_MVM_DQA_BSS_CLIENT_QUEUE: a queue reserved for BSS activity, to ensure + * that we are never left without the possibility to connect to an AP. + * @IWL_MVM_DQA_MIN_MGMT_QUEUE: first TXQ in pool for MGMT and non-QOS frames. + * Each MGMT queue is mapped to a single STA + * MGMT frames are frames that return true on ieee80211_is_mgmt() + * @IWL_MVM_DQA_MAX_MGMT_QUEUE: last TXQ in pool for MGMT frames + * @IWL_MVM_DQA_AP_PROBE_RESP_QUEUE: a queue reserved for P2P GO/SoftAP probe + * responses + * @IWL_MVM_DQA_MIN_DATA_QUEUE: first TXQ in pool for DATA frames. + * DATA frames are intended for !ieee80211_is_mgmt() frames, but if + * the MGMT TXQ pool is exhausted, mgmt frames can be sent on DATA queues + * as well + * @IWL_MVM_DQA_MAX_DATA_QUEUE: last TXQ in pool for DATA frames + */ +enum iwl_mvm_dqa_txq { + IWL_MVM_DQA_CMD_QUEUE = 0, + IWL_MVM_DQA_AUX_QUEUE = 1, + IWL_MVM_DQA_P2P_DEVICE_QUEUE = 2, + IWL_MVM_DQA_INJECT_MONITOR_QUEUE = 2, + IWL_MVM_DQA_GCAST_QUEUE = 3, + IWL_MVM_DQA_BSS_CLIENT_QUEUE = 4, + IWL_MVM_DQA_MIN_MGMT_QUEUE = 5, + IWL_MVM_DQA_MAX_MGMT_QUEUE = 8, + IWL_MVM_DQA_AP_PROBE_RESP_QUEUE = 9, + IWL_MVM_DQA_MIN_DATA_QUEUE = 10, + IWL_MVM_DQA_MAX_DATA_QUEUE = 30, +}; + +enum iwl_mvm_tx_fifo { + IWL_MVM_TX_FIFO_BK = 0, + IWL_MVM_TX_FIFO_BE, + IWL_MVM_TX_FIFO_VI, + IWL_MVM_TX_FIFO_VO, + IWL_MVM_TX_FIFO_MCAST = 5, + IWL_MVM_TX_FIFO_CMD = 7, +}; + +enum iwl_gen2_tx_fifo { + IWL_GEN2_TX_FIFO_CMD = 0, + IWL_GEN2_EDCA_TX_FIFO_BK, + IWL_GEN2_EDCA_TX_FIFO_BE, + IWL_GEN2_EDCA_TX_FIFO_VI, + IWL_GEN2_EDCA_TX_FIFO_VO, + IWL_GEN2_TRIG_TX_FIFO_BK, + IWL_GEN2_TRIG_TX_FIFO_BE, + IWL_GEN2_TRIG_TX_FIFO_VI, + IWL_GEN2_TRIG_TX_FIFO_VO, +}; + +/** + * enum iwl_tx_queue_cfg_actions - TXQ config options + * @TX_QUEUE_CFG_ENABLE_QUEUE: enable a queue + * @TX_QUEUE_CFG_TFD_SHORT_FORMAT: use short TFD format + */ +enum iwl_tx_queue_cfg_actions { + TX_QUEUE_CFG_ENABLE_QUEUE = BIT(0), + TX_QUEUE_CFG_TFD_SHORT_FORMAT = BIT(1), +}; + +#define IWL_DEFAULT_QUEUE_SIZE 256 +#define IWL_MGMT_QUEUE_SIZE 16 +#define IWL_CMD_QUEUE_SIZE 32 +/** + * struct iwl_tx_queue_cfg_cmd - txq hw scheduler config command + * @sta_id: station id + * @tid: tid of the queue + * @flags: see &enum iwl_tx_queue_cfg_actions + * @cb_size: size of TFD cyclic buffer. Value is exponent - 3. + * Minimum value 0 (8 TFDs), maximum value 5 (256 TFDs) + * @byte_cnt_addr: address of byte count table + * @tfdq_addr: address of TFD circular buffer + */ +struct iwl_tx_queue_cfg_cmd { + u8 sta_id; + u8 tid; + __le16 flags; + __le32 cb_size; + __le64 byte_cnt_addr; + __le64 tfdq_addr; +} __packed; /* TX_QUEUE_CFG_CMD_API_S_VER_2 */ + +/** + * struct iwl_tx_queue_cfg_rsp - response to txq hw scheduler config + * @queue_number: queue number assigned to this RA -TID + * @flags: set on failure + * @write_pointer: initial value for write pointer + * @reserved: reserved + */ +struct iwl_tx_queue_cfg_rsp { + __le16 queue_number; + __le16 flags; + __le16 write_pointer; + __le16 reserved; +} __packed; /* TX_QUEUE_CFG_RSP_API_S_VER_2 */ + +#endif /* __iwl_fw_api_txq_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/dbg.c b/sys/contrib/dev/iwlwifi/fw/dbg.c new file mode 100644 index 00000000000..4a8b570a069 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/dbg.c @@ -0,0 +1,2936 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include +#if defined(__FreeBSD__) +#include +#endif +#include "iwl-drv.h" +#include "runtime.h" +#include "dbg.h" +#include "debugfs.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" + +/** + * struct iwl_fw_dump_ptrs - set of pointers needed for the fw-error-dump + * + * @fwrt_ptr: pointer to the buffer coming from fwrt + * @trans_ptr: pointer to struct %iwl_trans_dump_data which contains the + * transport's data. + * @trans_len: length of the valid data in trans_ptr + * @fwrt_len: length of the valid data in fwrt_ptr + */ +struct iwl_fw_dump_ptrs { + struct iwl_trans_dump_data *trans_ptr; + void *fwrt_ptr; + u32 fwrt_len; +}; + +#define RADIO_REG_MAX_READ 0x2ad +static void iwl_read_radio_regs(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + u8 *pos = (void *)(*dump_data)->data; + int i; + + IWL_DEBUG_INFO(fwrt, "WRT radio registers dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RADIO_REG); + (*dump_data)->len = cpu_to_le32(RADIO_REG_MAX_READ); + + for (i = 0; i < RADIO_REG_MAX_READ; i++) { + u32 rd_cmd = RADIO_RSP_RD_CMD; + + rd_cmd |= i << RADIO_RSP_ADDR_POS; + iwl_write_prph_no_grab(fwrt->trans, RSP_RADIO_CMD, rd_cmd); + *pos = (u8)iwl_read_prph_no_grab(fwrt->trans, RSP_RADIO_RDDAT); + + pos++; + } + + *dump_data = iwl_fw_error_next_data(*dump_data); + + iwl_trans_release_nic_access(fwrt->trans); +} + +static void iwl_fwrt_dump_rxf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + int size, u32 offset, int fifo_num) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + u32 *fifo_data; + u32 fifo_len; + int i; + + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = size; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + return; + + /* Add a TLV for the RXF */ + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RXF); + (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(fifo_num); + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_D_SPACE + offset)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_WR_PTR + offset)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_RD_PTR + offset)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_FENCE_PTR + offset)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_SET_FENCE_MODE + offset)); + + /* Lock fence */ + iwl_trans_write_prph(fwrt->trans, RXF_SET_FENCE_MODE + offset, 0x1); + /* Set fence pointer to the same place like WR pointer */ + iwl_trans_write_prph(fwrt->trans, RXF_LD_WR2FENCE + offset, 0x1); + /* Set fence offset */ + iwl_trans_write_prph(fwrt->trans, + RXF_LD_FENCE_OFFSET_ADDR + offset, 0x0); + + /* Read FIFO */ + fifo_len /= sizeof(u32); /* Size in DWORDS */ + for (i = 0; i < fifo_len; i++) + fifo_data[i] = iwl_trans_read_prph(fwrt->trans, + RXF_FIFO_RD_FENCE_INC + + offset); + *dump_data = iwl_fw_error_next_data(*dump_data); +} + +static void iwl_fwrt_dump_txf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + int size, u32 offset, int fifo_num) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + u32 *fifo_data; + u32 fifo_len; + int i; + + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = size; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + return; + + /* Add a TLV for the FIFO */ + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXF); + (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(fifo_num); + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_FIFO_ITEM_CNT + offset)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_WR_PTR + offset)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_RD_PTR + offset)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_FENCE_PTR + offset)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_LOCK_FENCE + offset)); + + /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ + iwl_trans_write_prph(fwrt->trans, TXF_READ_MODIFY_ADDR + offset, + TXF_WR_PTR + offset); + + /* Dummy-read to advance the read pointer to the head */ + iwl_trans_read_prph(fwrt->trans, TXF_READ_MODIFY_DATA + offset); + + /* Read FIFO */ + for (i = 0; i < fifo_len / sizeof(u32); i++) + fifo_data[i] = iwl_trans_read_prph(fwrt->trans, + TXF_READ_MODIFY_DATA + + offset); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) + fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, + fifo_data, fifo_len); + + *dump_data = iwl_fw_error_next_data(*dump_data); +} + +static void iwl_fw_dump_rxf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + + IWL_DEBUG_INFO(fwrt, "WRT RX FIFO dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) { + /* Pull RXF1 */ + iwl_fwrt_dump_rxf(fwrt, dump_data, + cfg->lmac[0].rxfifo1_size, 0, 0); + /* Pull RXF2 */ + iwl_fwrt_dump_rxf(fwrt, dump_data, cfg->rxfifo2_size, + RXF_DIFF_FROM_PREV + + fwrt->trans->trans_cfg->umac_prph_offset, 1); + /* Pull LMAC2 RXF1 */ + if (fwrt->smem_cfg.num_lmacs > 1) + iwl_fwrt_dump_rxf(fwrt, dump_data, + cfg->lmac[1].rxfifo1_size, + LMAC2_PRPH_OFFSET, 2); + } + + iwl_trans_release_nic_access(fwrt->trans); +} + +static void iwl_fw_dump_txf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + u32 *fifo_data; + u32 fifo_len; + int i, j; + + IWL_DEBUG_INFO(fwrt, "WRT TX FIFO dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) { + /* Pull TXF data from LMAC1 */ + for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; i++) { + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, TXF_LARC_NUM, i); + iwl_fwrt_dump_txf(fwrt, dump_data, + cfg->lmac[0].txfifo_size[i], 0, i); + } + + /* Pull TXF data from LMAC2 */ + if (fwrt->smem_cfg.num_lmacs > 1) { + for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; + i++) { + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, + TXF_LARC_NUM + + LMAC2_PRPH_OFFSET, i); + iwl_fwrt_dump_txf(fwrt, dump_data, + cfg->lmac[1].txfifo_size[i], + LMAC2_PRPH_OFFSET, + i + cfg->num_txfifo_entries); + } + } + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) { + /* Pull UMAC internal TXF data from all TXFs */ + for (i = 0; + i < ARRAY_SIZE(fwrt->smem_cfg.internal_txfifo_size); + i++) { + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = fwrt->smem_cfg.internal_txfifo_size[i]; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + continue; + + /* Add a TLV for the internal FIFOs */ + (*dump_data)->type = + cpu_to_le32(IWL_FW_ERROR_DUMP_INTERNAL_TXF); + (*dump_data)->len = + cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(i); + + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, TXF_CPU2_NUM, i + + fwrt->smem_cfg.num_txfifo_entries); + + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_FIFO_ITEM_CNT)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_WR_PTR)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_RD_PTR)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_FENCE_PTR)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_LOCK_FENCE)); + + /* Set TXF_CPU2_READ_MODIFY_ADDR to TXF_CPU2_WR_PTR */ + iwl_trans_write_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_ADDR, + TXF_CPU2_WR_PTR); + + /* Dummy-read to advance the read pointer to head */ + iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_DATA); + + /* Read FIFO */ + fifo_len /= sizeof(u32); /* Size in DWORDS */ + for (j = 0; j < fifo_len; j++) + fifo_data[j] = + iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_DATA); + *dump_data = iwl_fw_error_next_data(*dump_data); + } + } + + iwl_trans_release_nic_access(fwrt->trans); +} + +#define IWL8260_ICCM_OFFSET 0x44000 /* Only for B-step */ +#define IWL8260_ICCM_LEN 0xC000 /* Only for B-step */ + +struct iwl_prph_range { + u32 start, end; +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_comm[] = { + { .start = 0x00a00000, .end = 0x00a00000 }, + { .start = 0x00a0000c, .end = 0x00a00024 }, + { .start = 0x00a0002c, .end = 0x00a0003c }, + { .start = 0x00a00410, .end = 0x00a00418 }, + { .start = 0x00a00420, .end = 0x00a00420 }, + { .start = 0x00a00428, .end = 0x00a00428 }, + { .start = 0x00a00430, .end = 0x00a0043c }, + { .start = 0x00a00444, .end = 0x00a00444 }, + { .start = 0x00a004c0, .end = 0x00a004cc }, + { .start = 0x00a004d8, .end = 0x00a004d8 }, + { .start = 0x00a004e0, .end = 0x00a004f0 }, + { .start = 0x00a00840, .end = 0x00a00840 }, + { .start = 0x00a00850, .end = 0x00a00858 }, + { .start = 0x00a01004, .end = 0x00a01008 }, + { .start = 0x00a01010, .end = 0x00a01010 }, + { .start = 0x00a01018, .end = 0x00a01018 }, + { .start = 0x00a01024, .end = 0x00a01024 }, + { .start = 0x00a0102c, .end = 0x00a01034 }, + { .start = 0x00a0103c, .end = 0x00a01040 }, + { .start = 0x00a01048, .end = 0x00a01094 }, + { .start = 0x00a01c00, .end = 0x00a01c20 }, + { .start = 0x00a01c58, .end = 0x00a01c58 }, + { .start = 0x00a01c7c, .end = 0x00a01c7c }, + { .start = 0x00a01c28, .end = 0x00a01c54 }, + { .start = 0x00a01c5c, .end = 0x00a01c5c }, + { .start = 0x00a01c60, .end = 0x00a01cdc }, + { .start = 0x00a01ce0, .end = 0x00a01d0c }, + { .start = 0x00a01d18, .end = 0x00a01d20 }, + { .start = 0x00a01d2c, .end = 0x00a01d30 }, + { .start = 0x00a01d40, .end = 0x00a01d5c }, + { .start = 0x00a01d80, .end = 0x00a01d80 }, + { .start = 0x00a01d98, .end = 0x00a01d9c }, + { .start = 0x00a01da8, .end = 0x00a01da8 }, + { .start = 0x00a01db8, .end = 0x00a01df4 }, + { .start = 0x00a01dc0, .end = 0x00a01dfc }, + { .start = 0x00a01e00, .end = 0x00a01e2c }, + { .start = 0x00a01e40, .end = 0x00a01e60 }, + { .start = 0x00a01e68, .end = 0x00a01e6c }, + { .start = 0x00a01e74, .end = 0x00a01e74 }, + { .start = 0x00a01e84, .end = 0x00a01e90 }, + { .start = 0x00a01e9c, .end = 0x00a01ec4 }, + { .start = 0x00a01ed0, .end = 0x00a01ee0 }, + { .start = 0x00a01f00, .end = 0x00a01f1c }, + { .start = 0x00a01f44, .end = 0x00a01ffc }, + { .start = 0x00a02000, .end = 0x00a02048 }, + { .start = 0x00a02068, .end = 0x00a020f0 }, + { .start = 0x00a02100, .end = 0x00a02118 }, + { .start = 0x00a02140, .end = 0x00a0214c }, + { .start = 0x00a02168, .end = 0x00a0218c }, + { .start = 0x00a021c0, .end = 0x00a021c0 }, + { .start = 0x00a02400, .end = 0x00a02410 }, + { .start = 0x00a02418, .end = 0x00a02420 }, + { .start = 0x00a02428, .end = 0x00a0242c }, + { .start = 0x00a02434, .end = 0x00a02434 }, + { .start = 0x00a02440, .end = 0x00a02460 }, + { .start = 0x00a02468, .end = 0x00a024b0 }, + { .start = 0x00a024c8, .end = 0x00a024cc }, + { .start = 0x00a02500, .end = 0x00a02504 }, + { .start = 0x00a0250c, .end = 0x00a02510 }, + { .start = 0x00a02540, .end = 0x00a02554 }, + { .start = 0x00a02580, .end = 0x00a025f4 }, + { .start = 0x00a02600, .end = 0x00a0260c }, + { .start = 0x00a02648, .end = 0x00a02650 }, + { .start = 0x00a02680, .end = 0x00a02680 }, + { .start = 0x00a026c0, .end = 0x00a026d0 }, + { .start = 0x00a02700, .end = 0x00a0270c }, + { .start = 0x00a02804, .end = 0x00a02804 }, + { .start = 0x00a02818, .end = 0x00a0281c }, + { .start = 0x00a02c00, .end = 0x00a02db4 }, + { .start = 0x00a02df4, .end = 0x00a02fb0 }, + { .start = 0x00a03000, .end = 0x00a03014 }, + { .start = 0x00a0301c, .end = 0x00a0302c }, + { .start = 0x00a03034, .end = 0x00a03038 }, + { .start = 0x00a03040, .end = 0x00a03048 }, + { .start = 0x00a03060, .end = 0x00a03068 }, + { .start = 0x00a03070, .end = 0x00a03074 }, + { .start = 0x00a0307c, .end = 0x00a0307c }, + { .start = 0x00a03080, .end = 0x00a03084 }, + { .start = 0x00a0308c, .end = 0x00a03090 }, + { .start = 0x00a03098, .end = 0x00a03098 }, + { .start = 0x00a030a0, .end = 0x00a030a0 }, + { .start = 0x00a030a8, .end = 0x00a030b4 }, + { .start = 0x00a030bc, .end = 0x00a030bc }, + { .start = 0x00a030c0, .end = 0x00a0312c }, + { .start = 0x00a03c00, .end = 0x00a03c5c }, + { .start = 0x00a04400, .end = 0x00a04454 }, + { .start = 0x00a04460, .end = 0x00a04474 }, + { .start = 0x00a044c0, .end = 0x00a044ec }, + { .start = 0x00a04500, .end = 0x00a04504 }, + { .start = 0x00a04510, .end = 0x00a04538 }, + { .start = 0x00a04540, .end = 0x00a04548 }, + { .start = 0x00a04560, .end = 0x00a0457c }, + { .start = 0x00a04590, .end = 0x00a04598 }, + { .start = 0x00a045c0, .end = 0x00a045f4 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_9000[] = { + { .start = 0x00a05c00, .end = 0x00a05c18 }, + { .start = 0x00a05400, .end = 0x00a056e8 }, + { .start = 0x00a08000, .end = 0x00a098bc }, + { .start = 0x00a02400, .end = 0x00a02758 }, + { .start = 0x00a04764, .end = 0x00a0476c }, + { .start = 0x00a04770, .end = 0x00a04774 }, + { .start = 0x00a04620, .end = 0x00a04624 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_22000[] = { + { .start = 0x00a00000, .end = 0x00a00000 }, + { .start = 0x00a0000c, .end = 0x00a00024 }, + { .start = 0x00a0002c, .end = 0x00a00034 }, + { .start = 0x00a0003c, .end = 0x00a0003c }, + { .start = 0x00a00410, .end = 0x00a00418 }, + { .start = 0x00a00420, .end = 0x00a00420 }, + { .start = 0x00a00428, .end = 0x00a00428 }, + { .start = 0x00a00430, .end = 0x00a0043c }, + { .start = 0x00a00444, .end = 0x00a00444 }, + { .start = 0x00a00840, .end = 0x00a00840 }, + { .start = 0x00a00850, .end = 0x00a00858 }, + { .start = 0x00a01004, .end = 0x00a01008 }, + { .start = 0x00a01010, .end = 0x00a01010 }, + { .start = 0x00a01018, .end = 0x00a01018 }, + { .start = 0x00a01024, .end = 0x00a01024 }, + { .start = 0x00a0102c, .end = 0x00a01034 }, + { .start = 0x00a0103c, .end = 0x00a01040 }, + { .start = 0x00a01048, .end = 0x00a01050 }, + { .start = 0x00a01058, .end = 0x00a01058 }, + { .start = 0x00a01060, .end = 0x00a01070 }, + { .start = 0x00a0108c, .end = 0x00a0108c }, + { .start = 0x00a01c20, .end = 0x00a01c28 }, + { .start = 0x00a01d10, .end = 0x00a01d10 }, + { .start = 0x00a01e28, .end = 0x00a01e2c }, + { .start = 0x00a01e60, .end = 0x00a01e60 }, + { .start = 0x00a01e80, .end = 0x00a01e80 }, + { .start = 0x00a01ea0, .end = 0x00a01ea0 }, + { .start = 0x00a02000, .end = 0x00a0201c }, + { .start = 0x00a02024, .end = 0x00a02024 }, + { .start = 0x00a02040, .end = 0x00a02048 }, + { .start = 0x00a020c0, .end = 0x00a020e0 }, + { .start = 0x00a02400, .end = 0x00a02404 }, + { .start = 0x00a0240c, .end = 0x00a02414 }, + { .start = 0x00a0241c, .end = 0x00a0243c }, + { .start = 0x00a02448, .end = 0x00a024bc }, + { .start = 0x00a024c4, .end = 0x00a024cc }, + { .start = 0x00a02508, .end = 0x00a02508 }, + { .start = 0x00a02510, .end = 0x00a02514 }, + { .start = 0x00a0251c, .end = 0x00a0251c }, + { .start = 0x00a0252c, .end = 0x00a0255c }, + { .start = 0x00a02564, .end = 0x00a025a0 }, + { .start = 0x00a025a8, .end = 0x00a025b4 }, + { .start = 0x00a025c0, .end = 0x00a025c0 }, + { .start = 0x00a025e8, .end = 0x00a025f4 }, + { .start = 0x00a02c08, .end = 0x00a02c18 }, + { .start = 0x00a02c2c, .end = 0x00a02c38 }, + { .start = 0x00a02c68, .end = 0x00a02c78 }, + { .start = 0x00a03000, .end = 0x00a03000 }, + { .start = 0x00a03010, .end = 0x00a03014 }, + { .start = 0x00a0301c, .end = 0x00a0302c }, + { .start = 0x00a03034, .end = 0x00a03038 }, + { .start = 0x00a03040, .end = 0x00a03044 }, + { .start = 0x00a03060, .end = 0x00a03068 }, + { .start = 0x00a03070, .end = 0x00a03070 }, + { .start = 0x00a0307c, .end = 0x00a03084 }, + { .start = 0x00a0308c, .end = 0x00a03090 }, + { .start = 0x00a03098, .end = 0x00a03098 }, + { .start = 0x00a030a0, .end = 0x00a030a0 }, + { .start = 0x00a030a8, .end = 0x00a030b4 }, + { .start = 0x00a030bc, .end = 0x00a030c0 }, + { .start = 0x00a030c8, .end = 0x00a030f4 }, + { .start = 0x00a03100, .end = 0x00a0312c }, + { .start = 0x00a03c00, .end = 0x00a03c5c }, + { .start = 0x00a04400, .end = 0x00a04454 }, + { .start = 0x00a04460, .end = 0x00a04474 }, + { .start = 0x00a044c0, .end = 0x00a044ec }, + { .start = 0x00a04500, .end = 0x00a04504 }, + { .start = 0x00a04510, .end = 0x00a04538 }, + { .start = 0x00a04540, .end = 0x00a04548 }, + { .start = 0x00a04560, .end = 0x00a04560 }, + { .start = 0x00a04570, .end = 0x00a0457c }, + { .start = 0x00a04590, .end = 0x00a04590 }, + { .start = 0x00a04598, .end = 0x00a04598 }, + { .start = 0x00a045c0, .end = 0x00a045f4 }, + { .start = 0x00a05c18, .end = 0x00a05c1c }, + { .start = 0x00a0c000, .end = 0x00a0c018 }, + { .start = 0x00a0c020, .end = 0x00a0c028 }, + { .start = 0x00a0c038, .end = 0x00a0c094 }, + { .start = 0x00a0c0c0, .end = 0x00a0c104 }, + { .start = 0x00a0c10c, .end = 0x00a0c118 }, + { .start = 0x00a0c150, .end = 0x00a0c174 }, + { .start = 0x00a0c17c, .end = 0x00a0c188 }, + { .start = 0x00a0c190, .end = 0x00a0c198 }, + { .start = 0x00a0c1a0, .end = 0x00a0c1a8 }, + { .start = 0x00a0c1b0, .end = 0x00a0c1b8 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_ax210[] = { + { .start = 0x00d03c00, .end = 0x00d03c64 }, + { .start = 0x00d05c18, .end = 0x00d05c1c }, + { .start = 0x00d0c000, .end = 0x00d0c174 }, +}; + +static void iwl_read_prph_block(struct iwl_trans *trans, u32 start, + u32 len_bytes, __le32 *data) +{ + u32 i; + + for (i = 0; i < len_bytes; i += 4) + *data++ = cpu_to_le32(iwl_read_prph_no_grab(trans, start + i)); +} + +static void iwl_dump_prph(struct iwl_fw_runtime *fwrt, + const struct iwl_prph_range *iwl_prph_dump_addr, + u32 range_len, void *ptr) +{ + struct iwl_fw_error_dump_prph *prph; + struct iwl_trans *trans = fwrt->trans; + struct iwl_fw_error_dump_data **data = + (struct iwl_fw_error_dump_data **)ptr; + u32 i; + + if (!data) + return; + + IWL_DEBUG_INFO(trans, "WRT PRPH dump\n"); + + if (!iwl_trans_grab_nic_access(trans)) + return; + + for (i = 0; i < range_len; i++) { + /* The range includes both boundaries */ + int num_bytes_in_chunk = iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PRPH); + (*data)->len = cpu_to_le32(sizeof(*prph) + + num_bytes_in_chunk); + prph = (void *)(*data)->data; + prph->prph_start = cpu_to_le32(iwl_prph_dump_addr[i].start); + + iwl_read_prph_block(trans, iwl_prph_dump_addr[i].start, + /* our range is inclusive, hence + 4 */ + iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4, + (void *)prph->data); + + *data = iwl_fw_error_next_data(*data); + } + + iwl_trans_release_nic_access(trans); +} + +/* + * alloc_sgtable - allocates scallerlist table in the given size, + * fills it with pages and returns it + * @size: the size (in bytes) of the table +*/ +static struct scatterlist *alloc_sgtable(int size) +{ + int alloc_size, nents, i; + struct page *new_page; + struct scatterlist *iter; + struct scatterlist *table; + + nents = DIV_ROUND_UP(size, PAGE_SIZE); + table = kcalloc(nents, sizeof(*table), GFP_KERNEL); + if (!table) + return NULL; + sg_init_table(table, nents); + iter = table; + for_each_sg(table, iter, sg_nents(table), i) { + new_page = alloc_page(GFP_KERNEL); + if (!new_page) { + /* release all previous allocated pages in the table */ + iter = table; + for_each_sg(table, iter, sg_nents(table), i) { + new_page = sg_page(iter); + if (new_page) + __free_page(new_page); + } + kfree(table); + return NULL; + } + alloc_size = min_t(int, size, PAGE_SIZE); + size -= PAGE_SIZE; + sg_set_page(iter, new_page, alloc_size, 0); + } + return table; +} + +static void iwl_fw_get_prph_len(struct iwl_fw_runtime *fwrt, + const struct iwl_prph_range *iwl_prph_dump_addr, + u32 range_len, void *ptr) +{ + u32 *prph_len = (u32 *)ptr; + int i, num_bytes_in_chunk; + + if (!prph_len) + return; + + for (i = 0; i < range_len; i++) { + /* The range includes both boundaries */ + num_bytes_in_chunk = + iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4; + + *prph_len += sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_prph) + + num_bytes_in_chunk; + } +} + +static void iwl_fw_prph_handler(struct iwl_fw_runtime *fwrt, void *ptr, + void (*handler)(struct iwl_fw_runtime *, + const struct iwl_prph_range *, + u32, void *)) +{ + u32 range_len; + + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_ax210); + handler(fwrt, iwl_prph_dump_addr_ax210, range_len, ptr); + } else if (fwrt->trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_22000) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_22000); + handler(fwrt, iwl_prph_dump_addr_22000, range_len, ptr); + } else { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_comm); + handler(fwrt, iwl_prph_dump_addr_comm, range_len, ptr); + + if (fwrt->trans->trans_cfg->mq_rx_supported) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_9000); + handler(fwrt, iwl_prph_dump_addr_9000, range_len, ptr); + } + } +} + +static void iwl_fw_dump_mem(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + u32 len, u32 ofs, u32 type) +{ + struct iwl_fw_error_dump_mem *dump_mem; + + if (!len) + return; + + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM); + (*dump_data)->len = cpu_to_le32(len + sizeof(*dump_mem)); + dump_mem = (void *)(*dump_data)->data; + dump_mem->type = cpu_to_le32(type); + dump_mem->offset = cpu_to_le32(ofs); + iwl_trans_read_mem_bytes(fwrt->trans, ofs, dump_mem->data, len); + *dump_data = iwl_fw_error_next_data(*dump_data); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, ofs, + dump_mem->data, len); + + IWL_DEBUG_INFO(fwrt, "WRT memory dump. Type=%u\n", dump_mem->type); +} + +#define ADD_LEN(len, item_len, const_len) \ + do {size_t item = item_len; len += (!!item) * const_len + item; } \ + while (0) + +static int iwl_fw_rxf_len(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_shared_mem_cfg *mem_cfg) +{ + size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fifo); + u32 fifo_len = 0; + int i; + + if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) + return 0; + + /* Count RXF2 size */ + ADD_LEN(fifo_len, mem_cfg->rxfifo2_size, hdr_len); + + /* Count RXF1 sizes */ + if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) + mem_cfg->num_lmacs = MAX_NUM_LMAC; + + for (i = 0; i < mem_cfg->num_lmacs; i++) + ADD_LEN(fifo_len, mem_cfg->lmac[i].rxfifo1_size, hdr_len); + + return fifo_len; +} + +static int iwl_fw_txf_len(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_shared_mem_cfg *mem_cfg) +{ + size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fifo); + u32 fifo_len = 0; + int i; + + if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) + goto dump_internal_txf; + + /* Count TXF sizes */ + if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) + mem_cfg->num_lmacs = MAX_NUM_LMAC; + + for (i = 0; i < mem_cfg->num_lmacs; i++) { + int j; + + for (j = 0; j < mem_cfg->num_txfifo_entries; j++) + ADD_LEN(fifo_len, mem_cfg->lmac[i].txfifo_size[j], + hdr_len); + } + +dump_internal_txf: + if (!(iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG))) + goto out; + + for (i = 0; i < ARRAY_SIZE(mem_cfg->internal_txfifo_size); i++) + ADD_LEN(fifo_len, mem_cfg->internal_txfifo_size[i], hdr_len); + +out: + return fifo_len; +} + +static void iwl_dump_paging(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **data) +{ + int i; + + IWL_DEBUG_INFO(fwrt, "WRT paging dump\n"); + for (i = 1; i < fwrt->num_of_paging_blk + 1; i++) { + struct iwl_fw_error_dump_paging *paging; + struct page *pages = + fwrt->fw_paging_db[i].fw_paging_block; + dma_addr_t addr = fwrt->fw_paging_db[i].fw_paging_phys; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PAGING); + (*data)->len = cpu_to_le32(sizeof(*paging) + + PAGING_BLOCK_SIZE); + paging = (void *)(*data)->data; + paging->index = cpu_to_le32(i); + dma_sync_single_for_cpu(fwrt->trans->dev, addr, + PAGING_BLOCK_SIZE, + DMA_BIDIRECTIONAL); + memcpy(paging->data, page_address(pages), + PAGING_BLOCK_SIZE); + dma_sync_single_for_device(fwrt->trans->dev, addr, + PAGING_BLOCK_SIZE, + DMA_BIDIRECTIONAL); + (*data) = iwl_fw_error_next_data(*data); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, + fwrt->fw_paging_db[i].fw_offs, + paging->data, + PAGING_BLOCK_SIZE); + } +} + +static struct iwl_fw_error_dump_file * +iwl_fw_error_dump_file(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dump_ptrs *fw_error_dump, + struct iwl_fwrt_dump_data *data) +{ + struct iwl_fw_error_dump_file *dump_file; + struct iwl_fw_error_dump_data *dump_data; + struct iwl_fw_error_dump_info *dump_info; + struct iwl_fw_error_dump_smem_cfg *dump_smem_cfg; + struct iwl_fw_error_dump_trigger_desc *dump_trig; + u32 sram_len, sram_ofs; + const struct iwl_fw_dbg_mem_seg_tlv *fw_mem = fwrt->fw->dbg.mem_tlv; + struct iwl_fwrt_shared_mem_cfg *mem_cfg = &fwrt->smem_cfg; + u32 file_len, fifo_len = 0, prph_len = 0, radio_len = 0; + u32 smem_len = fwrt->fw->dbg.n_mem_tlv ? 0 : fwrt->trans->cfg->smem_len; + u32 sram2_len = fwrt->fw->dbg.n_mem_tlv ? + 0 : fwrt->trans->cfg->dccm2_len; + int i; + + /* SRAM - include stack CCM if driver knows the values for it */ + if (!fwrt->trans->cfg->dccm_offset || !fwrt->trans->cfg->dccm_len) { + const struct fw_img *img; + + if (fwrt->cur_fw_img >= IWL_UCODE_TYPE_MAX) + return NULL; + img = &fwrt->fw->img[fwrt->cur_fw_img]; + sram_ofs = img->sec[IWL_UCODE_SECTION_DATA].offset; + sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; + } else { + sram_ofs = fwrt->trans->cfg->dccm_offset; + sram_len = fwrt->trans->cfg->dccm_len; + } + + /* reading RXF/TXF sizes */ + if (test_bit(STATUS_FW_ERROR, &fwrt->trans->status)) { + fifo_len = iwl_fw_rxf_len(fwrt, mem_cfg); + fifo_len += iwl_fw_txf_len(fwrt, mem_cfg); + + /* Make room for PRPH registers */ + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_PRPH)) + iwl_fw_prph_handler(fwrt, &prph_len, + iwl_fw_get_prph_len); + + if (fwrt->trans->trans_cfg->device_family == + IWL_DEVICE_FAMILY_7000 && + iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RADIO_REG)) + radio_len = sizeof(*dump_data) + RADIO_REG_MAX_READ; + } + + file_len = sizeof(*dump_file) + fifo_len + prph_len + radio_len; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) + file_len += sizeof(*dump_data) + sizeof(*dump_info); + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) + file_len += sizeof(*dump_data) + sizeof(*dump_smem_cfg); + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { + size_t hdr_len = sizeof(*dump_data) + + sizeof(struct iwl_fw_error_dump_mem); + + /* Dump SRAM only if no mem_tlvs */ + if (!fwrt->fw->dbg.n_mem_tlv) + ADD_LEN(file_len, sram_len, hdr_len); + + /* Make room for all mem types that exist */ + ADD_LEN(file_len, smem_len, hdr_len); + ADD_LEN(file_len, sram2_len, hdr_len); + + for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) + ADD_LEN(file_len, le32_to_cpu(fw_mem[i].len), hdr_len); + } + + /* Make room for fw's virtual image pages, if it exists */ + if (iwl_fw_dbg_is_paging_enabled(fwrt)) + file_len += fwrt->num_of_paging_blk * + (sizeof(*dump_data) + + sizeof(struct iwl_fw_error_dump_paging) + + PAGING_BLOCK_SIZE); + + if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { + file_len += sizeof(*dump_data) + + fwrt->trans->cfg->d3_debug_data_length * 2; + } + + /* If we only want a monitor dump, reset the file length */ + if (data->monitor_only) { + file_len = sizeof(*dump_file) + sizeof(*dump_data) * 2 + + sizeof(*dump_info) + sizeof(*dump_smem_cfg); + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && + data->desc) + file_len += sizeof(*dump_data) + sizeof(*dump_trig) + + data->desc->len; + + dump_file = vzalloc(file_len); + if (!dump_file) + return NULL; + + fw_error_dump->fwrt_ptr = dump_file; + + dump_file->barker = cpu_to_le32(IWL_FW_ERROR_DUMP_BARKER); + dump_data = (void *)dump_file->data; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) { + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_DEV_FW_INFO); + dump_data->len = cpu_to_le32(sizeof(*dump_info)); + dump_info = (void *)dump_data->data; + dump_info->hw_type = + cpu_to_le32(CSR_HW_REV_TYPE(fwrt->trans->hw_rev)); + dump_info->hw_step = + cpu_to_le32(CSR_HW_REV_STEP(fwrt->trans->hw_rev)); + memcpy(dump_info->fw_human_readable, fwrt->fw->human_readable, + sizeof(dump_info->fw_human_readable)); + strncpy(dump_info->dev_human_readable, fwrt->trans->name, + sizeof(dump_info->dev_human_readable) - 1); +#if defined(__linux__) + strncpy(dump_info->bus_human_readable, fwrt->dev->bus->name, + sizeof(dump_info->bus_human_readable) - 1); +#elif defined(__FreeBSD__) /* XXX TODO */ + strncpy(dump_info->bus_human_readable, "", + sizeof(dump_info->bus_human_readable) - 1); +#endif + dump_info->num_of_lmacs = fwrt->smem_cfg.num_lmacs; + dump_info->lmac_err_id[0] = + cpu_to_le32(fwrt->dump.lmac_err_id[0]); + if (fwrt->smem_cfg.num_lmacs > 1) + dump_info->lmac_err_id[1] = + cpu_to_le32(fwrt->dump.lmac_err_id[1]); + dump_info->umac_err_id = cpu_to_le32(fwrt->dump.umac_err_id); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) { + /* Dump shared memory configuration */ + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_CFG); + dump_data->len = cpu_to_le32(sizeof(*dump_smem_cfg)); + dump_smem_cfg = (void *)dump_data->data; + dump_smem_cfg->num_lmacs = cpu_to_le32(mem_cfg->num_lmacs); + dump_smem_cfg->num_txfifo_entries = + cpu_to_le32(mem_cfg->num_txfifo_entries); + for (i = 0; i < MAX_NUM_LMAC; i++) { + int j; + u32 *txf_size = mem_cfg->lmac[i].txfifo_size; + + for (j = 0; j < TX_FIFO_MAX_NUM; j++) + dump_smem_cfg->lmac[i].txfifo_size[j] = + cpu_to_le32(txf_size[j]); + dump_smem_cfg->lmac[i].rxfifo1_size = + cpu_to_le32(mem_cfg->lmac[i].rxfifo1_size); + } + dump_smem_cfg->rxfifo2_size = + cpu_to_le32(mem_cfg->rxfifo2_size); + dump_smem_cfg->internal_txfifo_addr = + cpu_to_le32(mem_cfg->internal_txfifo_addr); + for (i = 0; i < TX_FIFO_INTERNAL_MAX_NUM; i++) { + dump_smem_cfg->internal_txfifo_size[i] = + cpu_to_le32(mem_cfg->internal_txfifo_size[i]); + } + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* We only dump the FIFOs if the FW is in error state */ + if (fifo_len) { + iwl_fw_dump_rxf(fwrt, &dump_data); + iwl_fw_dump_txf(fwrt, &dump_data); + } + + if (radio_len) + iwl_read_radio_regs(fwrt, &dump_data); + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && + data->desc) { + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_ERROR_INFO); + dump_data->len = cpu_to_le32(sizeof(*dump_trig) + + data->desc->len); + dump_trig = (void *)dump_data->data; + memcpy(dump_trig, &data->desc->trig_desc, + sizeof(*dump_trig) + data->desc->len); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* In case we only want monitor dump, skip to dump trasport data */ + if (data->monitor_only) + goto out; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { + const struct iwl_fw_dbg_mem_seg_tlv *fw_dbg_mem = + fwrt->fw->dbg.mem_tlv; + + if (!fwrt->fw->dbg.n_mem_tlv) + iwl_fw_dump_mem(fwrt, &dump_data, sram_len, sram_ofs, + IWL_FW_ERROR_DUMP_MEM_SRAM); + + for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) { + u32 len = le32_to_cpu(fw_dbg_mem[i].len); + u32 ofs = le32_to_cpu(fw_dbg_mem[i].ofs); + + iwl_fw_dump_mem(fwrt, &dump_data, len, ofs, + le32_to_cpu(fw_dbg_mem[i].data_type)); + } + + iwl_fw_dump_mem(fwrt, &dump_data, smem_len, + fwrt->trans->cfg->smem_offset, + IWL_FW_ERROR_DUMP_MEM_SMEM); + + iwl_fw_dump_mem(fwrt, &dump_data, sram2_len, + fwrt->trans->cfg->dccm2_offset, + IWL_FW_ERROR_DUMP_MEM_SRAM); + } + + if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { + u32 addr = fwrt->trans->cfg->d3_debug_data_base_addr; + size_t data_size = fwrt->trans->cfg->d3_debug_data_length; + + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_D3_DEBUG_DATA); + dump_data->len = cpu_to_le32(data_size * 2); + + memcpy(dump_data->data, fwrt->dump.d3_debug_data, data_size); + + kfree(fwrt->dump.d3_debug_data); + fwrt->dump.d3_debug_data = NULL; + + iwl_trans_read_mem_bytes(fwrt->trans, addr, + dump_data->data + data_size, + data_size); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, addr, + dump_data->data + data_size, + data_size); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* Dump fw's virtual image */ + if (iwl_fw_dbg_is_paging_enabled(fwrt)) + iwl_dump_paging(fwrt, &dump_data); + + if (prph_len) + iwl_fw_prph_handler(fwrt, &dump_data, iwl_dump_prph); + +out: + dump_file->file_len = cpu_to_le32(file_len); + return dump_file; +} + +/** + * struct iwl_dump_ini_region_data - region data + * @reg_tlv: region TLV + * @dump_data: dump data + */ +struct iwl_dump_ini_region_data { + struct iwl_ucode_tlv *reg_tlv; + struct iwl_fwrt_dump_data *dump_data; +}; + +static int +iwl_dump_ini_prph_mac_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 prph_val; + u32 addr = le32_to_cpu(reg->addrs[idx]) + + le32_to_cpu(reg->dev_addr.offset); + int i; + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + for (i = 0; i < le32_to_cpu(reg->dev_addr.size); i += 4) { + prph_val = iwl_read_prph(fwrt->trans, addr + i); + if (prph_val == 0x5a5a5a5a) + return -EBUSY; + *val++ = cpu_to_le32(prph_val); + } + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_prph_phy_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 indirect_wr_addr = WMAL_INDRCT_RD_CMD1; + u32 indirect_rd_addr = WMAL_MRSPF_1; + u32 prph_val; + u32 addr = le32_to_cpu(reg->addrs[idx]); + u32 dphy_state; + u32 dphy_addr; + int i; + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + + if (fwrt->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + indirect_wr_addr = WMAL_INDRCT_CMD1; + + indirect_wr_addr += le32_to_cpu(reg->dev_addr.offset); + indirect_rd_addr += le32_to_cpu(reg->dev_addr.offset); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return -EBUSY; + + dphy_addr = (reg->dev_addr.offset) ? WFPM_LMAC2_PS_CTL_RW : + WFPM_LMAC1_PS_CTL_RW; + dphy_state = iwl_read_umac_prph_no_grab(fwrt->trans, dphy_addr); + + for (i = 0; i < le32_to_cpu(reg->dev_addr.size); i += 4) { + if (dphy_state == HBUS_TIMEOUT || + (dphy_state & WFPM_PS_CTL_RW_PHYRF_PD_FSM_CURSTATE_MSK) != + WFPM_PHYRF_STATE_ON) { + *val++ = cpu_to_le32(WFPM_DPHY_OFF); + continue; + } + + iwl_write_prph_no_grab(fwrt->trans, indirect_wr_addr, + WMAL_INDRCT_CMD(addr + i)); + prph_val = iwl_read_prph_no_grab(fwrt->trans, + indirect_rd_addr); + *val++ = cpu_to_le32(prph_val); + } + + iwl_trans_release_nic_access(fwrt->trans); + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_csr_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 addr = le32_to_cpu(reg->addrs[idx]) + + le32_to_cpu(reg->dev_addr.offset); + int i; + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + for (i = 0; i < le32_to_cpu(reg->dev_addr.size); i += 4) + *val++ = cpu_to_le32(iwl_trans_read32(fwrt->trans, addr + i)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_config_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_trans *trans = fwrt->trans; + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 addr = le32_to_cpu(reg->addrs[idx]) + + le32_to_cpu(reg->dev_addr.offset); + int i; + + /* we shouldn't get here if the trans doesn't have read_config32 */ + if (WARN_ON_ONCE(!trans->ops->read_config32)) + return -EOPNOTSUPP; + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + for (i = 0; i < le32_to_cpu(reg->dev_addr.size); i += 4) { + int ret; + u32 tmp; + + ret = trans->ops->read_config32(trans, addr + i, &tmp); + if (ret < 0) + return ret; + + *val++ = cpu_to_le32(tmp); + } + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_dev_mem_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + u32 addr = le32_to_cpu(reg->addrs[idx]) + + le32_to_cpu(reg->dev_addr.offset); + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + iwl_trans_read_mem_bytes(fwrt->trans, addr, range->data, + le32_to_cpu(reg->dev_addr.size)); + + if ((le32_to_cpu(reg->id) & IWL_FW_INI_REGION_V2_MASK) == + IWL_FW_INI_HW_SMEM_REGION_ID && + fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) + fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, + range->data, + le32_to_cpu(reg->dev_addr.size)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int _iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, + void *range_ptr, int idx) +{ + struct page *page = fwrt->fw_paging_db[idx].fw_paging_block; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + dma_addr_t addr = fwrt->fw_paging_db[idx].fw_paging_phys; + u32 page_size = fwrt->fw_paging_db[idx].fw_paging_size; + + range->page_num = cpu_to_le32(idx); + range->range_data_size = cpu_to_le32(page_size); + dma_sync_single_for_cpu(fwrt->trans->dev, addr, page_size, + DMA_BIDIRECTIONAL); + memcpy(range->data, page_address(page), page_size); + dma_sync_single_for_device(fwrt->trans->dev, addr, page_size, + DMA_BIDIRECTIONAL); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_error_dump_range *range; + u32 page_size; + + /* all paged index start from 1 to skip CSS section */ + idx++; + + if (!fwrt->trans->trans_cfg->gen2) + return _iwl_dump_ini_paging_iter(fwrt, range_ptr, idx); + + range = range_ptr; + page_size = fwrt->trans->init_dram.paging[idx].size; + + range->page_num = cpu_to_le32(idx); + range->range_data_size = cpu_to_le32(page_size); + memcpy(range->data, fwrt->trans->init_dram.paging[idx].block, + page_size); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_mon_dram_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + struct iwl_dram_data *frag; + u32 alloc_id = le32_to_cpu(reg->dram_alloc_id); + + frag = &fwrt->trans->dbg.fw_mon_ini[alloc_id].frags[idx]; + + range->dram_base_addr = cpu_to_le64(frag->physical); + range->range_data_size = cpu_to_le32(frag->size); + + memcpy(range->data, frag->block, frag->size); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_mon_smem_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + u32 addr = le32_to_cpu(reg->internal_buffer.base_addr); + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->internal_buffer.size; + iwl_trans_read_mem_bytes(fwrt->trans, addr, range->data, + le32_to_cpu(reg->internal_buffer.size)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static bool iwl_ini_txf_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_txf_iter_data *iter = &fwrt->dump.txf_iter_data; + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + int txf_num = cfg->num_txfifo_entries; + int int_txf_num = ARRAY_SIZE(cfg->internal_txfifo_size); + u32 lmac_bitmap = le32_to_cpu(reg->fifos.fid[0]); + + if (!idx) { + if (le32_to_cpu(reg->fifos.offset) && cfg->num_lmacs == 1) { + IWL_ERR(fwrt, "WRT: Invalid lmac offset 0x%x\n", + le32_to_cpu(reg->fifos.offset)); + return false; + } + + iter->internal_txf = 0; + iter->fifo_size = 0; + iter->fifo = -1; + if (le32_to_cpu(reg->fifos.offset)) + iter->lmac = 1; + else + iter->lmac = 0; + } + + if (!iter->internal_txf) { + for (iter->fifo++; iter->fifo < txf_num; iter->fifo++) { + iter->fifo_size = + cfg->lmac[iter->lmac].txfifo_size[iter->fifo]; + if (iter->fifo_size && (lmac_bitmap & BIT(iter->fifo))) + return true; + } + iter->fifo--; + } + + iter->internal_txf = 1; + + if (!fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) + return false; + + for (iter->fifo++; iter->fifo < int_txf_num + txf_num; iter->fifo++) { + iter->fifo_size = + cfg->internal_txfifo_size[iter->fifo - txf_num]; + if (iter->fifo_size && (lmac_bitmap & BIT(iter->fifo))) + return true; + } + + return false; +} + +static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + struct iwl_txf_iter_data *iter = &fwrt->dump.txf_iter_data; + struct iwl_fw_ini_error_dump_register *reg_dump = (void *)range->data; + u32 offs = le32_to_cpu(reg->fifos.offset), addr; + u32 registers_num = iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); + u32 registers_size = registers_num * sizeof(*reg_dump); + __le32 *data; + int i; + + if (!iwl_ini_txf_iter(fwrt, reg_data, idx)) + return -EIO; + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return -EBUSY; + + range->fifo_hdr.fifo_num = cpu_to_le32(iter->fifo); + range->fifo_hdr.num_of_registers = cpu_to_le32(registers_num); + range->range_data_size = cpu_to_le32(iter->fifo_size + registers_size); + + iwl_write_prph_no_grab(fwrt->trans, TXF_LARC_NUM + offs, iter->fifo); + + /* + * read txf registers. for each register, write to the dump the + * register address and its value + */ + for (i = 0; i < registers_num; i++) { + addr = le32_to_cpu(reg->addrs[i]) + offs; + + reg_dump->addr = cpu_to_le32(addr); + reg_dump->data = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, + addr)); + + reg_dump++; + } + + if (reg->fifos.hdr_only) { + range->range_data_size = cpu_to_le32(registers_size); + goto out; + } + + /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ + iwl_write_prph_no_grab(fwrt->trans, TXF_READ_MODIFY_ADDR + offs, + TXF_WR_PTR + offs); + + /* Dummy-read to advance the read pointer to the head */ + iwl_read_prph_no_grab(fwrt->trans, TXF_READ_MODIFY_DATA + offs); + + /* Read FIFO */ + addr = TXF_READ_MODIFY_DATA + offs; + data = (void *)reg_dump; + for (i = 0; i < iter->fifo_size; i += sizeof(*data)) + *data++ = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, addr)); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) + fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, + reg_dump, iter->fifo_size); + +out: + iwl_trans_release_nic_access(fwrt->trans); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +struct iwl_ini_rxf_data { + u32 fifo_num; + u32 size; + u32 offset; +}; + +static void iwl_ini_get_rxf_data(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + struct iwl_ini_rxf_data *data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 fid1 = le32_to_cpu(reg->fifos.fid[0]); + u32 fid2 = le32_to_cpu(reg->fifos.fid[1]); + u8 fifo_idx; + + if (!data) + return; + + /* make sure only one bit is set in only one fid */ + if (WARN_ONCE(hweight_long(fid1) + hweight_long(fid2) != 1, + "fid1=%x, fid2=%x\n", fid1, fid2)) + return; + + memset(data, 0, sizeof(*data)); + + if (fid1) { + fifo_idx = ffs(fid1) - 1; + if (WARN_ONCE(fifo_idx >= MAX_NUM_LMAC, "fifo_idx=%d\n", + fifo_idx)) + return; + + data->size = fwrt->smem_cfg.lmac[fifo_idx].rxfifo1_size; + data->fifo_num = fifo_idx; + } else { + u8 max_idx; + + fifo_idx = ffs(fid2) - 1; + if (iwl_fw_lookup_notif_ver(fwrt->fw, SYSTEM_GROUP, + SHARED_MEM_CFG_CMD, 0) <= 3) + max_idx = 0; + else + max_idx = 1; + + if (WARN_ONCE(fifo_idx > max_idx, + "invalid umac fifo idx %d", fifo_idx)) + return; + + /* use bit 31 to distinguish between umac and lmac rxf while + * parsing the dump + */ + data->fifo_num = fifo_idx | IWL_RXF_UMAC_BIT; + + switch (fifo_idx) { + case 0: + data->size = fwrt->smem_cfg.rxfifo2_size; + data->offset = iwl_umac_prph(fwrt->trans, + RXF_DIFF_FROM_PREV); + break; + case 1: + data->size = fwrt->smem_cfg.rxfifo2_control_size; + data->offset = iwl_umac_prph(fwrt->trans, + RXF2C_DIFF_FROM_PREV); + break; + } + } +} + +static int iwl_dump_ini_rxf_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + struct iwl_ini_rxf_data rxf_data; + struct iwl_fw_ini_error_dump_register *reg_dump = (void *)range->data; + u32 offs = le32_to_cpu(reg->fifos.offset), addr; + u32 registers_num = iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); + u32 registers_size = registers_num * sizeof(*reg_dump); + __le32 *data; + int i; + + iwl_ini_get_rxf_data(fwrt, reg_data, &rxf_data); + if (!rxf_data.size) + return -EIO; + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return -EBUSY; + + range->fifo_hdr.fifo_num = cpu_to_le32(rxf_data.fifo_num); + range->fifo_hdr.num_of_registers = cpu_to_le32(registers_num); + range->range_data_size = cpu_to_le32(rxf_data.size + registers_size); + + /* + * read rxf registers. for each register, write to the dump the + * register address and its value + */ + for (i = 0; i < registers_num; i++) { + addr = le32_to_cpu(reg->addrs[i]) + offs; + + reg_dump->addr = cpu_to_le32(addr); + reg_dump->data = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, + addr)); + + reg_dump++; + } + + if (reg->fifos.hdr_only) { + range->range_data_size = cpu_to_le32(registers_size); + goto out; + } + + offs = rxf_data.offset; + + /* Lock fence */ + iwl_write_prph_no_grab(fwrt->trans, RXF_SET_FENCE_MODE + offs, 0x1); + /* Set fence pointer to the same place like WR pointer */ + iwl_write_prph_no_grab(fwrt->trans, RXF_LD_WR2FENCE + offs, 0x1); + /* Set fence offset */ + iwl_write_prph_no_grab(fwrt->trans, RXF_LD_FENCE_OFFSET_ADDR + offs, + 0x0); + + /* Read FIFO */ + addr = RXF_FIFO_RD_FENCE_INC + offs; + data = (void *)reg_dump; + for (i = 0; i < rxf_data.size; i += sizeof(*data)) + *data++ = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, addr)); + +out: + iwl_trans_release_nic_access(fwrt->trans); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_err_table_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_region_err_table *err_table = ®->err_table; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + u32 addr = le32_to_cpu(err_table->base_addr) + + le32_to_cpu(err_table->offset); + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = err_table->size; + iwl_trans_read_mem_bytes(fwrt->trans, addr, range->data, + le32_to_cpu(err_table->size)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_special_mem_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_region_special_device_memory *special_mem = + ®->special_mem; + + struct iwl_fw_ini_error_dump_range *range = range_ptr; + u32 addr = le32_to_cpu(special_mem->base_addr) + + le32_to_cpu(special_mem->offset); + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = special_mem->size; + iwl_trans_read_mem_bytes(fwrt->trans, addr, range->data, + le32_to_cpu(special_mem->size)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_dbgi_sram_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 prph_data; + int i; + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return -EBUSY; + + range->range_data_size = reg->dev_addr.size; + iwl_write_prph_no_grab(fwrt->trans, DBGI_SRAM_TARGET_ACCESS_CFG, + DBGI_SRAM_TARGET_ACCESS_CFG_RESET_ADDRESS_MSK); + for (i = 0; i < (le32_to_cpu(reg->dev_addr.size) / 4); i++) { + prph_data = iwl_read_prph(fwrt->trans, (i % 2) ? + DBGI_SRAM_TARGET_ACCESS_RDATA_MSB : + DBGI_SRAM_TARGET_ACCESS_RDATA_LSB); + if (prph_data == 0x5a5a5a5a) { + iwl_trans_release_nic_access(fwrt->trans); + return -EBUSY; + } + *val++ = cpu_to_le32(prph_data); + } + iwl_trans_release_nic_access(fwrt->trans); + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_fw_pkt_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, int idx) +{ + struct iwl_fw_ini_error_dump_range *range = range_ptr; + struct iwl_rx_packet *pkt = reg_data->dump_data->fw_pkt; + u32 pkt_len; + + if (!pkt) + return -EIO; + + pkt_len = iwl_rx_packet_payload_len(pkt); + + memcpy(&range->fw_pkt_hdr, &pkt->hdr, sizeof(range->fw_pkt_hdr)); + range->range_data_size = cpu_to_le32(pkt_len); + + memcpy(range->data, pkt->data, pkt_len); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static void * +iwl_dump_ini_mem_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data) +{ + struct iwl_fw_ini_error_dump *dump = data; + + dump->header.version = cpu_to_le32(IWL_INI_DUMP_VER); + + return dump->data; +} + +/** + * mask_apply_and_normalize - applies mask on val and normalize the result + * + * The normalization is based on the first set bit in the mask + * + * @val: value + * @mask: mask to apply and to normalize with + */ +static u32 mask_apply_and_normalize(u32 val, u32 mask) +{ + return (val & mask) >> (ffs(mask) - 1); +} + +static __le32 iwl_get_mon_reg(struct iwl_fw_runtime *fwrt, u32 alloc_id, + const struct iwl_fw_mon_reg *reg_info) +{ + u32 val, offs; + + /* The header addresses of DBGCi is calculate as follows: + * DBGC1 address + (0x100 * i) + */ + offs = (alloc_id - IWL_FW_INI_ALLOCATION_ID_DBGC1) * 0x100; + + if (!reg_info || !reg_info->addr || !reg_info->mask) + return 0; + + val = iwl_read_prph_no_grab(fwrt->trans, reg_info->addr + offs); + + return cpu_to_le32(mask_apply_and_normalize(val, reg_info->mask)); +} + +static void * +iwl_dump_ini_mon_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + struct iwl_fw_ini_monitor_dump *data, + const struct iwl_fw_mon_regs *addrs) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 alloc_id = le32_to_cpu(reg->dram_alloc_id); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) { + IWL_ERR(fwrt, "Failed to get monitor header\n"); + return NULL; + } + + data->write_ptr = iwl_get_mon_reg(fwrt, alloc_id, + &addrs->write_ptr); + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + u32 wrt_ptr = le32_to_cpu(data->write_ptr); + + data->write_ptr = cpu_to_le32(wrt_ptr >> 2); + } + data->cycle_cnt = iwl_get_mon_reg(fwrt, alloc_id, + &addrs->cycle_cnt); + data->cur_frag = iwl_get_mon_reg(fwrt, alloc_id, + &addrs->cur_frag); + + iwl_trans_release_nic_access(fwrt->trans); + + data->header.version = cpu_to_le32(IWL_INI_DUMP_VER); + + return data->data; +} + +static void * +iwl_dump_ini_mon_dram_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data) +{ + struct iwl_fw_ini_monitor_dump *mon_dump = (void *)data; + + return iwl_dump_ini_mon_fill_header(fwrt, reg_data, mon_dump, + &fwrt->trans->cfg->mon_dram_regs); +} + +static void * +iwl_dump_ini_mon_smem_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data) +{ + struct iwl_fw_ini_monitor_dump *mon_dump = (void *)data; + + return iwl_dump_ini_mon_fill_header(fwrt, reg_data, mon_dump, + &fwrt->trans->cfg->mon_smem_regs); +} + +static void * +iwl_dump_ini_err_table_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_err_table_dump *dump = data; + + dump->header.version = cpu_to_le32(IWL_INI_DUMP_VER); + dump->version = reg->err_table.version; + + return dump->data; +} + +static void * +iwl_dump_ini_special_mem_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_special_device_memory *dump = data; + + dump->header.version = cpu_to_le32(IWL_INI_DUMP_VER); + dump->type = reg->special_mem.type; + dump->version = reg->special_mem.version; + + return dump->data; +} + +static u32 iwl_dump_ini_mem_ranges(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + + return iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); +} + +static u32 iwl_dump_ini_paging_ranges(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + if (fwrt->trans->trans_cfg->gen2) { + if (fwrt->trans->init_dram.paging_cnt) + return fwrt->trans->init_dram.paging_cnt - 1; + else + return 0; + } + + return fwrt->num_of_paging_blk; +} + +static u32 +iwl_dump_ini_mon_dram_ranges(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_mon *fw_mon; + u32 ranges = 0, alloc_id = le32_to_cpu(reg->dram_alloc_id); + int i; + + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + for (i = 0; i < fw_mon->num_frags; i++) { + if (!fw_mon->frags[i].size) + break; + + ranges++; + } + + return ranges; +} + +static u32 iwl_dump_ini_txf_ranges(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + u32 num_of_fifos = 0; + + while (iwl_ini_txf_iter(fwrt, reg_data, num_of_fifos)) + num_of_fifos++; + + return num_of_fifos; +} + +static u32 iwl_dump_ini_single_range(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + return 1; +} + +static u32 iwl_dump_ini_mem_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 size = le32_to_cpu(reg->dev_addr.size); + u32 ranges = iwl_dump_ini_mem_ranges(fwrt, reg_data); + + if (!size || !ranges) + return 0; + + return sizeof(struct iwl_fw_ini_error_dump) + ranges * + (size + sizeof(struct iwl_fw_ini_error_dump_range)); +} + +static u32 +iwl_dump_ini_paging_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + int i; + u32 range_header_len = sizeof(struct iwl_fw_ini_error_dump_range); + u32 size = sizeof(struct iwl_fw_ini_error_dump); + + /* start from 1 to skip CSS section */ + for (i = 1; i <= iwl_dump_ini_paging_ranges(fwrt, reg_data); i++) { + size += range_header_len; + if (fwrt->trans->trans_cfg->gen2) + size += fwrt->trans->init_dram.paging[i].size; + else + size += fwrt->fw_paging_db[i].fw_paging_size; + } + + return size; +} + +static u32 +iwl_dump_ini_mon_dram_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_mon *fw_mon; + u32 size = 0, alloc_id = le32_to_cpu(reg->dram_alloc_id); + int i; + + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + for (i = 0; i < fw_mon->num_frags; i++) { + struct iwl_dram_data *frag = &fw_mon->frags[i]; + + if (!frag->size) + break; + + size += sizeof(struct iwl_fw_ini_error_dump_range) + frag->size; + } + + if (size) + size += sizeof(struct iwl_fw_ini_monitor_dump); + + return size; +} + +static u32 +iwl_dump_ini_mon_smem_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 size; + + size = le32_to_cpu(reg->internal_buffer.size); + if (!size) + return 0; + + size += sizeof(struct iwl_fw_ini_monitor_dump) + + sizeof(struct iwl_fw_ini_error_dump_range); + + return size; +} + +static u32 iwl_dump_ini_txf_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_txf_iter_data *iter = &fwrt->dump.txf_iter_data; + u32 registers_num = iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); + u32 size = 0; + u32 fifo_hdr = sizeof(struct iwl_fw_ini_error_dump_range) + + registers_num * + sizeof(struct iwl_fw_ini_error_dump_register); + + while (iwl_ini_txf_iter(fwrt, reg_data, size)) { + size += fifo_hdr; + if (!reg->fifos.hdr_only) + size += iter->fifo_size; + } + + if (!size) + return 0; + + return size + sizeof(struct iwl_fw_ini_error_dump); +} + +static u32 iwl_dump_ini_rxf_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_ini_rxf_data rx_data; + u32 registers_num = iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); + u32 size = sizeof(struct iwl_fw_ini_error_dump) + + sizeof(struct iwl_fw_ini_error_dump_range) + + registers_num * sizeof(struct iwl_fw_ini_error_dump_register); + + if (reg->fifos.hdr_only) + return size; + + iwl_ini_get_rxf_data(fwrt, reg_data, &rx_data); + size += rx_data.size; + + return size; +} + +static u32 +iwl_dump_ini_err_table_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 size = le32_to_cpu(reg->err_table.size); + + if (size) + size += sizeof(struct iwl_fw_ini_err_table_dump) + + sizeof(struct iwl_fw_ini_error_dump_range); + + return size; +} + +static u32 +iwl_dump_ini_special_mem_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 size = le32_to_cpu(reg->special_mem.size); + + if (size) + size += sizeof(struct iwl_fw_ini_special_device_memory) + + sizeof(struct iwl_fw_ini_error_dump_range); + + return size; +} + +static u32 +iwl_dump_ini_fw_pkt_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + u32 size = 0; + + if (!reg_data->dump_data->fw_pkt) + return 0; + + size += iwl_rx_packet_payload_len(reg_data->dump_data->fw_pkt); + if (size) + size += sizeof(struct iwl_fw_ini_error_dump) + + sizeof(struct iwl_fw_ini_error_dump_range); + + return size; +} + +/** + * struct iwl_dump_ini_mem_ops - ini memory dump operations + * @get_num_of_ranges: returns the number of memory ranges in the region. + * @get_size: returns the total size of the region. + * @fill_mem_hdr: fills region type specific headers and returns pointer to + * the first range or NULL if failed to fill headers. + * @fill_range: copies a given memory range into the dump. + * Returns the size of the range or negative error value otherwise. + */ +struct iwl_dump_ini_mem_ops { + u32 (*get_num_of_ranges)(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data); + u32 (*get_size)(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data); + void *(*fill_mem_hdr)(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data); + int (*fill_range)(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range, int idx); +}; + +/** + * iwl_dump_ini_mem + * + * Creates a dump tlv and copy a memory region into it. + * Returns the size of the current dump tlv or 0 if failed + * + * @fwrt: fw runtime struct + * @list: list to add the dump tlv to + * @reg_data: memory region + * @ops: memory dump operations + */ +static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, + struct iwl_dump_ini_region_data *reg_data, + const struct iwl_dump_ini_mem_ops *ops) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_dump_entry *entry; + struct iwl_fw_error_dump_data *tlv; + struct iwl_fw_ini_error_dump_header *header; + u32 type = le32_to_cpu(reg->type), id = le32_to_cpu(reg->id); + u32 num_of_ranges, i, size; + void *range; + + /* + * The higher part of the ID in version 2 is irrelevant for + * us, so mask it out. + */ + if (le32_to_cpu(reg->hdr.version) == 2) + id &= IWL_FW_INI_REGION_V2_MASK; + + if (!ops->get_num_of_ranges || !ops->get_size || !ops->fill_mem_hdr || + !ops->fill_range) + return 0; + + size = ops->get_size(fwrt, reg_data); + if (!size) + return 0; + + entry = vzalloc(sizeof(*entry) + sizeof(*tlv) + size); + if (!entry) + return 0; + + entry->size = sizeof(*tlv) + size; + + tlv = (void *)entry->data; + tlv->type = reg->type; + tlv->len = cpu_to_le32(size); + + IWL_DEBUG_FW(fwrt, "WRT: Collecting region: id=%d, type=%d\n", id, + type); + + num_of_ranges = ops->get_num_of_ranges(fwrt, reg_data); + + header = (void *)tlv->data; + header->region_id = cpu_to_le32(id); + header->num_of_ranges = cpu_to_le32(num_of_ranges); + header->name_len = cpu_to_le32(IWL_FW_INI_MAX_NAME); + memcpy(header->name, reg->name, IWL_FW_INI_MAX_NAME); + + range = ops->fill_mem_hdr(fwrt, reg_data, header); + if (!range) { + IWL_ERR(fwrt, + "WRT: Failed to fill region header: id=%d, type=%d\n", + id, type); + goto out_err; + } + + for (i = 0; i < num_of_ranges; i++) { + int range_size = ops->fill_range(fwrt, reg_data, range, i); + + if (range_size < 0) { + IWL_ERR(fwrt, + "WRT: Failed to dump region: id=%d, type=%d\n", + id, type); + goto out_err; + } + range = (u8 *)range + range_size; + } + + list_add_tail(&entry->list, list); + + return entry->size; + +out_err: + vfree(entry); + + return 0; +} + +static u32 iwl_dump_ini_info(struct iwl_fw_runtime *fwrt, + struct iwl_fw_ini_trigger_tlv *trigger, + struct list_head *list) +{ + struct iwl_fw_ini_dump_entry *entry; + struct iwl_fw_error_dump_data *tlv; + struct iwl_fw_ini_dump_info *dump; + struct iwl_dbg_tlv_node *node; + struct iwl_fw_ini_dump_cfg_name *cfg_name; + u32 size = sizeof(*tlv) + sizeof(*dump); + u32 num_of_cfg_names = 0; + u32 hw_type; + + list_for_each_entry(node, &fwrt->trans->dbg.debug_info_tlv_list, list) { + size += sizeof(*cfg_name); + num_of_cfg_names++; + } + + entry = vzalloc(sizeof(*entry) + size); + if (!entry) + return 0; + + entry->size = size; + + tlv = (void *)entry->data; + tlv->type = cpu_to_le32(IWL_INI_DUMP_INFO_TYPE); + tlv->len = cpu_to_le32(size - sizeof(*tlv)); + + dump = (void *)tlv->data; + + dump->version = cpu_to_le32(IWL_INI_DUMP_VER); + dump->time_point = trigger->time_point; + dump->trigger_reason = trigger->trigger_reason; + dump->external_cfg_state = + cpu_to_le32(fwrt->trans->dbg.external_ini_cfg); + + dump->ver_type = cpu_to_le32(fwrt->dump.fw_ver.type); + dump->ver_subtype = cpu_to_le32(fwrt->dump.fw_ver.subtype); + + dump->hw_step = cpu_to_le32(CSR_HW_REV_STEP(fwrt->trans->hw_rev)); + + /* + * Several HWs all have type == 0x42, so we'll override this value + * according to the detected HW + */ + hw_type = CSR_HW_REV_TYPE(fwrt->trans->hw_rev); + if (hw_type == IWL_AX210_HW_TYPE) { + u32 prph_val = iwl_read_prph(fwrt->trans, WFPM_OTP_CFG1_ADDR_GEN2); + u32 is_jacket = !!(prph_val & WFPM_OTP_CFG1_IS_JACKET_BIT); + u32 is_cdb = !!(prph_val & WFPM_OTP_CFG1_IS_CDB_BIT); + u32 masked_bits = is_jacket | (is_cdb << 1); + + /* + * The HW type depends on certain bits in this case, so add + * these bits to the HW type. We won't have collisions since we + * add these bits after the highest possible bit in the mask. + */ + hw_type |= masked_bits << IWL_AX210_HW_TYPE_ADDITION_SHIFT; + } + dump->hw_type = cpu_to_le32(hw_type); + + dump->rf_id_flavor = + cpu_to_le32(CSR_HW_RFID_FLAVOR(fwrt->trans->hw_rf_id)); + dump->rf_id_dash = cpu_to_le32(CSR_HW_RFID_DASH(fwrt->trans->hw_rf_id)); + dump->rf_id_step = cpu_to_le32(CSR_HW_RFID_STEP(fwrt->trans->hw_rf_id)); + dump->rf_id_type = cpu_to_le32(CSR_HW_RFID_TYPE(fwrt->trans->hw_rf_id)); + + dump->lmac_major = cpu_to_le32(fwrt->dump.fw_ver.lmac_major); + dump->lmac_minor = cpu_to_le32(fwrt->dump.fw_ver.lmac_minor); + dump->umac_major = cpu_to_le32(fwrt->dump.fw_ver.umac_major); + dump->umac_minor = cpu_to_le32(fwrt->dump.fw_ver.umac_minor); + + dump->fw_mon_mode = cpu_to_le32(fwrt->trans->dbg.ini_dest); + dump->regions_mask = trigger->regions_mask & + ~cpu_to_le64(fwrt->trans->dbg.unsupported_region_msk); + + dump->build_tag_len = cpu_to_le32(sizeof(dump->build_tag)); + memcpy(dump->build_tag, fwrt->fw->human_readable, + sizeof(dump->build_tag)); + + cfg_name = dump->cfg_names; + dump->num_of_cfg_names = cpu_to_le32(num_of_cfg_names); + list_for_each_entry(node, &fwrt->trans->dbg.debug_info_tlv_list, list) { + struct iwl_fw_ini_debug_info_tlv *debug_info = + (void *)node->tlv.data; + + cfg_name->image_type = debug_info->image_type; + cfg_name->cfg_name_len = + cpu_to_le32(IWL_FW_INI_MAX_CFG_NAME); + memcpy(cfg_name->cfg_name, debug_info->debug_cfg_name, + sizeof(cfg_name->cfg_name)); + cfg_name++; + } + + /* add dump info TLV to the beginning of the list since it needs to be + * the first TLV in the dump + */ + list_add(&entry->list, list); + + return entry->size; +} + +static const struct iwl_dump_ini_mem_ops iwl_dump_ini_region_ops[] = { + [IWL_FW_INI_REGION_INVALID] = {}, + [IWL_FW_INI_REGION_INTERNAL_BUFFER] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_mon_smem_get_size, + .fill_mem_hdr = iwl_dump_ini_mon_smem_fill_header, + .fill_range = iwl_dump_ini_mon_smem_iter, + }, + [IWL_FW_INI_REGION_DRAM_BUFFER] = { + .get_num_of_ranges = iwl_dump_ini_mon_dram_ranges, + .get_size = iwl_dump_ini_mon_dram_get_size, + .fill_mem_hdr = iwl_dump_ini_mon_dram_fill_header, + .fill_range = iwl_dump_ini_mon_dram_iter, + }, + [IWL_FW_INI_REGION_TXF] = { + .get_num_of_ranges = iwl_dump_ini_txf_ranges, + .get_size = iwl_dump_ini_txf_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_txf_iter, + }, + [IWL_FW_INI_REGION_RXF] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_rxf_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_rxf_iter, + }, + [IWL_FW_INI_REGION_LMAC_ERROR_TABLE] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_err_table_get_size, + .fill_mem_hdr = iwl_dump_ini_err_table_fill_header, + .fill_range = iwl_dump_ini_err_table_iter, + }, + [IWL_FW_INI_REGION_UMAC_ERROR_TABLE] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_err_table_get_size, + .fill_mem_hdr = iwl_dump_ini_err_table_fill_header, + .fill_range = iwl_dump_ini_err_table_iter, + }, + [IWL_FW_INI_REGION_RSP_OR_NOTIF] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_fw_pkt_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_fw_pkt_iter, + }, + [IWL_FW_INI_REGION_DEVICE_MEMORY] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_dev_mem_iter, + }, + [IWL_FW_INI_REGION_PERIPHERY_MAC] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_prph_mac_iter, + }, + [IWL_FW_INI_REGION_PERIPHERY_PHY] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_prph_phy_iter, + }, + [IWL_FW_INI_REGION_PERIPHERY_AUX] = {}, + [IWL_FW_INI_REGION_PAGING] = { + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .get_num_of_ranges = iwl_dump_ini_paging_ranges, + .get_size = iwl_dump_ini_paging_get_size, + .fill_range = iwl_dump_ini_paging_iter, + }, + [IWL_FW_INI_REGION_CSR] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_csr_iter, + }, + [IWL_FW_INI_REGION_DRAM_IMR] = {}, + [IWL_FW_INI_REGION_PCI_IOSF_CONFIG] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_config_iter, + }, + [IWL_FW_INI_REGION_SPECIAL_DEVICE_MEMORY] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_special_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_special_mem_fill_header, + .fill_range = iwl_dump_ini_special_mem_iter, + }, + [IWL_FW_INI_REGION_DBGI_SRAM] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_dbgi_sram_iter, + }, +}; + +static u32 iwl_dump_ini_trigger(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + struct list_head *list) +{ + struct iwl_fw_ini_trigger_tlv *trigger = dump_data->trig; + enum iwl_fw_ini_time_point tp_id = le32_to_cpu(trigger->time_point); + struct iwl_dump_ini_region_data reg_data = { + .dump_data = dump_data, + }; + int i; + u32 size = 0; + u64 regions_mask = le64_to_cpu(trigger->regions_mask) & + ~(fwrt->trans->dbg.unsupported_region_msk); + + BUILD_BUG_ON(sizeof(trigger->regions_mask) != sizeof(regions_mask)); + BUILD_BUG_ON((sizeof(trigger->regions_mask) * BITS_PER_BYTE) < + ARRAY_SIZE(fwrt->trans->dbg.active_regions)); + + for (i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.active_regions); i++) { + u32 reg_type; + struct iwl_fw_ini_region_tlv *reg; + + if (!(BIT_ULL(i) & regions_mask)) + continue; + + reg_data.reg_tlv = fwrt->trans->dbg.active_regions[i]; + if (!reg_data.reg_tlv) { + IWL_WARN(fwrt, + "WRT: Unassigned region id %d, skipping\n", i); + continue; + } + + reg = (void *)reg_data.reg_tlv->data; + reg_type = le32_to_cpu(reg->type); + if (reg_type >= ARRAY_SIZE(iwl_dump_ini_region_ops)) + continue; + + if (reg_type == IWL_FW_INI_REGION_PERIPHERY_PHY && + tp_id != IWL_FW_INI_TIME_POINT_FW_ASSERT) { + IWL_WARN(fwrt, + "WRT: trying to collect phy prph at time point: %d, skipping\n", + tp_id); + continue; + } + + size += iwl_dump_ini_mem(fwrt, list, ®_data, + &iwl_dump_ini_region_ops[reg_type]); + } + + if (size) + size += iwl_dump_ini_info(fwrt, trigger, list); + + return size; +} + +static bool iwl_fw_ini_trigger_on(struct iwl_fw_runtime *fwrt, + struct iwl_fw_ini_trigger_tlv *trig) +{ + enum iwl_fw_ini_time_point tp_id = le32_to_cpu(trig->time_point); + u32 usec = le32_to_cpu(trig->ignore_consec); + + if (!iwl_trans_dbg_ini_valid(fwrt->trans) || + tp_id == IWL_FW_INI_TIME_POINT_INVALID || + tp_id >= IWL_FW_INI_TIME_POINT_NUM || + iwl_fw_dbg_no_trig_window(fwrt, tp_id, usec)) + return false; + + return true; +} + +static u32 iwl_dump_ini_file_gen(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + struct list_head *list) +{ + struct iwl_fw_ini_trigger_tlv *trigger = dump_data->trig; + struct iwl_fw_ini_dump_entry *entry; + struct iwl_fw_ini_dump_file_hdr *hdr; + u32 size; + + if (!trigger || !iwl_fw_ini_trigger_on(fwrt, trigger) || + !le64_to_cpu(trigger->regions_mask)) + return 0; + + entry = vzalloc(sizeof(*entry) + sizeof(*hdr)); + if (!entry) + return 0; + + entry->size = sizeof(*hdr); + + size = iwl_dump_ini_trigger(fwrt, dump_data, list); + if (!size) { + vfree(entry); + return 0; + } + + hdr = (void *)entry->data; + hdr->barker = cpu_to_le32(IWL_FW_INI_ERROR_DUMP_BARKER); + hdr->file_len = cpu_to_le32(size + entry->size); + + list_add(&entry->list, list); + + return le32_to_cpu(hdr->file_len); +} + +static inline void iwl_fw_free_dump_desc(struct iwl_fw_runtime *fwrt, + const struct iwl_fw_dump_desc *desc) +{ + if (desc && desc != &iwl_dump_desc_assert) + kfree(desc); + + fwrt->dump.lmac_err_id[0] = 0; + if (fwrt->smem_cfg.num_lmacs > 1) + fwrt->dump.lmac_err_id[1] = 0; + fwrt->dump.umac_err_id = 0; +} + +static void iwl_fw_error_dump(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data) +{ + struct iwl_fw_dump_ptrs fw_error_dump = {}; + struct iwl_fw_error_dump_file *dump_file; + struct scatterlist *sg_dump_data; + u32 file_len; + u32 dump_mask = fwrt->fw->dbg.dump_mask; + + dump_file = iwl_fw_error_dump_file(fwrt, &fw_error_dump, dump_data); + if (!dump_file) + return; + + if (dump_data->monitor_only) + dump_mask &= BIT(IWL_FW_ERROR_DUMP_FW_MONITOR); + + fw_error_dump.trans_ptr = iwl_trans_dump_data(fwrt->trans, dump_mask, + fwrt->sanitize_ops, + fwrt->sanitize_ctx); + file_len = le32_to_cpu(dump_file->file_len); + fw_error_dump.fwrt_len = file_len; + + if (fw_error_dump.trans_ptr) { + file_len += fw_error_dump.trans_ptr->len; + dump_file->file_len = cpu_to_le32(file_len); + } + + sg_dump_data = alloc_sgtable(file_len); + if (sg_dump_data) { + sg_pcopy_from_buffer(sg_dump_data, + sg_nents(sg_dump_data), + fw_error_dump.fwrt_ptr, + fw_error_dump.fwrt_len, 0); + if (fw_error_dump.trans_ptr) + sg_pcopy_from_buffer(sg_dump_data, + sg_nents(sg_dump_data), + fw_error_dump.trans_ptr->data, + fw_error_dump.trans_ptr->len, + fw_error_dump.fwrt_len); + dev_coredumpsg(fwrt->trans->dev, sg_dump_data, file_len, + GFP_KERNEL); + } + vfree(fw_error_dump.fwrt_ptr); + vfree(fw_error_dump.trans_ptr); +} + +static void iwl_dump_ini_list_free(struct list_head *list) +{ + while (!list_empty(list)) { + struct iwl_fw_ini_dump_entry *entry = + list_entry(list->next, typeof(*entry), list); + + list_del(&entry->list); + vfree(entry); + } +} + +static void iwl_fw_error_dump_data_free(struct iwl_fwrt_dump_data *dump_data) +{ + dump_data->trig = NULL; + kfree(dump_data->fw_pkt); + dump_data->fw_pkt = NULL; +} + +static void iwl_fw_error_ini_dump(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data) +{ +#if defined(__linux__) + struct list_head dump_list = LIST_HEAD_INIT(dump_list); +#elif defined(__FreeBSD__) + struct list_head dump_list = LINUX_LIST_HEAD_INIT(dump_list); +#endif + struct scatterlist *sg_dump_data; + u32 file_len = iwl_dump_ini_file_gen(fwrt, dump_data, &dump_list); + + if (!file_len) + return; + + sg_dump_data = alloc_sgtable(file_len); + if (sg_dump_data) { + struct iwl_fw_ini_dump_entry *entry; + int sg_entries = sg_nents(sg_dump_data); + u32 offs = 0; + + list_for_each_entry(entry, &dump_list, list) { + sg_pcopy_from_buffer(sg_dump_data, sg_entries, + entry->data, entry->size, offs); + offs += entry->size; + } + dev_coredumpsg(fwrt->trans->dev, sg_dump_data, file_len, + GFP_KERNEL); + } + iwl_dump_ini_list_free(&dump_list); +} + +const struct iwl_fw_dump_desc iwl_dump_desc_assert = { + .trig_desc = { + .type = cpu_to_le32(FW_DBG_TRIGGER_FW_ASSERT), + }, +}; +IWL_EXPORT_SYMBOL(iwl_dump_desc_assert); + +int iwl_fw_dbg_collect_desc(struct iwl_fw_runtime *fwrt, + const struct iwl_fw_dump_desc *desc, + bool monitor_only, + unsigned int delay) +{ + struct iwl_fwrt_wk_data *wk_data; + unsigned long idx; + + if (iwl_trans_dbg_ini_valid(fwrt->trans)) { + iwl_fw_free_dump_desc(fwrt, desc); + return 0; + } + + /* + * Check there is an available worker. + * ffz return value is undefined if no zero exists, + * so check against ~0UL first. + */ + if (fwrt->dump.active_wks == ~0UL) + return -EBUSY; + + idx = ffz(fwrt->dump.active_wks); + + if (idx >= IWL_FW_RUNTIME_DUMP_WK_NUM || + test_and_set_bit(fwrt->dump.wks[idx].idx, &fwrt->dump.active_wks)) + return -EBUSY; + + wk_data = &fwrt->dump.wks[idx]; + + if (WARN_ON(wk_data->dump_data.desc)) + iwl_fw_free_dump_desc(fwrt, wk_data->dump_data.desc); + + wk_data->dump_data.desc = desc; + wk_data->dump_data.monitor_only = monitor_only; + + IWL_WARN(fwrt, "Collecting data: trigger %d fired.\n", + le32_to_cpu(desc->trig_desc.type)); + + schedule_delayed_work(&wk_data->wk, usecs_to_jiffies(delay)); + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_collect_desc); + +int iwl_fw_dbg_error_collect(struct iwl_fw_runtime *fwrt, + enum iwl_fw_dbg_trigger trig_type) +{ + if (!test_bit(STATUS_DEVICE_ENABLED, &fwrt->trans->status)) + return -EIO; + + if (iwl_trans_dbg_ini_valid(fwrt->trans)) { + if (trig_type != FW_DBG_TRIGGER_ALIVE_TIMEOUT && + trig_type != FW_DBG_TRIGGER_DRIVER) + return -EIO; + + iwl_dbg_tlv_time_point(fwrt, + IWL_FW_INI_TIME_POINT_HOST_ALIVE_TIMEOUT, + NULL); + } else { + struct iwl_fw_dump_desc *iwl_dump_error_desc; + int ret; + + iwl_dump_error_desc = + kmalloc(sizeof(*iwl_dump_error_desc), GFP_KERNEL); + + if (!iwl_dump_error_desc) + return -ENOMEM; + + iwl_dump_error_desc->trig_desc.type = cpu_to_le32(trig_type); + iwl_dump_error_desc->len = 0; + + ret = iwl_fw_dbg_collect_desc(fwrt, iwl_dump_error_desc, + false, 0); + if (ret) { + kfree(iwl_dump_error_desc); + return ret; + } + } + + iwl_trans_sync_nmi(fwrt->trans); + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_error_collect); + +int iwl_fw_dbg_collect(struct iwl_fw_runtime *fwrt, + enum iwl_fw_dbg_trigger trig, + const char *str, size_t len, + struct iwl_fw_dbg_trigger_tlv *trigger) +{ + struct iwl_fw_dump_desc *desc; + unsigned int delay = 0; + bool monitor_only = false; + + if (trigger) { + u16 occurrences = le16_to_cpu(trigger->occurrences) - 1; + + if (!le16_to_cpu(trigger->occurrences)) + return 0; + + if (trigger->flags & IWL_FW_DBG_FORCE_RESTART) { + IWL_WARN(fwrt, "Force restart: trigger %d fired.\n", + trig); + iwl_force_nmi(fwrt->trans); + return 0; + } + + trigger->occurrences = cpu_to_le16(occurrences); + monitor_only = trigger->mode & IWL_FW_DBG_TRIGGER_MONITOR_ONLY; + + /* convert msec to usec */ + delay = le32_to_cpu(trigger->stop_delay) * USEC_PER_MSEC; + } + + desc = kzalloc(sizeof(*desc) + len, GFP_ATOMIC); + if (!desc) + return -ENOMEM; + + + desc->len = len; + desc->trig_desc.type = cpu_to_le32(trig); + memcpy(desc->trig_desc.data, str, len); + + return iwl_fw_dbg_collect_desc(fwrt, desc, monitor_only, delay); +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_collect); + +int iwl_fw_dbg_collect_trig(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_trigger_tlv *trigger, + const char *fmt, ...) +{ + int ret, len = 0; + char buf[64]; + + if (iwl_trans_dbg_ini_valid(fwrt->trans)) + return 0; + + if (fmt) { + va_list ap; + + buf[sizeof(buf) - 1] = '\0'; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + /* check for truncation */ + if (WARN_ON_ONCE(buf[sizeof(buf) - 1])) + buf[sizeof(buf) - 1] = '\0'; + + len = strlen(buf) + 1; + } + + ret = iwl_fw_dbg_collect(fwrt, le32_to_cpu(trigger->id), buf, len, + trigger); + + if (ret) + return ret; + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_collect_trig); + +int iwl_fw_start_dbg_conf(struct iwl_fw_runtime *fwrt, u8 conf_id) +{ + u8 *ptr; + int ret; + int i; + + if (WARN_ONCE(conf_id >= ARRAY_SIZE(fwrt->fw->dbg.conf_tlv), + "Invalid configuration %d\n", conf_id)) + return -EINVAL; + + /* EARLY START - firmware's configuration is hard coded */ + if ((!fwrt->fw->dbg.conf_tlv[conf_id] || + !fwrt->fw->dbg.conf_tlv[conf_id]->num_of_hcmds) && + conf_id == FW_DBG_START_FROM_ALIVE) + return 0; + + if (!fwrt->fw->dbg.conf_tlv[conf_id]) + return -EINVAL; + + if (fwrt->dump.conf != FW_DBG_INVALID) + IWL_INFO(fwrt, "FW already configured (%d) - re-configuring\n", + fwrt->dump.conf); + + /* Send all HCMDs for configuring the FW debug */ + ptr = (void *)&fwrt->fw->dbg.conf_tlv[conf_id]->hcmd; + for (i = 0; i < fwrt->fw->dbg.conf_tlv[conf_id]->num_of_hcmds; i++) { + struct iwl_fw_dbg_conf_hcmd *cmd = (void *)ptr; + struct iwl_host_cmd hcmd = { + .id = cmd->id, + .len = { le16_to_cpu(cmd->len), }, + .data = { cmd->data, }, + }; + + ret = iwl_trans_send_cmd(fwrt->trans, &hcmd); + if (ret) + return ret; + + ptr += sizeof(*cmd); + ptr += le16_to_cpu(cmd->len); + } + + fwrt->dump.conf = conf_id; + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_fw_start_dbg_conf); + +/* this function assumes dump_start was called beforehand and dump_end will be + * called afterwards + */ +static void iwl_fw_dbg_collect_sync(struct iwl_fw_runtime *fwrt, u8 wk_idx) +{ + struct iwl_fw_dbg_params params = {0}; + struct iwl_fwrt_dump_data *dump_data = + &fwrt->dump.wks[wk_idx].dump_data; + + if (!test_bit(wk_idx, &fwrt->dump.active_wks)) + return; + + if (!test_bit(STATUS_DEVICE_ENABLED, &fwrt->trans->status)) { + IWL_ERR(fwrt, "Device is not enabled - cannot dump error\n"); + goto out; + } + + /* there's no point in fw dump if the bus is dead */ + if (test_bit(STATUS_TRANS_DEAD, &fwrt->trans->status)) { + IWL_ERR(fwrt, "Skip fw error dump since bus is dead\n"); + goto out; + } + + iwl_fw_dbg_stop_restart_recording(fwrt, ¶ms, true); + + IWL_DEBUG_FW_INFO(fwrt, "WRT: Data collection start\n"); + if (iwl_trans_dbg_ini_valid(fwrt->trans)) + iwl_fw_error_ini_dump(fwrt, &fwrt->dump.wks[wk_idx].dump_data); + else + iwl_fw_error_dump(fwrt, &fwrt->dump.wks[wk_idx].dump_data); + IWL_DEBUG_FW_INFO(fwrt, "WRT: Data collection done\n"); + + iwl_fw_dbg_stop_restart_recording(fwrt, ¶ms, false); + +out: + if (iwl_trans_dbg_ini_valid(fwrt->trans)) { + iwl_fw_error_dump_data_free(dump_data); + } else { + iwl_fw_free_dump_desc(fwrt, dump_data->desc); + dump_data->desc = NULL; + } + + clear_bit(wk_idx, &fwrt->dump.active_wks); +} + +int iwl_fw_dbg_ini_collect(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + bool sync) +{ + struct iwl_fw_ini_trigger_tlv *trig = dump_data->trig; + enum iwl_fw_ini_time_point tp_id = le32_to_cpu(trig->time_point); + u32 occur, delay; + unsigned long idx; + + if (!iwl_fw_ini_trigger_on(fwrt, trig)) { + IWL_WARN(fwrt, "WRT: Trigger %d is not active, aborting dump\n", + tp_id); + return -EINVAL; + } + + delay = le32_to_cpu(trig->dump_delay); + occur = le32_to_cpu(trig->occurrences); + if (!occur) + return 0; + + trig->occurrences = cpu_to_le32(--occur); + + /* Check there is an available worker. + * ffz return value is undefined if no zero exists, + * so check against ~0UL first. + */ + if (fwrt->dump.active_wks == ~0UL) + return -EBUSY; + + idx = ffz(fwrt->dump.active_wks); + + if (idx >= IWL_FW_RUNTIME_DUMP_WK_NUM || + test_and_set_bit(fwrt->dump.wks[idx].idx, &fwrt->dump.active_wks)) + return -EBUSY; + + fwrt->dump.wks[idx].dump_data = *dump_data; + + if (sync) + delay = 0; + + IWL_WARN(fwrt, + "WRT: Collecting data: ini trigger %d fired (delay=%dms).\n", + tp_id, (u32)(delay / USEC_PER_MSEC)); + + schedule_delayed_work(&fwrt->dump.wks[idx].wk, usecs_to_jiffies(delay)); + + if (sync) + iwl_fw_dbg_collect_sync(fwrt, idx); + + return 0; +} + +void iwl_fw_error_dump_wk(struct work_struct *work) +{ + struct iwl_fwrt_wk_data *wks = + container_of(work, typeof(*wks), wk.work); + struct iwl_fw_runtime *fwrt = + container_of(wks, typeof(*fwrt), dump.wks[wks->idx]); + + /* assumes the op mode mutex is locked in dump_start since + * iwl_fw_dbg_collect_sync can't run in parallel + */ + if (fwrt->ops && fwrt->ops->dump_start && + fwrt->ops->dump_start(fwrt->ops_ctx)) + return; + + iwl_fw_dbg_collect_sync(fwrt, wks->idx); + + if (fwrt->ops && fwrt->ops->dump_end) + fwrt->ops->dump_end(fwrt->ops_ctx); +} + +void iwl_fw_dbg_read_d3_debug_data(struct iwl_fw_runtime *fwrt) +{ + const struct iwl_cfg *cfg = fwrt->trans->cfg; + + if (!iwl_fw_dbg_is_d3_debug_enabled(fwrt)) + return; + + if (!fwrt->dump.d3_debug_data) { + fwrt->dump.d3_debug_data = kmalloc(cfg->d3_debug_data_length, + GFP_KERNEL); + if (!fwrt->dump.d3_debug_data) { + IWL_ERR(fwrt, + "failed to allocate memory for D3 debug data\n"); + return; + } + } + + /* if the buffer holds previous debug data it is overwritten */ + iwl_trans_read_mem_bytes(fwrt->trans, cfg->d3_debug_data_base_addr, + fwrt->dump.d3_debug_data, + cfg->d3_debug_data_length); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, + cfg->d3_debug_data_base_addr, + fwrt->dump.d3_debug_data, + cfg->d3_debug_data_length); +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_read_d3_debug_data); + +void iwl_fw_dbg_stop_sync(struct iwl_fw_runtime *fwrt) +{ + int i; + + iwl_dbg_tlv_del_timers(fwrt->trans); + for (i = 0; i < IWL_FW_RUNTIME_DUMP_WK_NUM; i++) + iwl_fw_dbg_collect_sync(fwrt, i); + + iwl_fw_dbg_stop_restart_recording(fwrt, NULL, true); +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_stop_sync); + +static int iwl_fw_dbg_suspend_resume_hcmd(struct iwl_trans *trans, bool suspend) +{ + struct iwl_dbg_suspend_resume_cmd cmd = { + .operation = suspend ? + cpu_to_le32(DBGC_SUSPEND_CMD) : + cpu_to_le32(DBGC_RESUME_CMD), + }; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DEBUG_GROUP, DBGC_SUSPEND_RESUME), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + + return iwl_trans_send_cmd(trans, &hcmd); +} + +static void iwl_fw_dbg_stop_recording(struct iwl_trans *trans, + struct iwl_fw_dbg_params *params) +{ + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000) { + iwl_set_bits_prph(trans, MON_BUFF_SAMPLE_CTL, 0x100); + return; + } + + if (params) { + params->in_sample = iwl_read_umac_prph(trans, DBGC_IN_SAMPLE); + params->out_ctrl = iwl_read_umac_prph(trans, DBGC_OUT_CTRL); + } + + iwl_write_umac_prph(trans, DBGC_IN_SAMPLE, 0); + /* wait for the DBGC to finish writing the internal buffer to DRAM to + * avoid halting the HW while writing + */ + usleep_range(700, 1000); + iwl_write_umac_prph(trans, DBGC_OUT_CTRL, 0); +} + +static int iwl_fw_dbg_restart_recording(struct iwl_trans *trans, + struct iwl_fw_dbg_params *params) +{ + if (!params) + return -EIO; + + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000) { + iwl_clear_bits_prph(trans, MON_BUFF_SAMPLE_CTL, 0x100); + iwl_clear_bits_prph(trans, MON_BUFF_SAMPLE_CTL, 0x1); + iwl_set_bits_prph(trans, MON_BUFF_SAMPLE_CTL, 0x1); + } else { + iwl_write_umac_prph(trans, DBGC_IN_SAMPLE, params->in_sample); + iwl_write_umac_prph(trans, DBGC_OUT_CTRL, params->out_ctrl); + } + + return 0; +} + +void iwl_fw_dbg_stop_restart_recording(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_params *params, + bool stop) +{ + int ret __maybe_unused = 0; + + if (test_bit(STATUS_FW_ERROR, &fwrt->trans->status)) + return; + + if (fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DBG_SUSPEND_RESUME_CMD_SUPP)) + ret = iwl_fw_dbg_suspend_resume_hcmd(fwrt->trans, stop); + else if (stop) + iwl_fw_dbg_stop_recording(fwrt->trans, params); + else + ret = iwl_fw_dbg_restart_recording(fwrt->trans, params); +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (!ret) { + if (stop) + fwrt->trans->dbg.rec_on = false; + else + iwl_fw_set_dbg_rec_on(fwrt); + } +#endif +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_stop_restart_recording); diff --git a/sys/contrib/dev/iwlwifi/fw/dbg.h b/sys/contrib/dev/iwlwifi/fw/dbg.h new file mode 100644 index 00000000000..8c3c890066b --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/dbg.h @@ -0,0 +1,327 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2019, 2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_dbg_h__ +#define __iwl_fw_dbg_h__ +#include +#include +#include "runtime.h" +#include "iwl-prph.h" +#include "iwl-io.h" +#include "file.h" +#include "error-dump.h" +#include "api/commands.h" +#include "api/dbg-tlv.h" +#include "api/alive.h" + +/** + * struct iwl_fw_dump_desc - describes the dump + * @len: length of trig_desc->data + * @trig_desc: the description of the dump + */ +struct iwl_fw_dump_desc { + size_t len; + /* must be last */ + struct iwl_fw_error_dump_trigger_desc trig_desc; +}; + +/** + * struct iwl_fw_dbg_params - register values to restore + * @in_sample: DBGC_IN_SAMPLE value + * @out_ctrl: DBGC_OUT_CTRL value + */ +struct iwl_fw_dbg_params { + u32 in_sample; + u32 out_ctrl; +}; + +extern const struct iwl_fw_dump_desc iwl_dump_desc_assert; + +int iwl_fw_dbg_collect_desc(struct iwl_fw_runtime *fwrt, + const struct iwl_fw_dump_desc *desc, + bool monitor_only, unsigned int delay); +int iwl_fw_dbg_error_collect(struct iwl_fw_runtime *fwrt, + enum iwl_fw_dbg_trigger trig_type); +int iwl_fw_dbg_ini_collect(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + bool sync); +int iwl_fw_dbg_collect(struct iwl_fw_runtime *fwrt, + enum iwl_fw_dbg_trigger trig, const char *str, + size_t len, struct iwl_fw_dbg_trigger_tlv *trigger); +int iwl_fw_dbg_collect_trig(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_trigger_tlv *trigger, + const char *fmt, ...) __printf(3, 4); +int iwl_fw_start_dbg_conf(struct iwl_fw_runtime *fwrt, u8 id); + +#define iwl_fw_dbg_trigger_enabled(fw, id) ({ \ + void *__dbg_trigger = (fw)->dbg.trigger_tlv[(id)]; \ + unlikely(__dbg_trigger); \ +}) + +static inline struct iwl_fw_dbg_trigger_tlv* +_iwl_fw_dbg_get_trigger(const struct iwl_fw *fw, enum iwl_fw_dbg_trigger id) +{ + return fw->dbg.trigger_tlv[id]; +} + +#define iwl_fw_dbg_get_trigger(fw, id) ({ \ + BUILD_BUG_ON(!__builtin_constant_p(id)); \ + BUILD_BUG_ON((id) >= FW_DBG_TRIGGER_MAX); \ + _iwl_fw_dbg_get_trigger((fw), (id)); \ +}) + +static inline bool +iwl_fw_dbg_trigger_vif_match(struct iwl_fw_dbg_trigger_tlv *trig, + struct wireless_dev *wdev) +{ + u32 trig_vif = le32_to_cpu(trig->vif_type); + + return trig_vif == IWL_FW_DBG_CONF_VIF_ANY || + wdev->iftype == trig_vif; +} + +static inline bool +iwl_fw_dbg_trigger_stop_conf_match(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_trigger_tlv *trig) +{ + return ((trig->mode & IWL_FW_DBG_TRIGGER_STOP) && + (fwrt->dump.conf == FW_DBG_INVALID || + (BIT(fwrt->dump.conf) & le32_to_cpu(trig->stop_conf_ids)))); +} + +static inline bool +iwl_fw_dbg_no_trig_window(struct iwl_fw_runtime *fwrt, u32 id, u32 dis_usec) +{ + unsigned long wind_jiff = usecs_to_jiffies(dis_usec); + + /* If this is the first event checked, jump to update start ts */ + if (fwrt->dump.non_collect_ts_start[id] && + (time_after(fwrt->dump.non_collect_ts_start[id] + wind_jiff, + jiffies))) + return true; + + fwrt->dump.non_collect_ts_start[id] = jiffies; + return false; +} + +static inline bool +iwl_fw_dbg_trigger_check_stop(struct iwl_fw_runtime *fwrt, + struct wireless_dev *wdev, + struct iwl_fw_dbg_trigger_tlv *trig) +{ + u32 usec = le16_to_cpu(trig->trig_dis_ms) * USEC_PER_MSEC; + + if (wdev && !iwl_fw_dbg_trigger_vif_match(trig, wdev)) + return false; + + if (iwl_fw_dbg_no_trig_window(fwrt, le32_to_cpu(trig->id), usec)) { + IWL_WARN(fwrt, "Trigger %d occurred while no-collect window.\n", + trig->id); + return false; + } + + return iwl_fw_dbg_trigger_stop_conf_match(fwrt, trig); +} + +static inline struct iwl_fw_dbg_trigger_tlv* +_iwl_fw_dbg_trigger_on(struct iwl_fw_runtime *fwrt, + struct wireless_dev *wdev, + const enum iwl_fw_dbg_trigger id) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + + if (iwl_trans_dbg_ini_valid(fwrt->trans)) + return NULL; + + if (!iwl_fw_dbg_trigger_enabled(fwrt->fw, id)) + return NULL; + + trig = _iwl_fw_dbg_get_trigger(fwrt->fw, id); + + if (!iwl_fw_dbg_trigger_check_stop(fwrt, wdev, trig)) + return NULL; + + return trig; +} + +#define iwl_fw_dbg_trigger_on(fwrt, wdev, id) ({ \ + BUILD_BUG_ON(!__builtin_constant_p(id)); \ + BUILD_BUG_ON((id) >= FW_DBG_TRIGGER_MAX); \ + _iwl_fw_dbg_trigger_on((fwrt), (wdev), (id)); \ +}) + +static inline void +_iwl_fw_dbg_trigger_simple_stop(struct iwl_fw_runtime *fwrt, + struct wireless_dev *wdev, + struct iwl_fw_dbg_trigger_tlv *trigger) +{ + if (!trigger) + return; + + if (!iwl_fw_dbg_trigger_check_stop(fwrt, wdev, trigger)) + return; + + iwl_fw_dbg_collect_trig(fwrt, trigger, NULL); +} + +#define iwl_fw_dbg_trigger_simple_stop(fwrt, wdev, trig) \ + _iwl_fw_dbg_trigger_simple_stop((fwrt), (wdev), \ + iwl_fw_dbg_get_trigger((fwrt)->fw,\ + (trig))) +void iwl_fw_dbg_stop_restart_recording(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_params *params, + bool stop); + +#ifdef CONFIG_IWLWIFI_DEBUGFS +static inline void iwl_fw_set_dbg_rec_on(struct iwl_fw_runtime *fwrt) +{ + if (fwrt->cur_fw_img == IWL_UCODE_REGULAR && + (fwrt->fw->dbg.dest_tlv || + fwrt->trans->dbg.ini_dest != IWL_FW_INI_LOCATION_INVALID)) + fwrt->trans->dbg.rec_on = true; +} +#endif + +static inline void iwl_fw_dump_conf_clear(struct iwl_fw_runtime *fwrt) +{ + fwrt->dump.conf = FW_DBG_INVALID; +} + +void iwl_fw_error_dump_wk(struct work_struct *work); + +static inline bool iwl_fw_dbg_type_on(struct iwl_fw_runtime *fwrt, u32 type) +{ + return (fwrt->fw->dbg.dump_mask & BIT(type)); +} + +static inline bool iwl_fw_dbg_is_d3_debug_enabled(struct iwl_fw_runtime *fwrt) +{ + return fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_D3_DEBUG) && + fwrt->trans->cfg->d3_debug_data_length && fwrt->ops && + fwrt->ops->d3_debug_enable && + fwrt->ops->d3_debug_enable(fwrt->ops_ctx) && + iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_D3_DEBUG_DATA); +} + +static inline bool iwl_fw_dbg_is_paging_enabled(struct iwl_fw_runtime *fwrt) +{ + return iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_PAGING) && + !fwrt->trans->trans_cfg->gen2 && + fwrt->cur_fw_img < IWL_UCODE_TYPE_MAX && + fwrt->fw->img[fwrt->cur_fw_img].paging_mem_size && + fwrt->fw_paging_db[0].fw_paging_block; +} + +void iwl_fw_dbg_read_d3_debug_data(struct iwl_fw_runtime *fwrt); + +static inline void iwl_fw_flush_dumps(struct iwl_fw_runtime *fwrt) +{ + int i; + + iwl_dbg_tlv_del_timers(fwrt->trans); + for (i = 0; i < IWL_FW_RUNTIME_DUMP_WK_NUM; i++) + flush_delayed_work(&fwrt->dump.wks[i].wk); +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +static inline void iwl_fw_cancel_timestamp(struct iwl_fw_runtime *fwrt) +{ + fwrt->timestamp.delay = 0; + cancel_delayed_work_sync(&fwrt->timestamp.wk); +} + +void iwl_fw_trigger_timestamp(struct iwl_fw_runtime *fwrt, u32 delay); + +static inline void iwl_fw_suspend_timestamp(struct iwl_fw_runtime *fwrt) +{ + cancel_delayed_work_sync(&fwrt->timestamp.wk); +} + +static inline void iwl_fw_resume_timestamp(struct iwl_fw_runtime *fwrt) +{ + if (!fwrt->timestamp.delay) + return; + + schedule_delayed_work(&fwrt->timestamp.wk, + round_jiffies_relative(fwrt->timestamp.delay)); +} + +#else + +static inline void iwl_fw_cancel_timestamp(struct iwl_fw_runtime *fwrt) {} + +static inline void iwl_fw_trigger_timestamp(struct iwl_fw_runtime *fwrt, + u32 delay) {} + +static inline void iwl_fw_suspend_timestamp(struct iwl_fw_runtime *fwrt) {} + +static inline void iwl_fw_resume_timestamp(struct iwl_fw_runtime *fwrt) {} + +#endif /* CONFIG_IWLWIFI_DEBUGFS */ + +void iwl_fw_dbg_stop_sync(struct iwl_fw_runtime *fwrt); + +static inline void iwl_fw_lmac1_set_alive_err_table(struct iwl_trans *trans, + u32 lmac_error_event_table) +{ + if (!(trans->dbg.error_event_table_tlv_status & + IWL_ERROR_EVENT_TABLE_LMAC1) || + WARN_ON(trans->dbg.lmac_error_event_table[0] != + lmac_error_event_table)) + trans->dbg.lmac_error_event_table[0] = lmac_error_event_table; +} + +static inline void iwl_fw_umac_set_alive_err_table(struct iwl_trans *trans, + u32 umac_error_event_table) +{ + if (!(trans->dbg.error_event_table_tlv_status & + IWL_ERROR_EVENT_TABLE_UMAC) || + WARN_ON(trans->dbg.umac_error_event_table != + umac_error_event_table)) + trans->dbg.umac_error_event_table = umac_error_event_table; +} + +static inline void iwl_fw_error_collect(struct iwl_fw_runtime *fwrt, bool sync) +{ + enum iwl_fw_ini_time_point tp_id; + + if (!iwl_trans_dbg_ini_valid(fwrt->trans)) { + iwl_fw_dbg_collect_desc(fwrt, &iwl_dump_desc_assert, false, 0); + return; + } + + if (fwrt->trans->dbg.hw_error) { + tp_id = IWL_FW_INI_TIME_POINT_FW_HW_ERROR; + fwrt->trans->dbg.hw_error = false; + } else { + tp_id = IWL_FW_INI_TIME_POINT_FW_ASSERT; + } + + _iwl_dbg_tlv_time_point(fwrt, tp_id, NULL, sync); +} + +void iwl_fw_error_print_fseq_regs(struct iwl_fw_runtime *fwrt); + +static inline void iwl_fwrt_update_fw_versions(struct iwl_fw_runtime *fwrt, + struct iwl_lmac_alive *lmac, + struct iwl_umac_alive *umac) +{ + if (lmac) { + fwrt->dump.fw_ver.type = lmac->ver_type; + fwrt->dump.fw_ver.subtype = lmac->ver_subtype; + fwrt->dump.fw_ver.lmac_major = le32_to_cpu(lmac->ucode_major); + fwrt->dump.fw_ver.lmac_minor = le32_to_cpu(lmac->ucode_minor); + } + + if (umac) { + fwrt->dump.fw_ver.umac_major = le32_to_cpu(umac->umac_major); + fwrt->dump.fw_ver.umac_minor = le32_to_cpu(umac->umac_minor); + } +} + +void iwl_fwrt_dump_error_logs(struct iwl_fw_runtime *fwrt); +#endif /* __iwl_fw_dbg_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/debugfs.h b/sys/contrib/dev/iwlwifi/fw/debugfs.h new file mode 100644 index 00000000000..0248d40bc23 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/debugfs.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include "runtime.h" + +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_fwrt_dbgfs_register(struct iwl_fw_runtime *fwrt, + struct dentry *dbgfs_dir); + +#else +static inline void iwl_fwrt_dbgfs_register(struct iwl_fw_runtime *fwrt, + struct dentry *dbgfs_dir) { } + +#endif /* CONFIG_IWLWIFI_DEBUGFS */ diff --git a/sys/contrib/dev/iwlwifi/fw/dump.c b/sys/contrib/dev/iwlwifi/fw/dump.c new file mode 100644 index 00000000000..016b3a4c5f5 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/dump.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include +#include "iwl-drv.h" +#include "runtime.h" +#include "dbg.h" +#include "debugfs.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" + +/* + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_error_event_table_v1 { + u32 valid; /* (nonzero) valid, (0) log is empty */ + u32 error_id; /* type of error */ + u32 pc; /* program counter */ + u32 blink1; /* branch link */ + u32 blink2; /* branch link */ + u32 ilink1; /* interrupt link */ + u32 ilink2; /* interrupt link */ + u32 data1; /* error-specific data */ + u32 data2; /* error-specific data */ + u32 data3; /* error-specific data */ + u32 bcon_time; /* beacon timer */ + u32 tsf_low; /* network timestamp function timer */ + u32 tsf_hi; /* network timestamp function timer */ + u32 gp1; /* GP1 timer register */ + u32 gp2; /* GP2 timer register */ + u32 gp3; /* GP3 timer register */ + u32 ucode_ver; /* uCode version */ + u32 hw_ver; /* HW Silicon version */ + u32 brd_ver; /* HW board version */ + u32 log_pc; /* log program counter */ + u32 frame_ptr; /* frame pointer */ + u32 stack_ptr; /* stack pointer */ + u32 hcmd; /* last host command header */ + u32 isr0; /* isr status register LMPM_NIC_ISR0: + * rxtx_flag */ + u32 isr1; /* isr status register LMPM_NIC_ISR1: + * host_flag */ + u32 isr2; /* isr status register LMPM_NIC_ISR2: + * enc_flag */ + u32 isr3; /* isr status register LMPM_NIC_ISR3: + * time_flag */ + u32 isr4; /* isr status register LMPM_NIC_ISR4: + * wico interrupt */ + u32 isr_pref; /* isr status register LMPM_NIC_PREF_STAT */ + u32 wait_event; /* wait event() caller address */ + u32 l2p_control; /* L2pControlField */ + u32 l2p_duration; /* L2pDurationField */ + u32 l2p_mhvalid; /* L2pMhValidBits */ + u32 l2p_addr_match; /* L2pAddrMatchStat */ + u32 lmpm_pmg_sel; /* indicate which clocks are turned on + * (LMPM_PMG_SEL) */ + u32 u_timestamp; /* indicate when the date and time of the + * compilation */ + u32 flow_handler; /* FH read/write pointers, RX credit */ +} __packed /* LOG_ERROR_TABLE_API_S_VER_1 */; + +struct iwl_error_event_table { + u32 valid; /* (nonzero) valid, (0) log is empty */ + u32 error_id; /* type of error */ + u32 trm_hw_status0; /* TRM HW status */ + u32 trm_hw_status1; /* TRM HW status */ + u32 blink2; /* branch link */ + u32 ilink1; /* interrupt link */ + u32 ilink2; /* interrupt link */ + u32 data1; /* error-specific data */ + u32 data2; /* error-specific data */ + u32 data3; /* error-specific data */ + u32 bcon_time; /* beacon timer */ + u32 tsf_low; /* network timestamp function timer */ + u32 tsf_hi; /* network timestamp function timer */ + u32 gp1; /* GP1 timer register */ + u32 gp2; /* GP2 timer register */ + u32 fw_rev_type; /* firmware revision type */ + u32 major; /* uCode version major */ + u32 minor; /* uCode version minor */ + u32 hw_ver; /* HW Silicon version */ + u32 brd_ver; /* HW board version */ + u32 log_pc; /* log program counter */ + u32 frame_ptr; /* frame pointer */ + u32 stack_ptr; /* stack pointer */ + u32 hcmd; /* last host command header */ + u32 isr0; /* isr status register LMPM_NIC_ISR0: + * rxtx_flag */ + u32 isr1; /* isr status register LMPM_NIC_ISR1: + * host_flag */ + u32 isr2; /* isr status register LMPM_NIC_ISR2: + * enc_flag */ + u32 isr3; /* isr status register LMPM_NIC_ISR3: + * time_flag */ + u32 isr4; /* isr status register LMPM_NIC_ISR4: + * wico interrupt */ + u32 last_cmd_id; /* last HCMD id handled by the firmware */ + u32 wait_event; /* wait event() caller address */ + u32 l2p_control; /* L2pControlField */ + u32 l2p_duration; /* L2pDurationField */ + u32 l2p_mhvalid; /* L2pMhValidBits */ + u32 l2p_addr_match; /* L2pAddrMatchStat */ + u32 lmpm_pmg_sel; /* indicate which clocks are turned on + * (LMPM_PMG_SEL) */ + u32 u_timestamp; /* indicate when the date and time of the + * compilation */ + u32 flow_handler; /* FH read/write pointers, RX credit */ +} __packed /* LOG_ERROR_TABLE_API_S_VER_3 */; + +/* + * UMAC error struct - relevant starting from family 8000 chip. + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_umac_error_event_table { + u32 valid; /* (nonzero) valid, (0) log is empty */ + u32 error_id; /* type of error */ + u32 blink1; /* branch link */ + u32 blink2; /* branch link */ + u32 ilink1; /* interrupt link */ + u32 ilink2; /* interrupt link */ + u32 data1; /* error-specific data */ + u32 data2; /* error-specific data */ + u32 data3; /* error-specific data */ + u32 umac_major; + u32 umac_minor; + u32 frame_pointer; /* core register 27*/ + u32 stack_pointer; /* core register 28 */ + u32 cmd_header; /* latest host cmd sent to UMAC */ + u32 nic_isr_pref; /* ISR status register */ +} __packed; + +#define ERROR_START_OFFSET (1 * sizeof(u32)) +#define ERROR_ELEM_SIZE (7 * sizeof(u32)) + +static void iwl_fwrt_dump_umac_error_log(struct iwl_fw_runtime *fwrt) +{ + struct iwl_trans *trans = fwrt->trans; + struct iwl_umac_error_event_table table = {}; + u32 base = fwrt->trans->dbg.umac_error_event_table; + + if (!base && + !(fwrt->trans->dbg.error_event_table_tlv_status & + IWL_ERROR_EVENT_TABLE_UMAC)) + return; + + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + if (table.valid) + fwrt->dump.umac_err_id = table.error_id; + + if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { + IWL_ERR(trans, "Start IWL Error Log Dump:\n"); + IWL_ERR(trans, "Transport status: 0x%08lX, valid: %d\n", + fwrt->trans->status, table.valid); + } + + IWL_ERR(fwrt, "0x%08X | %s\n", table.error_id, + iwl_fw_lookup_assert_desc(table.error_id)); + IWL_ERR(fwrt, "0x%08X | umac branchlink1\n", table.blink1); + IWL_ERR(fwrt, "0x%08X | umac branchlink2\n", table.blink2); + IWL_ERR(fwrt, "0x%08X | umac interruptlink1\n", table.ilink1); + IWL_ERR(fwrt, "0x%08X | umac interruptlink2\n", table.ilink2); + IWL_ERR(fwrt, "0x%08X | umac data1\n", table.data1); + IWL_ERR(fwrt, "0x%08X | umac data2\n", table.data2); + IWL_ERR(fwrt, "0x%08X | umac data3\n", table.data3); + IWL_ERR(fwrt, "0x%08X | umac major\n", table.umac_major); + IWL_ERR(fwrt, "0x%08X | umac minor\n", table.umac_minor); + IWL_ERR(fwrt, "0x%08X | frame pointer\n", table.frame_pointer); + IWL_ERR(fwrt, "0x%08X | stack pointer\n", table.stack_pointer); + IWL_ERR(fwrt, "0x%08X | last host cmd\n", table.cmd_header); + IWL_ERR(fwrt, "0x%08X | isr status reg\n", table.nic_isr_pref); +} + +static void iwl_fwrt_dump_lmac_error_log(struct iwl_fw_runtime *fwrt, u8 lmac_num) +{ + struct iwl_trans *trans = fwrt->trans; + struct iwl_error_event_table table = {}; + u32 val, base = fwrt->trans->dbg.lmac_error_event_table[lmac_num]; + + if (fwrt->cur_fw_img == IWL_UCODE_INIT) { + if (!base) + base = fwrt->fw->init_errlog_ptr; + } else { + if (!base) + base = fwrt->fw->inst_errlog_ptr; + } + + if (base < 0x400000) { + IWL_ERR(fwrt, + "Not valid error log pointer 0x%08X for %s uCode\n", + base, + (fwrt->cur_fw_img == IWL_UCODE_INIT) + ? "Init" : "RT"); + return; + } + + /* check if there is a HW error */ + val = iwl_trans_read_mem32(trans, base); + if (((val & ~0xf) == 0xa5a5a5a0) || ((val & ~0xf) == 0x5a5a5a50)) { + int err; + + IWL_ERR(trans, "HW error, resetting before reading\n"); + + /* reset the device */ + iwl_trans_sw_reset(trans); + + err = iwl_finish_nic_init(trans); + if (err) + return; + } + + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + if (table.valid) + fwrt->dump.lmac_err_id[lmac_num] = table.error_id; + + if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { + IWL_ERR(trans, "Start IWL Error Log Dump:\n"); + IWL_ERR(trans, "Transport status: 0x%08lX, valid: %d\n", + fwrt->trans->status, table.valid); + } + + /* Do not change this output - scripts rely on it */ + + IWL_ERR(fwrt, "Loaded firmware version: %s\n", fwrt->fw->fw_version); + + IWL_ERR(fwrt, "0x%08X | %-28s\n", table.error_id, + iwl_fw_lookup_assert_desc(table.error_id)); + IWL_ERR(fwrt, "0x%08X | trm_hw_status0\n", table.trm_hw_status0); + IWL_ERR(fwrt, "0x%08X | trm_hw_status1\n", table.trm_hw_status1); + IWL_ERR(fwrt, "0x%08X | branchlink2\n", table.blink2); + IWL_ERR(fwrt, "0x%08X | interruptlink1\n", table.ilink1); + IWL_ERR(fwrt, "0x%08X | interruptlink2\n", table.ilink2); + IWL_ERR(fwrt, "0x%08X | data1\n", table.data1); + IWL_ERR(fwrt, "0x%08X | data2\n", table.data2); + IWL_ERR(fwrt, "0x%08X | data3\n", table.data3); + IWL_ERR(fwrt, "0x%08X | beacon time\n", table.bcon_time); + IWL_ERR(fwrt, "0x%08X | tsf low\n", table.tsf_low); + IWL_ERR(fwrt, "0x%08X | tsf hi\n", table.tsf_hi); + IWL_ERR(fwrt, "0x%08X | time gp1\n", table.gp1); + IWL_ERR(fwrt, "0x%08X | time gp2\n", table.gp2); + IWL_ERR(fwrt, "0x%08X | uCode revision type\n", table.fw_rev_type); + IWL_ERR(fwrt, "0x%08X | uCode version major\n", table.major); + IWL_ERR(fwrt, "0x%08X | uCode version minor\n", table.minor); + IWL_ERR(fwrt, "0x%08X | hw version\n", table.hw_ver); + IWL_ERR(fwrt, "0x%08X | board version\n", table.brd_ver); + IWL_ERR(fwrt, "0x%08X | hcmd\n", table.hcmd); + IWL_ERR(fwrt, "0x%08X | isr0\n", table.isr0); + IWL_ERR(fwrt, "0x%08X | isr1\n", table.isr1); + IWL_ERR(fwrt, "0x%08X | isr2\n", table.isr2); + IWL_ERR(fwrt, "0x%08X | isr3\n", table.isr3); + IWL_ERR(fwrt, "0x%08X | isr4\n", table.isr4); + IWL_ERR(fwrt, "0x%08X | last cmd Id\n", table.last_cmd_id); + IWL_ERR(fwrt, "0x%08X | wait_event\n", table.wait_event); + IWL_ERR(fwrt, "0x%08X | l2p_control\n", table.l2p_control); + IWL_ERR(fwrt, "0x%08X | l2p_duration\n", table.l2p_duration); + IWL_ERR(fwrt, "0x%08X | l2p_mhvalid\n", table.l2p_mhvalid); + IWL_ERR(fwrt, "0x%08X | l2p_addr_match\n", table.l2p_addr_match); + IWL_ERR(fwrt, "0x%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel); + IWL_ERR(fwrt, "0x%08X | timestamp\n", table.u_timestamp); + IWL_ERR(fwrt, "0x%08X | flow_handler\n", table.flow_handler); +} + +/* + * TCM error struct. + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_tcm_error_event_table { + u32 valid; + u32 error_id; + u32 blink2; + u32 ilink1; + u32 ilink2; + u32 data1, data2, data3; + u32 logpc; + u32 frame_pointer; + u32 stack_pointer; + u32 msgid; + u32 isr; + u32 hw_status[5]; + u32 sw_status[1]; + u32 reserved[4]; +} __packed; /* TCM_LOG_ERROR_TABLE_API_S_VER_1 */ + +static void iwl_fwrt_dump_tcm_error_log(struct iwl_fw_runtime *fwrt) +{ + struct iwl_trans *trans = fwrt->trans; + struct iwl_tcm_error_event_table table = {}; + u32 base = fwrt->trans->dbg.tcm_error_event_table; + int i; + + if (!base || + !(fwrt->trans->dbg.error_event_table_tlv_status & + IWL_ERROR_EVENT_TABLE_TCM)) + return; + + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + IWL_ERR(fwrt, "TCM status:\n"); + IWL_ERR(fwrt, "0x%08X | error ID\n", table.error_id); + IWL_ERR(fwrt, "0x%08X | tcm branchlink2\n", table.blink2); + IWL_ERR(fwrt, "0x%08X | tcm interruptlink1\n", table.ilink1); + IWL_ERR(fwrt, "0x%08X | tcm interruptlink2\n", table.ilink2); + IWL_ERR(fwrt, "0x%08X | tcm data1\n", table.data1); + IWL_ERR(fwrt, "0x%08X | tcm data2\n", table.data2); + IWL_ERR(fwrt, "0x%08X | tcm data3\n", table.data3); + IWL_ERR(fwrt, "0x%08X | tcm log PC\n", table.logpc); + IWL_ERR(fwrt, "0x%08X | tcm frame pointer\n", table.frame_pointer); + IWL_ERR(fwrt, "0x%08X | tcm stack pointer\n", table.stack_pointer); + IWL_ERR(fwrt, "0x%08X | tcm msg ID\n", table.msgid); + IWL_ERR(fwrt, "0x%08X | tcm ISR status\n", table.isr); + for (i = 0; i < ARRAY_SIZE(table.hw_status); i++) + IWL_ERR(fwrt, "0x%08X | tcm HW status[%d]\n", + table.hw_status[i], i); + for (i = 0; i < ARRAY_SIZE(table.sw_status); i++) + IWL_ERR(fwrt, "0x%08X | tcm SW status[%d]\n", + table.sw_status[i], i); + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + u32 scratch = iwl_read32(trans, CSR_FUNC_SCRATCH); + + IWL_ERR(fwrt, "Function Scratch status:\n"); + IWL_ERR(fwrt, "0x%08X | Func Scratch\n", scratch); + } +} + +static void iwl_fwrt_dump_iml_error_log(struct iwl_fw_runtime *fwrt) +{ + struct iwl_trans *trans = fwrt->trans; + u32 error, data1; + + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000) { + error = UMAG_SB_CPU_2_STATUS; + data1 = UMAG_SB_CPU_1_STATUS; + } else if (fwrt->trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_8000) { + error = SB_CPU_2_STATUS; + data1 = SB_CPU_1_STATUS; + } else { + return; + } + + error = iwl_read_umac_prph(trans, UMAG_SB_CPU_2_STATUS); + + IWL_ERR(trans, "IML/ROM dump:\n"); + + if (error & 0xFFFF0000) + IWL_ERR(trans, "0x%04X | IML/ROM SYSASSERT\n", error >> 16); + + IWL_ERR(fwrt, "0x%08X | IML/ROM error/state\n", error); + IWL_ERR(fwrt, "0x%08X | IML/ROM data1\n", + iwl_read_umac_prph(trans, data1)); + + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000) + IWL_ERR(fwrt, "0x%08X | IML/ROM WFPM_AUTH_KEY_0\n", + iwl_read_umac_prph(trans, SB_MODIFY_CFG_FLAG)); +} + +#define FSEQ_REG(x) { .addr = (x), .str = #x, } + +static void iwl_fwrt_dump_fseq_regs(struct iwl_fw_runtime *fwrt) +{ + struct iwl_trans *trans = fwrt->trans; + int i; + struct { + u32 addr; + const char *str; + } fseq_regs[] = { + FSEQ_REG(FSEQ_ERROR_CODE), + FSEQ_REG(FSEQ_TOP_INIT_VERSION), + FSEQ_REG(FSEQ_CNVIO_INIT_VERSION), + FSEQ_REG(FSEQ_OTP_VERSION), + FSEQ_REG(FSEQ_TOP_CONTENT_VERSION), + FSEQ_REG(FSEQ_ALIVE_TOKEN), + FSEQ_REG(FSEQ_CNVI_ID), + FSEQ_REG(FSEQ_CNVR_ID), + FSEQ_REG(CNVI_AUX_MISC_CHIP), + FSEQ_REG(CNVR_AUX_MISC_CHIP), + FSEQ_REG(CNVR_SCU_SD_REGS_SD_REG_DIG_DCDC_VTRIM), + FSEQ_REG(CNVR_SCU_SD_REGS_SD_REG_ACTIVE_VDIG_MIRROR), + }; + + if (!iwl_trans_grab_nic_access(trans)) + return; + + IWL_ERR(fwrt, "Fseq Registers:\n"); + + for (i = 0; i < ARRAY_SIZE(fseq_regs); i++) + IWL_ERR(fwrt, "0x%08X | %s\n", + iwl_read_prph_no_grab(trans, fseq_regs[i].addr), + fseq_regs[i].str); + + iwl_trans_release_nic_access(trans); +} + +void iwl_fwrt_dump_error_logs(struct iwl_fw_runtime *fwrt) +{ + if (!test_bit(STATUS_DEVICE_ENABLED, &fwrt->trans->status)) { + IWL_ERR(fwrt, + "DEVICE_ENABLED bit is not set. Aborting dump.\n"); + return; + } + + iwl_fwrt_dump_lmac_error_log(fwrt, 0); + if (fwrt->trans->dbg.lmac_error_event_table[1]) + iwl_fwrt_dump_lmac_error_log(fwrt, 1); + iwl_fwrt_dump_umac_error_log(fwrt); + iwl_fwrt_dump_tcm_error_log(fwrt); + iwl_fwrt_dump_iml_error_log(fwrt); + iwl_fwrt_dump_fseq_regs(fwrt); +} +IWL_EXPORT_SYMBOL(iwl_fwrt_dump_error_logs); diff --git a/sys/contrib/dev/iwlwifi/fw/error-dump.h b/sys/contrib/dev/iwlwifi/fw/error-dump.h new file mode 100644 index 00000000000..9036b32ec76 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/error-dump.h @@ -0,0 +1,542 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2014, 2018-2021 Intel Corporation + * Copyright (C) 2014-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __fw_error_dump_h__ +#define __fw_error_dump_h__ + +#include +#include "fw/api/cmdhdr.h" + +#define IWL_FW_ERROR_DUMP_BARKER 0x14789632 +#define IWL_FW_INI_ERROR_DUMP_BARKER 0x14789633 + +/** + * enum iwl_fw_error_dump_type - types of data in the dump file + * @IWL_FW_ERROR_DUMP_CSR: Control Status Registers - from offset 0 + * @IWL_FW_ERROR_DUMP_RXF: + * @IWL_FW_ERROR_DUMP_TXCMD: last TX command data, structured as + * &struct iwl_fw_error_dump_txcmd packets + * @IWL_FW_ERROR_DUMP_DEV_FW_INFO: struct %iwl_fw_error_dump_info + * info on the device / firmware. + * @IWL_FW_ERROR_DUMP_FW_MONITOR: firmware monitor + * @IWL_FW_ERROR_DUMP_PRPH: range of periphery registers - there can be several + * sections like this in a single file. + * @IWL_FW_ERROR_DUMP_FH_REGS: range of FH registers + * @IWL_FW_ERROR_DUMP_MEM: chunk of memory + * @IWL_FW_ERROR_DUMP_ERROR_INFO: description of what triggered this dump. + * Structured as &struct iwl_fw_error_dump_trigger_desc. + * @IWL_FW_ERROR_DUMP_RB: the content of an RB structured as + * &struct iwl_fw_error_dump_rb + * @IWL_FW_ERROR_PAGING: UMAC's image memory segments which were + * paged to the DRAM. + * @IWL_FW_ERROR_DUMP_RADIO_REG: Dump the radio registers. + * @IWL_FW_ERROR_DUMP_EXTERNAL: used only by external code utilities, and + * for that reason is not in use in any other place in the Linux Wi-Fi + * stack. + * @IWL_FW_ERROR_DUMP_MEM_CFG: the addresses and sizes of fifos in the smem, + * which we get from the fw after ALIVE. The content is structured as + * &struct iwl_fw_error_dump_smem_cfg. + */ +enum iwl_fw_error_dump_type { + /* 0 is deprecated */ + IWL_FW_ERROR_DUMP_CSR = 1, + IWL_FW_ERROR_DUMP_RXF = 2, + IWL_FW_ERROR_DUMP_TXCMD = 3, + IWL_FW_ERROR_DUMP_DEV_FW_INFO = 4, + IWL_FW_ERROR_DUMP_FW_MONITOR = 5, + IWL_FW_ERROR_DUMP_PRPH = 6, + IWL_FW_ERROR_DUMP_TXF = 7, + IWL_FW_ERROR_DUMP_FH_REGS = 8, + IWL_FW_ERROR_DUMP_MEM = 9, + IWL_FW_ERROR_DUMP_ERROR_INFO = 10, + IWL_FW_ERROR_DUMP_RB = 11, + IWL_FW_ERROR_DUMP_PAGING = 12, + IWL_FW_ERROR_DUMP_RADIO_REG = 13, + IWL_FW_ERROR_DUMP_INTERNAL_TXF = 14, + IWL_FW_ERROR_DUMP_EXTERNAL = 15, /* Do not move */ + IWL_FW_ERROR_DUMP_MEM_CFG = 16, + IWL_FW_ERROR_DUMP_D3_DEBUG_DATA = 17, + + IWL_FW_ERROR_DUMP_MAX, +}; + +/** + * struct iwl_fw_error_dump_data - data for one type + * @type: &enum iwl_fw_error_dump_type + * @len: the length starting from %data + * @data: the data itself + */ +struct iwl_fw_error_dump_data { + __le32 type; + __le32 len; + __u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_file - the layout of the header of the file + * @barker: must be %IWL_FW_ERROR_DUMP_BARKER + * @file_len: the length of all the file starting from %barker + * @data: array of &struct iwl_fw_error_dump_data + */ +struct iwl_fw_error_dump_file { + __le32 barker; + __le32 file_len; + u8 data[0]; +} __packed; + +/** + * struct iwl_fw_error_dump_txcmd - TX command data + * @cmdlen: original length of command + * @caplen: captured length of command (may be less) + * @data: captured command data, @caplen bytes + */ +struct iwl_fw_error_dump_txcmd { + __le32 cmdlen; + __le32 caplen; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_fifo - RX/TX FIFO data + * @fifo_num: number of FIFO (starting from 0) + * @available_bytes: num of bytes available in FIFO (may be less than FIFO size) + * @wr_ptr: position of write pointer + * @rd_ptr: position of read pointer + * @fence_ptr: position of fence pointer + * @fence_mode: the current mode of the fence (before locking) - + * 0=follow RD pointer ; 1 = freeze + * @data: all of the FIFO's data + */ +struct iwl_fw_error_dump_fifo { + __le32 fifo_num; + __le32 available_bytes; + __le32 wr_ptr; + __le32 rd_ptr; + __le32 fence_ptr; + __le32 fence_mode; + u8 data[]; +} __packed; + +enum iwl_fw_error_dump_family { + IWL_FW_ERROR_DUMP_FAMILY_7 = 7, + IWL_FW_ERROR_DUMP_FAMILY_8 = 8, +}; + +#define MAX_NUM_LMAC 2 + +/** + * struct iwl_fw_error_dump_info - info on the device / firmware + * @hw_type: the type of the device + * @hw_step: the step of the device + * @fw_human_readable: human readable FW version + * @dev_human_readable: name of the device + * @bus_human_readable: name of the bus used + * @num_of_lmacs: the number of lmacs + * @lmac_err_id: the lmac 0/1 error_id/rt_status that triggered the latest dump + * if the dump collection was not initiated by an assert, the value is 0 + * @umac_err_id: the umac error_id/rt_status that triggered the latest dump + * if the dump collection was not initiated by an assert, the value is 0 + */ +struct iwl_fw_error_dump_info { + __le32 hw_type; + __le32 hw_step; + u8 fw_human_readable[FW_VER_HUMAN_READABLE_SZ]; + u8 dev_human_readable[64]; + u8 bus_human_readable[8]; + u8 num_of_lmacs; + __le32 umac_err_id; + __le32 lmac_err_id[MAX_NUM_LMAC]; +} __packed; + +/** + * struct iwl_fw_error_dump_fw_mon - FW monitor data + * @fw_mon_wr_ptr: the position of the write pointer in the cyclic buffer + * @fw_mon_base_ptr: base pointer of the data + * @fw_mon_cycle_cnt: number of wraparounds + * @fw_mon_base_high_ptr: used in AX210 devices, the base adderss is 64 bit + * so fw_mon_base_ptr holds LSB 32 bits and fw_mon_base_high_ptr hold + * MSB 32 bits + * @reserved: for future use + * @data: captured data + */ +struct iwl_fw_error_dump_fw_mon { + __le32 fw_mon_wr_ptr; + __le32 fw_mon_base_ptr; + __le32 fw_mon_cycle_cnt; + __le32 fw_mon_base_high_ptr; + __le32 reserved[2]; + u8 data[]; +} __packed; + +#define MAX_NUM_LMAC 2 +#define TX_FIFO_INTERNAL_MAX_NUM 6 +#define TX_FIFO_MAX_NUM 15 +/** + * struct iwl_fw_error_dump_smem_cfg - Dump SMEM configuration + * This must follow &struct iwl_fwrt_shared_mem_cfg. + * @num_lmacs: number of lmacs + * @num_txfifo_entries: number of tx fifos + * @lmac: sizes of lmacs txfifos and rxfifo1 + * @rxfifo2_size: size of rxfifo2 + * @internal_txfifo_addr: address of internal tx fifo + * @internal_txfifo_size: size of internal tx fifo + */ +struct iwl_fw_error_dump_smem_cfg { + __le32 num_lmacs; + __le32 num_txfifo_entries; + struct { + __le32 txfifo_size[TX_FIFO_MAX_NUM]; + __le32 rxfifo1_size; + } lmac[MAX_NUM_LMAC]; + __le32 rxfifo2_size; + __le32 internal_txfifo_addr; + __le32 internal_txfifo_size[TX_FIFO_INTERNAL_MAX_NUM]; +} __packed; +/** + * struct iwl_fw_error_dump_prph - periphery registers data + * @prph_start: address of the first register in this chunk + * @data: the content of the registers + */ +struct iwl_fw_error_dump_prph { + __le32 prph_start; + __le32 data[]; +}; + +enum iwl_fw_error_dump_mem_type { + IWL_FW_ERROR_DUMP_MEM_SRAM, + IWL_FW_ERROR_DUMP_MEM_SMEM, + IWL_FW_ERROR_DUMP_MEM_NAMED_MEM = 10, +}; + +/** + * struct iwl_fw_error_dump_mem - chunk of memory + * @type: &enum iwl_fw_error_dump_mem_type + * @offset: the offset from which the memory was read + * @data: the content of the memory + */ +struct iwl_fw_error_dump_mem { + __le32 type; + __le32 offset; + u8 data[]; +}; + +/* Dump version, used by the dump parser to differentiate between + * different dump formats + */ +#define IWL_INI_DUMP_VER 1 + +/* Use bit 31 as dump info type to avoid colliding with region types */ +#define IWL_INI_DUMP_INFO_TYPE BIT(31) + +/** + * struct iwl_fw_ini_dump_entry + * @list: list of dump entries + * @size: size of the data + * @data: entry data + */ +struct iwl_fw_ini_dump_entry { + struct list_head list; + u32 size; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_file - header of dump file + * @barker: must be %IWL_FW_INI_ERROR_DUMP_BARKER + * @file_len: the length of all the file including the header + */ +struct iwl_fw_ini_dump_file_hdr { + __le32 barker; + __le32 file_len; +} __packed; + +/** + * struct iwl_fw_ini_fifo_hdr - fifo range header + * @fifo_num: the fifo number. In case of umac rx fifo, set BIT(31) to + * distinguish between lmac and umac rx fifos + * @num_of_registers: num of registers to dump, dword size each + */ +struct iwl_fw_ini_fifo_hdr { + __le32 fifo_num; + __le32 num_of_registers; +} __packed; + +/** + * struct iwl_fw_ini_error_dump_range - range of memory + * @range_data_size: the size of this range, in bytes + * @internal_base_addr: base address of internal memory range + * @dram_base_addr: base address of dram monitor range + * @page_num: page number of memory range + * @fifo_hdr: fifo header of memory range + * @fw_pkt: FW packet header of memory range + * @data: the actual memory + */ +struct iwl_fw_ini_error_dump_range { + __le32 range_data_size; + union { + __le32 internal_base_addr; + __le64 dram_base_addr; + __le32 page_num; + struct iwl_fw_ini_fifo_hdr fifo_hdr; + struct iwl_cmd_header fw_pkt_hdr; + }; + __le32 data[]; +} __packed; + +/** + * struct iwl_fw_ini_error_dump_header - ini region dump header + * @version: dump version + * @region_id: id of the region + * @num_of_ranges: number of ranges in this region + * @name_len: number of bytes allocated to the name string of this region + * @name: name of the region + */ +struct iwl_fw_ini_error_dump_header { + __le32 version; + __le32 region_id; + __le32 num_of_ranges; + __le32 name_len; + u8 name[IWL_FW_INI_MAX_NAME]; +}; + +/** + * struct iwl_fw_ini_error_dump - ini region dump + * @header: the header of this region + * @data: data of memory ranges in this region, + * see &struct iwl_fw_ini_error_dump_range + */ +struct iwl_fw_ini_error_dump { + struct iwl_fw_ini_error_dump_header header; + u8 data[]; +} __packed; + +/* This bit is used to differentiate between lmac and umac rxf */ +#define IWL_RXF_UMAC_BIT BIT(31) + +/** + * struct iwl_fw_ini_error_dump_register - ini register dump + * @addr: address of the register + * @data: data of the register + */ +struct iwl_fw_ini_error_dump_register { + __le32 addr; + __le32 data; +} __packed; + +/** + * struct iwl_fw_ini_dump_cfg_name - configuration name + * @image_type: image type the configuration is related to + * @cfg_name_len: length of the configuration name + * @cfg_name: name of the configuraiton + */ +struct iwl_fw_ini_dump_cfg_name { + __le32 image_type; + __le32 cfg_name_len; + u8 cfg_name[IWL_FW_INI_MAX_CFG_NAME]; +} __packed; + +/* AX210's HW type */ +#define IWL_AX210_HW_TYPE 0x42 +/* How many bits to roll when adding to the HW type of AX210 HW */ +#define IWL_AX210_HW_TYPE_ADDITION_SHIFT 12 + +/* struct iwl_fw_ini_dump_info - ini dump information + * @version: dump version + * @time_point: time point that caused the dump collection + * @trigger_reason: reason of the trigger + * @external_cfg_state: &enum iwl_ini_cfg_state + * @ver_type: FW version type + * @ver_subtype: FW version subype + * @hw_step: HW step + * @hw_type: HW type + * @rf_id_flavor: HW RF id flavor + * @rf_id_dash: HW RF id dash + * @rf_id_step: HW RF id step + * @rf_id_type: HW RF id type + * @lmac_major: lmac major version + * @lmac_minor: lmac minor version + * @umac_major: umac major version + * @umac_minor: umac minor version + * @fw_mon_mode: FW monitor mode &enum iwl_fw_ini_buffer_location + * @regions_mask: bitmap mask of regions ids in the dump + * @build_tag_len: length of the build tag + * @build_tag: build tag string + * @num_of_cfg_names: number of configuration name structs + * @cfg_names: configuration names + */ +struct iwl_fw_ini_dump_info { + __le32 version; + __le32 time_point; + __le32 trigger_reason; + __le32 external_cfg_state; + __le32 ver_type; + __le32 ver_subtype; + __le32 hw_step; + __le32 hw_type; + __le32 rf_id_flavor; + __le32 rf_id_dash; + __le32 rf_id_step; + __le32 rf_id_type; + __le32 lmac_major; + __le32 lmac_minor; + __le32 umac_major; + __le32 umac_minor; + __le32 fw_mon_mode; + __le64 regions_mask; + __le32 build_tag_len; + u8 build_tag[FW_VER_HUMAN_READABLE_SZ]; + __le32 num_of_cfg_names; + struct iwl_fw_ini_dump_cfg_name cfg_names[]; +} __packed; + +/** + * struct iwl_fw_ini_err_table_dump - ini error table dump + * @header: header of the region + * @version: error table version + * @data: data of memory ranges in this region, + * see &struct iwl_fw_ini_error_dump_range + */ +struct iwl_fw_ini_err_table_dump { + struct iwl_fw_ini_error_dump_header header; + __le32 version; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_rb - content of an Receive Buffer + * @index: the index of the Receive Buffer in the Rx queue + * @rxq: the RB's Rx queue + * @reserved: + * @data: the content of the Receive Buffer + */ +struct iwl_fw_error_dump_rb { + __le32 index; + __le32 rxq; + __le32 reserved; + u8 data[]; +}; + +/** + * struct iwl_fw_ini_monitor_dump - ini monitor dump + * @header: header of the region + * @write_ptr: write pointer position in the buffer + * @cycle_cnt: cycles count + * @cur_frag: current fragment in use + * @data: data of memory ranges in this region, + * see &struct iwl_fw_ini_error_dump_range + */ +struct iwl_fw_ini_monitor_dump { + struct iwl_fw_ini_error_dump_header header; + __le32 write_ptr; + __le32 cycle_cnt; + __le32 cur_frag; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_ini_special_device_memory - special device memory + * @header: header of the region + * @type: type of special memory + * @version: struct special memory version + * @data: data of memory ranges in this region, + * see &struct iwl_fw_ini_error_dump_range + */ +struct iwl_fw_ini_special_device_memory { + struct iwl_fw_ini_error_dump_header header; + __le16 type; + __le16 version; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_paging - content of the UMAC's image page + * block on DRAM + * @index: the index of the page block + * @reserved: + * @data: the content of the page block + */ +struct iwl_fw_error_dump_paging { + __le32 index; + __le32 reserved; + u8 data[]; +}; + +/** + * iwl_fw_error_next_data - advance fw error dump data pointer + * @data: previous data block + * Returns: next data block + */ +static inline struct iwl_fw_error_dump_data * +iwl_fw_error_next_data(struct iwl_fw_error_dump_data *data) +{ + return (void *)(data->data + le32_to_cpu(data->len)); +} + +/** + * enum iwl_fw_dbg_trigger - triggers available + * + * @FW_DBG_TRIGGER_USER: trigger log collection by user + * This should not be defined as a trigger to the driver, but a value the + * driver should set to indicate that the trigger was initiated by the + * user. + * @FW_DBG_TRIGGER_FW_ASSERT: trigger log collection when the firmware asserts + * @FW_DBG_TRIGGER_MISSED_BEACONS: trigger log collection when beacons are + * missed. + * @FW_DBG_TRIGGER_CHANNEL_SWITCH: trigger log collection upon channel switch. + * @FW_DBG_TRIGGER_FW_NOTIF: trigger log collection when the firmware sends a + * command response or a notification. + * @FW_DBG_TRIGGER_MLME: trigger log collection upon MLME event. + * @FW_DBG_TRIGGER_STATS: trigger log collection upon statistics threshold. + * @FW_DBG_TRIGGER_RSSI: trigger log collection when the rssi of the beacon + * goes below a threshold. + * @FW_DBG_TRIGGER_TXQ_TIMERS: configures the timers for the Tx queue hang + * detection. + * @FW_DBG_TRIGGER_TIME_EVENT: trigger log collection upon time events related + * events. + * @FW_DBG_TRIGGER_BA: trigger log collection upon BlockAck related events. + * @FW_DBG_TX_LATENCY: trigger log collection when the tx latency goes above a + * threshold. + * @FW_DBG_TDLS: trigger log collection upon TDLS related events. + * @FW_DBG_TRIGGER_TX_STATUS: trigger log collection upon tx status when + * the firmware sends a tx reply. + * @FW_DBG_TRIGGER_ALIVE_TIMEOUT: trigger log collection if alive flow timeouts + * @FW_DBG_TRIGGER_DRIVER: trigger log collection upon a flow failure + * in the driver. + */ +enum iwl_fw_dbg_trigger { + FW_DBG_TRIGGER_INVALID = 0, + FW_DBG_TRIGGER_USER, + FW_DBG_TRIGGER_FW_ASSERT, + FW_DBG_TRIGGER_MISSED_BEACONS, + FW_DBG_TRIGGER_CHANNEL_SWITCH, + FW_DBG_TRIGGER_FW_NOTIF, + FW_DBG_TRIGGER_MLME, + FW_DBG_TRIGGER_STATS, + FW_DBG_TRIGGER_RSSI, + FW_DBG_TRIGGER_TXQ_TIMERS, + FW_DBG_TRIGGER_TIME_EVENT, + FW_DBG_TRIGGER_BA, + FW_DBG_TRIGGER_TX_LATENCY, + FW_DBG_TRIGGER_TDLS, + FW_DBG_TRIGGER_TX_STATUS, + FW_DBG_TRIGGER_ALIVE_TIMEOUT, + FW_DBG_TRIGGER_DRIVER, + + /* must be last */ + FW_DBG_TRIGGER_MAX, +}; + +/** + * struct iwl_fw_error_dump_trigger_desc - describes the trigger condition + * @type: &enum iwl_fw_dbg_trigger + * @data: raw data about what happened + */ +struct iwl_fw_error_dump_trigger_desc { + __le32 type; + u8 data[]; +}; + +#endif /* __fw_error_dump_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/file.h b/sys/contrib/dev/iwlwifi/fw/file.h new file mode 100644 index 00000000000..3d572f5024b --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/file.h @@ -0,0 +1,984 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2008-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_file_h__ +#define __iwl_fw_file_h__ + +#include +#include + +/* v1/v2 uCode file layout */ +struct iwl_ucode_header { + __le32 ver; /* major/minor/API/serial */ + union { + struct { + __le32 inst_size; /* bytes of runtime code */ + __le32 data_size; /* bytes of runtime data */ + __le32 init_size; /* bytes of init code */ + __le32 init_data_size; /* bytes of init data */ + __le32 boot_size; /* bytes of bootstrap code */ + u8 data[0]; /* in same order as sizes */ + } v1; + struct { + __le32 build; /* build number */ + __le32 inst_size; /* bytes of runtime code */ + __le32 data_size; /* bytes of runtime data */ + __le32 init_size; /* bytes of init code */ + __le32 init_data_size; /* bytes of init data */ + __le32 boot_size; /* bytes of bootstrap code */ + u8 data[0]; /* in same order as sizes */ + } v2; + } u; +}; + +#define IWL_UCODE_TLV_DEBUG_BASE 0x1000005 +#define IWL_UCODE_TLV_CONST_BASE 0x100 + +/* + * new TLV uCode file layout + * + * The new TLV file format contains TLVs, that each specify + * some piece of data. + */ + +enum iwl_ucode_tlv_type { + IWL_UCODE_TLV_INVALID = 0, /* unused */ + IWL_UCODE_TLV_INST = 1, + IWL_UCODE_TLV_DATA = 2, + IWL_UCODE_TLV_INIT = 3, + IWL_UCODE_TLV_INIT_DATA = 4, + IWL_UCODE_TLV_BOOT = 5, + IWL_UCODE_TLV_PROBE_MAX_LEN = 6, /* a u32 value */ + IWL_UCODE_TLV_PAN = 7, /* deprecated -- only used in DVM */ + IWL_UCODE_TLV_MEM_DESC = 7, /* replaces PAN in non-DVM */ + IWL_UCODE_TLV_RUNT_EVTLOG_PTR = 8, + IWL_UCODE_TLV_RUNT_EVTLOG_SIZE = 9, + IWL_UCODE_TLV_RUNT_ERRLOG_PTR = 10, + IWL_UCODE_TLV_INIT_EVTLOG_PTR = 11, + IWL_UCODE_TLV_INIT_EVTLOG_SIZE = 12, + IWL_UCODE_TLV_INIT_ERRLOG_PTR = 13, + IWL_UCODE_TLV_ENHANCE_SENS_TBL = 14, + IWL_UCODE_TLV_PHY_CALIBRATION_SIZE = 15, + IWL_UCODE_TLV_WOWLAN_INST = 16, + IWL_UCODE_TLV_WOWLAN_DATA = 17, + IWL_UCODE_TLV_FLAGS = 18, + IWL_UCODE_TLV_SEC_RT = 19, + IWL_UCODE_TLV_SEC_INIT = 20, + IWL_UCODE_TLV_SEC_WOWLAN = 21, + IWL_UCODE_TLV_DEF_CALIB = 22, + IWL_UCODE_TLV_PHY_SKU = 23, + IWL_UCODE_TLV_SECURE_SEC_RT = 24, + IWL_UCODE_TLV_SECURE_SEC_INIT = 25, + IWL_UCODE_TLV_SECURE_SEC_WOWLAN = 26, + IWL_UCODE_TLV_NUM_OF_CPU = 27, + IWL_UCODE_TLV_CSCHEME = 28, + IWL_UCODE_TLV_API_CHANGES_SET = 29, + IWL_UCODE_TLV_ENABLED_CAPABILITIES = 30, + IWL_UCODE_TLV_N_SCAN_CHANNELS = 31, + IWL_UCODE_TLV_PAGING = 32, + IWL_UCODE_TLV_SEC_RT_USNIFFER = 34, + /* 35 is unused */ + IWL_UCODE_TLV_FW_VERSION = 36, + IWL_UCODE_TLV_FW_DBG_DEST = 38, + IWL_UCODE_TLV_FW_DBG_CONF = 39, + IWL_UCODE_TLV_FW_DBG_TRIGGER = 40, + IWL_UCODE_TLV_CMD_VERSIONS = 48, + IWL_UCODE_TLV_FW_GSCAN_CAPA = 50, + IWL_UCODE_TLV_FW_MEM_SEG = 51, + IWL_UCODE_TLV_IML = 52, + IWL_UCODE_TLV_UMAC_DEBUG_ADDRS = 54, + IWL_UCODE_TLV_LMAC_DEBUG_ADDRS = 55, + IWL_UCODE_TLV_FW_RECOVERY_INFO = 57, + IWL_UCODE_TLV_HW_TYPE = 58, + IWL_UCODE_TLV_FW_FSEQ_VERSION = 60, + IWL_UCODE_TLV_PHY_INTEGRATION_VERSION = 61, + + IWL_UCODE_TLV_PNVM_VERSION = 62, + IWL_UCODE_TLV_PNVM_SKU = 64, + IWL_UCODE_TLV_TCM_DEBUG_ADDRS = 65, + + IWL_UCODE_TLV_SEC_TABLE_ADDR = 66, + IWL_UCODE_TLV_D3_KEK_KCK_ADDR = 67, + + IWL_UCODE_TLV_FW_NUM_STATIONS = IWL_UCODE_TLV_CONST_BASE + 0, + + IWL_UCODE_TLV_TYPE_DEBUG_INFO = IWL_UCODE_TLV_DEBUG_BASE + 0, + IWL_UCODE_TLV_TYPE_BUFFER_ALLOCATION = IWL_UCODE_TLV_DEBUG_BASE + 1, + IWL_UCODE_TLV_TYPE_HCMD = IWL_UCODE_TLV_DEBUG_BASE + 2, + IWL_UCODE_TLV_TYPE_REGIONS = IWL_UCODE_TLV_DEBUG_BASE + 3, + IWL_UCODE_TLV_TYPE_TRIGGERS = IWL_UCODE_TLV_DEBUG_BASE + 4, + IWL_UCODE_TLV_TYPE_CONF_SET = IWL_UCODE_TLV_DEBUG_BASE + 5, + IWL_UCODE_TLV_DEBUG_MAX = IWL_UCODE_TLV_TYPE_TRIGGERS, + + /* TLVs 0x1000-0x2000 are for internal driver usage */ + IWL_UCODE_TLV_FW_DBG_DUMP_LST = 0x1000, +}; + +struct iwl_ucode_tlv { + __le32 type; /* see above */ + __le32 length; /* not including type/length fields */ + u8 data[0]; +}; + +#define IWL_TLV_UCODE_MAGIC 0x0a4c5749 +#define FW_VER_HUMAN_READABLE_SZ 64 + +struct iwl_tlv_ucode_header { + /* + * The TLV style ucode header is distinguished from + * the v1/v2 style header by first four bytes being + * zero, as such is an invalid combination of + * major/minor/API/serial versions. + */ + __le32 zero; + __le32 magic; + u8 human_readable[FW_VER_HUMAN_READABLE_SZ]; + /* major/minor/API/serial or major in new format */ + __le32 ver; + __le32 build; + __le64 ignore; + /* + * The data contained herein has a TLV layout, + * see above for the TLV header and types. + * Note that each TLV is padded to a length + * that is a multiple of 4 for alignment. + */ + u8 data[0]; +}; + +/* + * ucode TLVs + * + * ability to get extension for: flags & capabilities from ucode binaries files + */ +struct iwl_ucode_api { + __le32 api_index; + __le32 api_flags; +} __packed; + +struct iwl_ucode_capa { + __le32 api_index; + __le32 api_capa; +} __packed; + +/** + * enum iwl_ucode_tlv_flag - ucode API flags + * @IWL_UCODE_TLV_FLAGS_PAN: This is PAN capable microcode; this previously + * was a separate TLV but moved here to save space. + * @IWL_UCODE_TLV_FLAGS_NEWSCAN: new uCode scan behavior on hidden SSID, + * treats good CRC threshold as a boolean + * @IWL_UCODE_TLV_FLAGS_MFP: This uCode image supports MFP (802.11w). + * @IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT: This uCode image supports uAPSD + * @IWL_UCODE_TLV_FLAGS_SHORT_BL: 16 entries of block list instead of 64 in scan + * offload profile config command. + * @IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS: D3 image supports up to six + * (rather than two) IPv6 addresses + * @IWL_UCODE_TLV_FLAGS_NO_BASIC_SSID: not sending a probe with the SSID element + * from the probe request template. + * @IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL: new NS offload (small version) + * @IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE: new NS offload (large version) + * @IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT: General support for uAPSD + * @IWL_UCODE_TLV_FLAGS_P2P_PS_UAPSD: P2P client supports uAPSD power save + * @IWL_UCODE_TLV_FLAGS_BCAST_FILTERING: uCode supports broadcast filtering. + * @IWL_UCODE_TLV_FLAGS_EBS_SUPPORT: this uCode image supports EBS. + */ +enum iwl_ucode_tlv_flag { + IWL_UCODE_TLV_FLAGS_PAN = BIT(0), + IWL_UCODE_TLV_FLAGS_NEWSCAN = BIT(1), + IWL_UCODE_TLV_FLAGS_MFP = BIT(2), + IWL_UCODE_TLV_FLAGS_SHORT_BL = BIT(7), + IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS = BIT(10), + IWL_UCODE_TLV_FLAGS_NO_BASIC_SSID = BIT(12), + IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL = BIT(15), + IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE = BIT(16), + IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT = BIT(24), + IWL_UCODE_TLV_FLAGS_EBS_SUPPORT = BIT(25), + IWL_UCODE_TLV_FLAGS_P2P_PS_UAPSD = BIT(26), + IWL_UCODE_TLV_FLAGS_BCAST_FILTERING = BIT(29), +}; + +typedef unsigned int __bitwise iwl_ucode_tlv_api_t; + +/** + * enum iwl_ucode_tlv_api - ucode api + * @IWL_UCODE_TLV_API_FRAGMENTED_SCAN: This ucode supports active dwell time + * longer than the passive one, which is essential for fragmented scan. + * @IWL_UCODE_TLV_API_WIFI_MCC_UPDATE: ucode supports MCC updates with source. + * @IWL_UCODE_TLV_API_LQ_SS_PARAMS: Configure STBC/BFER via LQ CMD ss_params + * @IWL_UCODE_TLV_API_NEW_VERSION: new versioning format + * @IWL_UCODE_TLV_API_SCAN_TSF_REPORT: Scan start time reported in scan + * iteration complete notification, and the timestamp reported for RX + * received during scan, are reported in TSF of the mac specified in the + * scan request. + * @IWL_UCODE_TLV_API_TKIP_MIC_KEYS: This ucode supports version 2 of + * ADD_MODIFY_STA_KEY_API_S_VER_2. + * @IWL_UCODE_TLV_API_STA_TYPE: This ucode supports station type assignement. + * @IWL_UCODE_TLV_API_NAN2_VER2: This ucode supports NAN API version 2 + * @IWL_UCODE_TLV_API_NEW_RX_STATS: should new RX STATISTICS API be used + * @IWL_UCODE_TLV_API_QUOTA_LOW_LATENCY: Quota command includes a field + * indicating low latency direction. + * @IWL_UCODE_TLV_API_DEPRECATE_TTAK: RX status flag TTAK ok (bit 7) is + * deprecated. + * @IWL_UCODE_TLV_API_ADAPTIVE_DWELL_V2: This ucode supports version 8 + * of scan request: SCAN_REQUEST_CMD_UMAC_API_S_VER_8 + * @IWL_UCODE_TLV_API_FRAG_EBS: This ucode supports fragmented EBS + * @IWL_UCODE_TLV_API_REDUCE_TX_POWER: This ucode supports v5 of + * the REDUCE_TX_POWER_CMD. + * @IWL_UCODE_TLV_API_SHORT_BEACON_NOTIF: This ucode supports the short + * version of the beacon notification. + * @IWL_UCODE_TLV_API_BEACON_FILTER_V4: This ucode supports v4 of + * BEACON_FILTER_CONFIG_API_S_VER_4. + * @IWL_UCODE_TLV_API_REGULATORY_NVM_INFO: This ucode supports v4 of + * REGULATORY_NVM_GET_INFO_RSP_API_S. + * @IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ: This ucode supports v7 of + * LOCATION_RANGE_REQ_CMD_API_S and v6 of LOCATION_RANGE_RESP_NTFY_API_S. + * @IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS: This ucode supports v2 of + * SCAN_OFFLOAD_PROFILE_MATCH_RESULTS_S and v3 of + * SCAN_OFFLOAD_PROFILES_QUERY_RSP_S. + * @IWL_UCODE_TLV_API_MBSSID_HE: This ucode supports v2 of + * STA_CONTEXT_DOT11AX_API_S + * @IWL_UCODE_TLV_API_SAR_TABLE_VER: This ucode supports different sar + * version tables. + * @IWL_UCODE_TLV_API_REDUCED_SCAN_CONFIG: This ucode supports v3 of + * SCAN_CONFIG_DB_CMD_API_S. + * + * @NUM_IWL_UCODE_TLV_API: number of bits used + */ +enum iwl_ucode_tlv_api { + /* API Set 0 */ + IWL_UCODE_TLV_API_FRAGMENTED_SCAN = (__force iwl_ucode_tlv_api_t)8, + IWL_UCODE_TLV_API_WIFI_MCC_UPDATE = (__force iwl_ucode_tlv_api_t)9, + IWL_UCODE_TLV_API_LQ_SS_PARAMS = (__force iwl_ucode_tlv_api_t)18, + IWL_UCODE_TLV_API_NEW_VERSION = (__force iwl_ucode_tlv_api_t)20, + IWL_UCODE_TLV_API_SCAN_TSF_REPORT = (__force iwl_ucode_tlv_api_t)28, + IWL_UCODE_TLV_API_TKIP_MIC_KEYS = (__force iwl_ucode_tlv_api_t)29, + IWL_UCODE_TLV_API_STA_TYPE = (__force iwl_ucode_tlv_api_t)30, + IWL_UCODE_TLV_API_NAN2_VER2 = (__force iwl_ucode_tlv_api_t)31, + /* API Set 1 */ + IWL_UCODE_TLV_API_ADAPTIVE_DWELL = (__force iwl_ucode_tlv_api_t)32, + IWL_UCODE_TLV_API_OCE = (__force iwl_ucode_tlv_api_t)33, + IWL_UCODE_TLV_API_NEW_BEACON_TEMPLATE = (__force iwl_ucode_tlv_api_t)34, + IWL_UCODE_TLV_API_NEW_RX_STATS = (__force iwl_ucode_tlv_api_t)35, + IWL_UCODE_TLV_API_WOWLAN_KEY_MATERIAL = (__force iwl_ucode_tlv_api_t)36, + IWL_UCODE_TLV_API_QUOTA_LOW_LATENCY = (__force iwl_ucode_tlv_api_t)38, + IWL_UCODE_TLV_API_DEPRECATE_TTAK = (__force iwl_ucode_tlv_api_t)41, + IWL_UCODE_TLV_API_ADAPTIVE_DWELL_V2 = (__force iwl_ucode_tlv_api_t)42, + IWL_UCODE_TLV_API_FRAG_EBS = (__force iwl_ucode_tlv_api_t)44, + IWL_UCODE_TLV_API_REDUCE_TX_POWER = (__force iwl_ucode_tlv_api_t)45, + IWL_UCODE_TLV_API_SHORT_BEACON_NOTIF = (__force iwl_ucode_tlv_api_t)46, + IWL_UCODE_TLV_API_BEACON_FILTER_V4 = (__force iwl_ucode_tlv_api_t)47, + IWL_UCODE_TLV_API_REGULATORY_NVM_INFO = (__force iwl_ucode_tlv_api_t)48, + IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ = (__force iwl_ucode_tlv_api_t)49, + IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS = (__force iwl_ucode_tlv_api_t)50, + IWL_UCODE_TLV_API_MBSSID_HE = (__force iwl_ucode_tlv_api_t)52, + IWL_UCODE_TLV_API_WOWLAN_TCP_SYN_WAKE = (__force iwl_ucode_tlv_api_t)53, + IWL_UCODE_TLV_API_FTM_RTT_ACCURACY = (__force iwl_ucode_tlv_api_t)54, + IWL_UCODE_TLV_API_SAR_TABLE_VER = (__force iwl_ucode_tlv_api_t)55, + IWL_UCODE_TLV_API_REDUCED_SCAN_CONFIG = (__force iwl_ucode_tlv_api_t)56, + IWL_UCODE_TLV_API_ADWELL_HB_DEF_N_AP = (__force iwl_ucode_tlv_api_t)57, + IWL_UCODE_TLV_API_SCAN_EXT_CHAN_VER = (__force iwl_ucode_tlv_api_t)58, + IWL_UCODE_TLV_API_BAND_IN_RX_DATA = (__force iwl_ucode_tlv_api_t)59, + + +#ifdef __CHECKER__ + /* sparse says it cannot increment the previous enum member */ +#define NUM_IWL_UCODE_TLV_API 128 +#else + NUM_IWL_UCODE_TLV_API +#endif +}; + +typedef unsigned int __bitwise iwl_ucode_tlv_capa_t; + +/** + * enum iwl_ucode_tlv_capa - ucode capabilities + * @IWL_UCODE_TLV_CAPA_D0I3_SUPPORT: supports D0i3 + * @IWL_UCODE_TLV_CAPA_LAR_SUPPORT: supports Location Aware Regulatory + * @IWL_UCODE_TLV_CAPA_UMAC_SCAN: supports UMAC scan. + * @IWL_UCODE_TLV_CAPA_BEAMFORMER: supports Beamformer + * @IWL_UCODE_TLV_CAPA_TDLS_SUPPORT: support basic TDLS functionality + * @IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT: supports insertion of current + * tx power value into TPC Report action frame and Link Measurement Report + * action frame + * @IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT: supports updating current + * channel in DS parameter set element in probe requests. + * @IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT: supports adding TPC Report IE in + * probe requests. + * @IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT: supports Quiet Period requests + * @IWL_UCODE_TLV_CAPA_DQA_SUPPORT: supports dynamic queue allocation (DQA), + * which also implies support for the scheduler configuration command + * @IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH: supports TDLS channel switching + * @IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG: Consolidated D3-D0 image + * @IWL_UCODE_TLV_CAPA_HOTSPOT_SUPPORT: supports Hot Spot Command + * @IWL_UCODE_TLV_CAPA_DC2DC_SUPPORT: supports DC2DC Command + * @IWL_UCODE_TLV_CAPA_CSUM_SUPPORT: supports TCP Checksum Offload + * @IWL_UCODE_TLV_CAPA_RADIO_BEACON_STATS: support radio and beacon statistics + * @IWL_UCODE_TLV_CAPA_P2P_SCM_UAPSD: supports U-APSD on p2p interface when it + * is standalone or with a BSS station interface in the same binding. + * @IWL_UCODE_TLV_CAPA_BT_COEX_PLCR: enabled BT Coex packet level co-running + * @IWL_UCODE_TLV_CAPA_LAR_MULTI_MCC: ucode supports LAR updates with different + * sources for the MCC. This TLV bit is a future replacement to + * IWL_UCODE_TLV_API_WIFI_MCC_UPDATE. When either is set, multi-source LAR + * is supported. + * @IWL_UCODE_TLV_CAPA_BT_COEX_RRC: supports BT Coex RRC + * @IWL_UCODE_TLV_CAPA_GSCAN_SUPPORT: supports gscan (no longer used) + * @IWL_UCODE_TLV_CAPA_SOC_LATENCY_SUPPORT: the firmware supports setting + * stabilization latency for SoCs. + * @IWL_UCODE_TLV_CAPA_STA_PM_NOTIF: firmware will send STA PM notification + * @IWL_UCODE_TLV_CAPA_TLC_OFFLOAD: firmware implements rate scaling algorithm + * @IWL_UCODE_TLV_CAPA_DYNAMIC_QUOTA: firmware implements quota related + * @IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2: firmware implements Coex Schema 2 + * IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD: firmware supports CSA command + * @IWL_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS: firmware supports ultra high band + * (6 GHz). + * @IWL_UCODE_TLV_CAPA_CS_MODIFY: firmware supports modify action CSA command + * @IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE: extended DTS measurement + * @IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS: supports short PM timeouts + * @IWL_UCODE_TLV_CAPA_BT_MPLUT_SUPPORT: supports bt-coex Multi-priority LUT + * @IWL_UCODE_TLV_CAPA_CSA_AND_TBTT_OFFLOAD: the firmware supports CSA + * countdown offloading. Beacon notifications are not sent to the host. + * The fw also offloads TBTT alignment. + * @IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION: firmware will decide on what + * antenna the beacon should be transmitted + * @IWL_UCODE_TLV_CAPA_BEACON_STORING: firmware will store the latest beacon + * from AP and will send it upon d0i3 exit. + * @IWL_UCODE_TLV_CAPA_LAR_SUPPORT_V3: support LAR API V3 + * @IWL_UCODE_TLV_CAPA_CT_KILL_BY_FW: firmware responsible for CT-kill + * @IWL_UCODE_TLV_CAPA_TEMP_THS_REPORT_SUPPORT: supports temperature + * thresholds reporting + * @IWL_UCODE_TLV_CAPA_CTDP_SUPPORT: supports cTDP command + * @IWL_UCODE_TLV_CAPA_USNIFFER_UNIFIED: supports usniffer enabled in + * regular image. + * @IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG: support getting more shared + * memory addresses from the firmware. + * @IWL_UCODE_TLV_CAPA_LQM_SUPPORT: supports Link Quality Measurement + * @IWL_UCODE_TLV_CAPA_TX_POWER_ACK: reduced TX power API has larger + * command size (command version 4) that supports toggling ACK TX + * power reduction. + * @IWL_UCODE_TLV_CAPA_D3_DEBUG: supports debug recording during D3 + * @IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT: MCC response support 11ax + * capability. + * @IWL_UCODE_TLV_CAPA_CSI_REPORTING: firmware is capable of being configured + * to report the CSI information with (certain) RX frames + * @IWL_UCODE_TLV_CAPA_FTM_CALIBRATED: has FTM calibrated and thus supports both + * initiator and responder + * @IWL_UCODE_TLV_CAPA_MLME_OFFLOAD: supports MLME offload + * @IWL_UCODE_TLV_CAPA_PROTECTED_TWT: Supports protection of TWT action frames + * @IWL_UCODE_TLV_CAPA_FW_RESET_HANDSHAKE: Supports the firmware handshake in + * reset flow + * @IWL_UCODE_TLV_CAPA_PASSIVE_6GHZ_SCAN: Support for passive scan on 6GHz PSC + * channels even when these are not enabled. + * + * @NUM_IWL_UCODE_TLV_CAPA: number of bits used + */ +enum iwl_ucode_tlv_capa { + /* set 0 */ + IWL_UCODE_TLV_CAPA_D0I3_SUPPORT = (__force iwl_ucode_tlv_capa_t)0, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT = (__force iwl_ucode_tlv_capa_t)1, + IWL_UCODE_TLV_CAPA_UMAC_SCAN = (__force iwl_ucode_tlv_capa_t)2, + IWL_UCODE_TLV_CAPA_BEAMFORMER = (__force iwl_ucode_tlv_capa_t)3, + IWL_UCODE_TLV_CAPA_TDLS_SUPPORT = (__force iwl_ucode_tlv_capa_t)6, + IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT = (__force iwl_ucode_tlv_capa_t)8, + IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT = (__force iwl_ucode_tlv_capa_t)9, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT = (__force iwl_ucode_tlv_capa_t)10, + IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT = (__force iwl_ucode_tlv_capa_t)11, + IWL_UCODE_TLV_CAPA_DQA_SUPPORT = (__force iwl_ucode_tlv_capa_t)12, + IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH = (__force iwl_ucode_tlv_capa_t)13, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG = (__force iwl_ucode_tlv_capa_t)17, + IWL_UCODE_TLV_CAPA_HOTSPOT_SUPPORT = (__force iwl_ucode_tlv_capa_t)18, + IWL_UCODE_TLV_CAPA_DC2DC_CONFIG_SUPPORT = (__force iwl_ucode_tlv_capa_t)19, + IWL_UCODE_TLV_CAPA_CSUM_SUPPORT = (__force iwl_ucode_tlv_capa_t)21, + IWL_UCODE_TLV_CAPA_RADIO_BEACON_STATS = (__force iwl_ucode_tlv_capa_t)22, + IWL_UCODE_TLV_CAPA_P2P_SCM_UAPSD = (__force iwl_ucode_tlv_capa_t)26, + IWL_UCODE_TLV_CAPA_BT_COEX_PLCR = (__force iwl_ucode_tlv_capa_t)28, + IWL_UCODE_TLV_CAPA_LAR_MULTI_MCC = (__force iwl_ucode_tlv_capa_t)29, + IWL_UCODE_TLV_CAPA_BT_COEX_RRC = (__force iwl_ucode_tlv_capa_t)30, + IWL_UCODE_TLV_CAPA_GSCAN_SUPPORT = (__force iwl_ucode_tlv_capa_t)31, + + /* set 1 */ + IWL_UCODE_TLV_CAPA_SOC_LATENCY_SUPPORT = (__force iwl_ucode_tlv_capa_t)37, + IWL_UCODE_TLV_CAPA_STA_PM_NOTIF = (__force iwl_ucode_tlv_capa_t)38, + IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT = (__force iwl_ucode_tlv_capa_t)39, + IWL_UCODE_TLV_CAPA_CDB_SUPPORT = (__force iwl_ucode_tlv_capa_t)40, + IWL_UCODE_TLV_CAPA_D0I3_END_FIRST = (__force iwl_ucode_tlv_capa_t)41, + IWL_UCODE_TLV_CAPA_TLC_OFFLOAD = (__force iwl_ucode_tlv_capa_t)43, + IWL_UCODE_TLV_CAPA_DYNAMIC_QUOTA = (__force iwl_ucode_tlv_capa_t)44, + IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2 = (__force iwl_ucode_tlv_capa_t)45, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD = (__force iwl_ucode_tlv_capa_t)46, + IWL_UCODE_TLV_CAPA_FTM_CALIBRATED = (__force iwl_ucode_tlv_capa_t)47, + IWL_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS = (__force iwl_ucode_tlv_capa_t)48, + IWL_UCODE_TLV_CAPA_CS_MODIFY = (__force iwl_ucode_tlv_capa_t)49, + IWL_UCODE_TLV_CAPA_SET_LTR_GEN2 = (__force iwl_ucode_tlv_capa_t)50, + IWL_UCODE_TLV_CAPA_SET_PPAG = (__force iwl_ucode_tlv_capa_t)52, + IWL_UCODE_TLV_CAPA_TAS_CFG = (__force iwl_ucode_tlv_capa_t)53, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD = (__force iwl_ucode_tlv_capa_t)54, + IWL_UCODE_TLV_CAPA_PROTECTED_TWT = (__force iwl_ucode_tlv_capa_t)56, + IWL_UCODE_TLV_CAPA_FW_RESET_HANDSHAKE = (__force iwl_ucode_tlv_capa_t)57, + IWL_UCODE_TLV_CAPA_PASSIVE_6GHZ_SCAN = (__force iwl_ucode_tlv_capa_t)58, + IWL_UCODE_TLV_CAPA_HIDDEN_6GHZ_SCAN = (__force iwl_ucode_tlv_capa_t)59, + IWL_UCODE_TLV_CAPA_BROADCAST_TWT = (__force iwl_ucode_tlv_capa_t)60, + IWL_UCODE_TLV_CAPA_COEX_HIGH_PRIO = (__force iwl_ucode_tlv_capa_t)61, + IWL_UCODE_TLV_CAPA_RFIM_SUPPORT = (__force iwl_ucode_tlv_capa_t)62, + + /* set 2 */ + IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE = (__force iwl_ucode_tlv_capa_t)64, + IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS = (__force iwl_ucode_tlv_capa_t)65, + IWL_UCODE_TLV_CAPA_BT_MPLUT_SUPPORT = (__force iwl_ucode_tlv_capa_t)67, + IWL_UCODE_TLV_CAPA_MULTI_QUEUE_RX_SUPPORT = (__force iwl_ucode_tlv_capa_t)68, + IWL_UCODE_TLV_CAPA_CSA_AND_TBTT_OFFLOAD = (__force iwl_ucode_tlv_capa_t)70, + IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION = (__force iwl_ucode_tlv_capa_t)71, + IWL_UCODE_TLV_CAPA_BEACON_STORING = (__force iwl_ucode_tlv_capa_t)72, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT_V3 = (__force iwl_ucode_tlv_capa_t)73, + IWL_UCODE_TLV_CAPA_CT_KILL_BY_FW = (__force iwl_ucode_tlv_capa_t)74, + IWL_UCODE_TLV_CAPA_TEMP_THS_REPORT_SUPPORT = (__force iwl_ucode_tlv_capa_t)75, + IWL_UCODE_TLV_CAPA_CTDP_SUPPORT = (__force iwl_ucode_tlv_capa_t)76, + IWL_UCODE_TLV_CAPA_USNIFFER_UNIFIED = (__force iwl_ucode_tlv_capa_t)77, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG = (__force iwl_ucode_tlv_capa_t)80, + IWL_UCODE_TLV_CAPA_LQM_SUPPORT = (__force iwl_ucode_tlv_capa_t)81, + IWL_UCODE_TLV_CAPA_TX_POWER_ACK = (__force iwl_ucode_tlv_capa_t)84, + IWL_UCODE_TLV_CAPA_D3_DEBUG = (__force iwl_ucode_tlv_capa_t)87, + IWL_UCODE_TLV_CAPA_LED_CMD_SUPPORT = (__force iwl_ucode_tlv_capa_t)88, + IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT = (__force iwl_ucode_tlv_capa_t)89, + IWL_UCODE_TLV_CAPA_CSI_REPORTING = (__force iwl_ucode_tlv_capa_t)90, + IWL_UCODE_TLV_CAPA_DBG_SUSPEND_RESUME_CMD_SUPP = (__force iwl_ucode_tlv_capa_t)92, + IWL_UCODE_TLV_CAPA_DBG_BUF_ALLOC_CMD_SUPP = (__force iwl_ucode_tlv_capa_t)93, + + /* set 3 */ + IWL_UCODE_TLV_CAPA_MLME_OFFLOAD = (__force iwl_ucode_tlv_capa_t)96, + + /* + * @IWL_UCODE_TLV_CAPA_PSC_CHAN_SUPPORT: supports PSC channels + */ + IWL_UCODE_TLV_CAPA_PSC_CHAN_SUPPORT = (__force iwl_ucode_tlv_capa_t)98, + + IWL_UCODE_TLV_CAPA_BIGTK_SUPPORT = (__force iwl_ucode_tlv_capa_t)100, + IWL_UCODE_TLV_CAPA_DRAM_FRAG_SUPPORT = (__force iwl_ucode_tlv_capa_t)104, + +#ifdef __CHECKER__ + /* sparse says it cannot increment the previous enum member */ +#define NUM_IWL_UCODE_TLV_CAPA 128 +#else + NUM_IWL_UCODE_TLV_CAPA +#endif +}; + +/* The default calibrate table size if not specified by firmware file */ +#define IWL_DEFAULT_STANDARD_PHY_CALIBRATE_TBL_SIZE 18 +#define IWL_MAX_STANDARD_PHY_CALIBRATE_TBL_SIZE 19 +#define IWL_MAX_PHY_CALIBRATE_TBL_SIZE 253 + +/* The default max probe length if not specified by the firmware file */ +#define IWL_DEFAULT_MAX_PROBE_LENGTH 200 + +/* + * For 16.0 uCode and above, there is no differentiation between sections, + * just an offset to the HW address. + */ +#define CPU1_CPU2_SEPARATOR_SECTION 0xFFFFCCCC +#define PAGING_SEPARATOR_SECTION 0xAAAABBBB + +/* uCode version contains 4 values: Major/Minor/API/Serial */ +#define IWL_UCODE_MAJOR(ver) (((ver) & 0xFF000000) >> 24) +#define IWL_UCODE_MINOR(ver) (((ver) & 0x00FF0000) >> 16) +#define IWL_UCODE_API(ver) (((ver) & 0x0000FF00) >> 8) +#define IWL_UCODE_SERIAL(ver) ((ver) & 0x000000FF) + +/** + * struct iwl_tlv_calib_ctrl - Calibration control struct. + * Sent as part of the phy configuration command. + * @flow_trigger: bitmap for which calibrations to perform according to + * flow triggers. + * @event_trigger: bitmap for which calibrations to perform according to + * event triggers. + */ +struct iwl_tlv_calib_ctrl { + __le32 flow_trigger; + __le32 event_trigger; +} __packed; + +enum iwl_fw_phy_cfg { + FW_PHY_CFG_RADIO_TYPE_POS = 0, + FW_PHY_CFG_RADIO_TYPE = 0x3 << FW_PHY_CFG_RADIO_TYPE_POS, + FW_PHY_CFG_RADIO_STEP_POS = 2, + FW_PHY_CFG_RADIO_STEP = 0x3 << FW_PHY_CFG_RADIO_STEP_POS, + FW_PHY_CFG_RADIO_DASH_POS = 4, + FW_PHY_CFG_RADIO_DASH = 0x3 << FW_PHY_CFG_RADIO_DASH_POS, + FW_PHY_CFG_TX_CHAIN_POS = 16, + FW_PHY_CFG_TX_CHAIN = 0xf << FW_PHY_CFG_TX_CHAIN_POS, + FW_PHY_CFG_RX_CHAIN_POS = 20, + FW_PHY_CFG_RX_CHAIN = 0xf << FW_PHY_CFG_RX_CHAIN_POS, + FW_PHY_CFG_CHAIN_SAD_POS = 23, + FW_PHY_CFG_CHAIN_SAD_ENABLED = 0x1 << FW_PHY_CFG_CHAIN_SAD_POS, + FW_PHY_CFG_CHAIN_SAD_ANT_A = 0x2 << FW_PHY_CFG_CHAIN_SAD_POS, + FW_PHY_CFG_CHAIN_SAD_ANT_B = 0x4 << FW_PHY_CFG_CHAIN_SAD_POS, + FW_PHY_CFG_SHARED_CLK = BIT(31), +}; + +#define IWL_UCODE_MAX_CS 1 + +/** + * struct iwl_fw_cipher_scheme - a cipher scheme supported by FW. + * @cipher: a cipher suite selector + * @flags: cipher scheme flags (currently reserved for a future use) + * @hdr_len: a size of MPDU security header + * @pn_len: a size of PN + * @pn_off: an offset of pn from the beginning of the security header + * @key_idx_off: an offset of key index byte in the security header + * @key_idx_mask: a bit mask of key_idx bits + * @key_idx_shift: bit shift needed to get key_idx + * @mic_len: mic length in bytes + * @hw_cipher: a HW cipher index used in host commands + */ +struct iwl_fw_cipher_scheme { + __le32 cipher; + u8 flags; + u8 hdr_len; + u8 pn_len; + u8 pn_off; + u8 key_idx_off; + u8 key_idx_mask; + u8 key_idx_shift; + u8 mic_len; + u8 hw_cipher; +} __packed; + +enum iwl_fw_dbg_reg_operator { + CSR_ASSIGN, + CSR_SETBIT, + CSR_CLEARBIT, + + PRPH_ASSIGN, + PRPH_SETBIT, + PRPH_CLEARBIT, + + INDIRECT_ASSIGN, + INDIRECT_SETBIT, + INDIRECT_CLEARBIT, + + PRPH_BLOCKBIT, +}; + +/** + * struct iwl_fw_dbg_reg_op - an operation on a register + * + * @op: &enum iwl_fw_dbg_reg_operator + * @addr: offset of the register + * @val: value + */ +struct iwl_fw_dbg_reg_op { + u8 op; + u8 reserved[3]; + __le32 addr; + __le32 val; +} __packed; + +/** + * enum iwl_fw_dbg_monitor_mode - available monitor recording modes + * + * @SMEM_MODE: monitor stores the data in SMEM + * @EXTERNAL_MODE: monitor stores the data in allocated DRAM + * @MARBH_MODE: monitor stores the data in MARBH buffer + * @MIPI_MODE: monitor outputs the data through the MIPI interface + */ +enum iwl_fw_dbg_monitor_mode { + SMEM_MODE = 0, + EXTERNAL_MODE = 1, + MARBH_MODE = 2, + MIPI_MODE = 3, +}; + +/** + * struct iwl_fw_dbg_mem_seg_tlv - configures the debug data memory segments + * + * @data_type: the memory segment type to record + * @ofs: the memory segment offset + * @len: the memory segment length, in bytes + * + * This parses IWL_UCODE_TLV_FW_MEM_SEG + */ +struct iwl_fw_dbg_mem_seg_tlv { + __le32 data_type; + __le32 ofs; + __le32 len; +} __packed; + +/** + * struct iwl_fw_dbg_dest_tlv_v1 - configures the destination of the debug data + * + * @version: version of the TLV - currently 0 + * @monitor_mode: &enum iwl_fw_dbg_monitor_mode + * @size_power: buffer size will be 2^(size_power + 11) + * @base_reg: addr of the base addr register (PRPH) + * @end_reg: addr of the end addr register (PRPH) + * @write_ptr_reg: the addr of the reg of the write pointer + * @wrap_count: the addr of the reg of the wrap_count + * @base_shift: shift right of the base addr reg + * @end_shift: shift right of the end addr reg + * @reg_ops: array of registers operations + * + * This parses IWL_UCODE_TLV_FW_DBG_DEST + */ +struct iwl_fw_dbg_dest_tlv_v1 { + u8 version; + u8 monitor_mode; + u8 size_power; + u8 reserved; + __le32 base_reg; + __le32 end_reg; + __le32 write_ptr_reg; + __le32 wrap_count; + u8 base_shift; + u8 end_shift; + struct iwl_fw_dbg_reg_op reg_ops[0]; +} __packed; + +/* Mask of the register for defining the LDBG MAC2SMEM buffer SMEM size */ +#define IWL_LDBG_M2S_BUF_SIZE_MSK 0x0fff0000 +/* Mask of the register for defining the LDBG MAC2SMEM SMEM base address */ +#define IWL_LDBG_M2S_BUF_BA_MSK 0x00000fff +/* The smem buffer chunks are in units of 256 bits */ +#define IWL_M2S_UNIT_SIZE 0x100 + +struct iwl_fw_dbg_dest_tlv { + u8 version; + u8 monitor_mode; + u8 size_power; + u8 reserved; + __le32 cfg_reg; + __le32 write_ptr_reg; + __le32 wrap_count; + u8 base_shift; + u8 size_shift; + struct iwl_fw_dbg_reg_op reg_ops[0]; +} __packed; + +struct iwl_fw_dbg_conf_hcmd { + u8 id; + u8 reserved; + __le16 len; + u8 data[0]; +} __packed; + +/** + * enum iwl_fw_dbg_trigger_mode - triggers functionalities + * + * @IWL_FW_DBG_TRIGGER_START: when trigger occurs re-conf the dbg mechanism + * @IWL_FW_DBG_TRIGGER_STOP: when trigger occurs pull the dbg data + * @IWL_FW_DBG_TRIGGER_MONITOR_ONLY: when trigger occurs trigger is set to + * collect only monitor data + */ +enum iwl_fw_dbg_trigger_mode { + IWL_FW_DBG_TRIGGER_START = BIT(0), + IWL_FW_DBG_TRIGGER_STOP = BIT(1), + IWL_FW_DBG_TRIGGER_MONITOR_ONLY = BIT(2), +}; + +/** + * enum iwl_fw_dbg_trigger_flags - the flags supported by wrt triggers + * @IWL_FW_DBG_FORCE_RESTART: force a firmware restart + */ +enum iwl_fw_dbg_trigger_flags { + IWL_FW_DBG_FORCE_RESTART = BIT(0), +}; + +/** + * enum iwl_fw_dbg_trigger_vif_type - define the VIF type for a trigger + * @IWL_FW_DBG_CONF_VIF_ANY: any vif type + * @IWL_FW_DBG_CONF_VIF_IBSS: IBSS mode + * @IWL_FW_DBG_CONF_VIF_STATION: BSS mode + * @IWL_FW_DBG_CONF_VIF_AP: AP mode + * @IWL_FW_DBG_CONF_VIF_P2P_CLIENT: P2P Client mode + * @IWL_FW_DBG_CONF_VIF_P2P_GO: P2P GO mode + * @IWL_FW_DBG_CONF_VIF_P2P_DEVICE: P2P device + */ +enum iwl_fw_dbg_trigger_vif_type { + IWL_FW_DBG_CONF_VIF_ANY = NL80211_IFTYPE_UNSPECIFIED, + IWL_FW_DBG_CONF_VIF_IBSS = NL80211_IFTYPE_ADHOC, + IWL_FW_DBG_CONF_VIF_STATION = NL80211_IFTYPE_STATION, + IWL_FW_DBG_CONF_VIF_AP = NL80211_IFTYPE_AP, + IWL_FW_DBG_CONF_VIF_P2P_CLIENT = NL80211_IFTYPE_P2P_CLIENT, + IWL_FW_DBG_CONF_VIF_P2P_GO = NL80211_IFTYPE_P2P_GO, + IWL_FW_DBG_CONF_VIF_P2P_DEVICE = NL80211_IFTYPE_P2P_DEVICE, +}; + +/** + * struct iwl_fw_dbg_trigger_tlv - a TLV that describes the trigger + * @id: &enum iwl_fw_dbg_trigger + * @vif_type: &enum iwl_fw_dbg_trigger_vif_type + * @stop_conf_ids: bitmap of configurations this trigger relates to. + * if the mode is %IWL_FW_DBG_TRIGGER_STOP, then if the bit corresponding + * to the currently running configuration is set, the data should be + * collected. + * @stop_delay: how many milliseconds to wait before collecting the data + * after the STOP trigger fires. + * @mode: &enum iwl_fw_dbg_trigger_mode - can be stop / start of both + * @start_conf_id: if mode is %IWL_FW_DBG_TRIGGER_START, this defines what + * configuration should be applied when the triggers kicks in. + * @occurrences: number of occurrences. 0 means the trigger will never fire. + * @trig_dis_ms: the time, in milliseconds, after an occurrence of this + * trigger in which another occurrence should be ignored. + * @flags: &enum iwl_fw_dbg_trigger_flags + */ +struct iwl_fw_dbg_trigger_tlv { + __le32 id; + __le32 vif_type; + __le32 stop_conf_ids; + __le32 stop_delay; + u8 mode; + u8 start_conf_id; + __le16 occurrences; + __le16 trig_dis_ms; + u8 flags; + u8 reserved[5]; + + u8 data[0]; +} __packed; + +#define FW_DBG_START_FROM_ALIVE 0 +#define FW_DBG_CONF_MAX 32 +#define FW_DBG_INVALID 0xff + +/** + * struct iwl_fw_dbg_trigger_missed_bcon - configures trigger for missed beacons + * @stop_consec_missed_bcon: stop recording if threshold is crossed. + * @stop_consec_missed_bcon_since_rx: stop recording if threshold is crossed. + * @start_consec_missed_bcon: start recording if threshold is crossed. + * @start_consec_missed_bcon_since_rx: start recording if threshold is crossed. + * @reserved1: reserved + * @reserved2: reserved + */ +struct iwl_fw_dbg_trigger_missed_bcon { + __le32 stop_consec_missed_bcon; + __le32 stop_consec_missed_bcon_since_rx; + __le32 reserved2[2]; + __le32 start_consec_missed_bcon; + __le32 start_consec_missed_bcon_since_rx; + __le32 reserved1[2]; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_cmd - configures trigger for messages from FW. + * cmds: the list of commands to trigger the collection on + */ +struct iwl_fw_dbg_trigger_cmd { + struct cmd { + u8 cmd_id; + u8 group_id; + } __packed cmds[16]; +} __packed; + +/** + * iwl_fw_dbg_trigger_stats - configures trigger for statistics + * @stop_offset: the offset of the value to be monitored + * @stop_threshold: the threshold above which to collect + * @start_offset: the offset of the value to be monitored + * @start_threshold: the threshold above which to start recording + */ +struct iwl_fw_dbg_trigger_stats { + __le32 stop_offset; + __le32 stop_threshold; + __le32 start_offset; + __le32 start_threshold; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_low_rssi - trigger for low beacon RSSI + * @rssi: RSSI value to trigger at + */ +struct iwl_fw_dbg_trigger_low_rssi { + __le32 rssi; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_mlme - configures trigger for mlme events + * @stop_auth_denied: number of denied authentication to collect + * @stop_auth_timeout: number of authentication timeout to collect + * @stop_rx_deauth: number of Rx deauth before to collect + * @stop_tx_deauth: number of Tx deauth before to collect + * @stop_assoc_denied: number of denied association to collect + * @stop_assoc_timeout: number of association timeout to collect + * @stop_connection_loss: number of connection loss to collect + * @start_auth_denied: number of denied authentication to start recording + * @start_auth_timeout: number of authentication timeout to start recording + * @start_rx_deauth: number of Rx deauth to start recording + * @start_tx_deauth: number of Tx deauth to start recording + * @start_assoc_denied: number of denied association to start recording + * @start_assoc_timeout: number of association timeout to start recording + * @start_connection_loss: number of connection loss to start recording + */ +struct iwl_fw_dbg_trigger_mlme { + u8 stop_auth_denied; + u8 stop_auth_timeout; + u8 stop_rx_deauth; + u8 stop_tx_deauth; + + u8 stop_assoc_denied; + u8 stop_assoc_timeout; + u8 stop_connection_loss; + u8 reserved; + + u8 start_auth_denied; + u8 start_auth_timeout; + u8 start_rx_deauth; + u8 start_tx_deauth; + + u8 start_assoc_denied; + u8 start_assoc_timeout; + u8 start_connection_loss; + u8 reserved2; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_txq_timer - configures the Tx queue's timer + * @command_queue: timeout for the command queue in ms + * @bss: timeout for the queues of a BSS (except for TDLS queues) in ms + * @softap: timeout for the queues of a softAP in ms + * @p2p_go: timeout for the queues of a P2P GO in ms + * @p2p_client: timeout for the queues of a P2P client in ms + * @p2p_device: timeout for the queues of a P2P device in ms + * @ibss: timeout for the queues of an IBSS in ms + * @tdls: timeout for the queues of a TDLS station in ms + */ +struct iwl_fw_dbg_trigger_txq_timer { + __le32 command_queue; + __le32 bss; + __le32 softap; + __le32 p2p_go; + __le32 p2p_client; + __le32 p2p_device; + __le32 ibss; + __le32 tdls; + __le32 reserved[4]; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_time_event - configures a time event trigger + * time_Events: a list of tuples . The driver will issue a + * trigger each time a time event notification that relates to time event + * id with one of the actions in the bitmap is received and + * BIT(notif->status) is set in status_bitmap. + * + */ +struct iwl_fw_dbg_trigger_time_event { + struct { + __le32 id; + __le32 action_bitmap; + __le32 status_bitmap; + } __packed time_events[16]; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_ba - configures BlockAck related trigger + * rx_ba_start: tid bitmap to configure on what tid the trigger should occur + * when an Rx BlockAck session is started. + * rx_ba_stop: tid bitmap to configure on what tid the trigger should occur + * when an Rx BlockAck session is stopped. + * tx_ba_start: tid bitmap to configure on what tid the trigger should occur + * when a Tx BlockAck session is started. + * tx_ba_stop: tid bitmap to configure on what tid the trigger should occur + * when a Tx BlockAck session is stopped. + * rx_bar: tid bitmap to configure on what tid the trigger should occur + * when a BAR is received (for a Tx BlockAck session). + * tx_bar: tid bitmap to configure on what tid the trigger should occur + * when a BAR is send (for an Rx BlocAck session). + * frame_timeout: tid bitmap to configure on what tid the trigger should occur + * when a frame times out in the reordering buffer. + */ +struct iwl_fw_dbg_trigger_ba { + __le16 rx_ba_start; + __le16 rx_ba_stop; + __le16 tx_ba_start; + __le16 tx_ba_stop; + __le16 rx_bar; + __le16 tx_bar; + __le16 frame_timeout; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_tdls - configures trigger for TDLS events. + * @action_bitmap: the TDLS action to trigger the collection upon + * @peer_mode: trigger on specific peer or all + * @peer: the TDLS peer to trigger the collection on + */ +struct iwl_fw_dbg_trigger_tdls { + u8 action_bitmap; + u8 peer_mode; + u8 peer[ETH_ALEN]; + u8 reserved[4]; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_tx_status - configures trigger for tx response + * status. + * @statuses: the list of statuses to trigger the collection on + */ +struct iwl_fw_dbg_trigger_tx_status { + struct tx_status { + u8 status; + u8 reserved[3]; + } __packed statuses[16]; + __le32 reserved[2]; +} __packed; + +/** + * struct iwl_fw_dbg_conf_tlv - a TLV that describes a debug configuration. + * @id: conf id + * @usniffer: should the uSniffer image be used + * @num_of_hcmds: how many HCMDs to send are present here + * @hcmd: a variable length host command to be sent to apply the configuration. + * If there is more than one HCMD to send, they will appear one after the + * other and be sent in the order that they appear in. + * This parses IWL_UCODE_TLV_FW_DBG_CONF. The user can add up-to + * %FW_DBG_CONF_MAX configuration per run. + */ +struct iwl_fw_dbg_conf_tlv { + u8 id; + u8 usniffer; + u8 reserved; + u8 num_of_hcmds; + struct iwl_fw_dbg_conf_hcmd hcmd; +} __packed; + +#define IWL_FW_CMD_VER_UNKNOWN 99 + +/** + * struct iwl_fw_cmd_version - firmware command version entry + * @cmd: command ID + * @group: group ID + * @cmd_ver: command version + * @notif_ver: notification version + */ +struct iwl_fw_cmd_version { + u8 cmd; + u8 group; + u8 cmd_ver; + u8 notif_ver; +} __packed; + +struct iwl_fw_tcm_error_addr { + __le32 addr; +}; /* FW_TLV_TCM_ERROR_INFO_ADDRS_S */ + +struct iwl_fw_dump_exclude { + __le32 addr, size; +}; + +static inline size_t _iwl_tlv_array_len(const struct iwl_ucode_tlv *tlv, + size_t fixed_size, size_t var_size) +{ + size_t var_len = le32_to_cpu(tlv->length) - fixed_size; + + if (WARN_ON(var_len % var_size)) + return 0; + + return var_len / var_size; +} + +#define iwl_tlv_array_len(_tlv_ptr, _struct_ptr, _memb) \ + _iwl_tlv_array_len((_tlv_ptr), sizeof(*(_struct_ptr)), \ + sizeof(_struct_ptr->_memb[0])) + +#endif /* __iwl_fw_file_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/img.c b/sys/contrib/dev/iwlwifi/fw/img.c new file mode 100644 index 00000000000..24a96667369 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/img.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright(c) 2019 - 2020 Intel Corporation + */ + +#include "img.h" + +u8 iwl_fw_lookup_cmd_ver(const struct iwl_fw *fw, u8 grp, u8 cmd, u8 def) +{ + const struct iwl_fw_cmd_version *entry; + unsigned int i; + + if (!fw->ucode_capa.cmd_versions || + !fw->ucode_capa.n_cmd_versions) + return def; + + entry = fw->ucode_capa.cmd_versions; + for (i = 0; i < fw->ucode_capa.n_cmd_versions; i++, entry++) { + if (entry->group == grp && entry->cmd == cmd) { + if (entry->cmd_ver == IWL_FW_CMD_VER_UNKNOWN) + return def; + return entry->cmd_ver; + } + } + + return def; +} +EXPORT_SYMBOL_GPL(iwl_fw_lookup_cmd_ver); + +u8 iwl_fw_lookup_notif_ver(const struct iwl_fw *fw, u8 grp, u8 cmd, u8 def) +{ + const struct iwl_fw_cmd_version *entry; + unsigned int i; + + if (!fw->ucode_capa.cmd_versions || + !fw->ucode_capa.n_cmd_versions) + return def; + + entry = fw->ucode_capa.cmd_versions; + for (i = 0; i < fw->ucode_capa.n_cmd_versions; i++, entry++) { + if (entry->group == grp && entry->cmd == cmd) { + if (entry->notif_ver == IWL_FW_CMD_VER_UNKNOWN) + return def; + return entry->notif_ver; + } + } + + return def; +} +EXPORT_SYMBOL_GPL(iwl_fw_lookup_notif_ver); + +#define FW_SYSASSERT_CPU_MASK 0xf0000000 +static const struct { + const char *name; + u8 num; +} advanced_lookup[] = { + { "NMI_INTERRUPT_WDG", 0x34 }, + { "SYSASSERT", 0x35 }, + { "UCODE_VERSION_MISMATCH", 0x37 }, + { "BAD_COMMAND", 0x38 }, + { "BAD_COMMAND", 0x39 }, + { "NMI_INTERRUPT_DATA_ACTION_PT", 0x3C }, + { "FATAL_ERROR", 0x3D }, + { "NMI_TRM_HW_ERR", 0x46 }, + { "NMI_INTERRUPT_TRM", 0x4C }, + { "NMI_INTERRUPT_BREAK_POINT", 0x54 }, + { "NMI_INTERRUPT_WDG_RXF_FULL", 0x5C }, + { "NMI_INTERRUPT_WDG_NO_RBD_RXF_FULL", 0x64 }, + { "NMI_INTERRUPT_HOST", 0x66 }, + { "NMI_INTERRUPT_LMAC_FATAL", 0x70 }, + { "NMI_INTERRUPT_UMAC_FATAL", 0x71 }, + { "NMI_INTERRUPT_OTHER_LMAC_FATAL", 0x73 }, + { "NMI_INTERRUPT_ACTION_PT", 0x7C }, + { "NMI_INTERRUPT_UNKNOWN", 0x84 }, + { "NMI_INTERRUPT_INST_ACTION_PT", 0x86 }, + { "ADVANCED_SYSASSERT", 0 }, +}; + +const char *iwl_fw_lookup_assert_desc(u32 num) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(advanced_lookup) - 1; i++) + if (advanced_lookup[i].num == (num & ~FW_SYSASSERT_CPU_MASK)) + return advanced_lookup[i].name; + + /* No entry matches 'num', so it is the last: ADVANCED_SYSASSERT */ + return advanced_lookup[i].name; +} +EXPORT_SYMBOL_GPL(iwl_fw_lookup_assert_desc); diff --git a/sys/contrib/dev/iwlwifi/fw/img.h b/sys/contrib/dev/iwlwifi/fw/img.h new file mode 100644 index 00000000000..993bda17fa3 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/img.h @@ -0,0 +1,282 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_img_h__ +#define __iwl_fw_img_h__ +#include + +#include "api/dbg-tlv.h" + +#include "file.h" +#include "error-dump.h" + +/** + * enum iwl_ucode_type + * + * The type of ucode. + * + * @IWL_UCODE_REGULAR: Normal runtime ucode + * @IWL_UCODE_INIT: Initial ucode + * @IWL_UCODE_WOWLAN: Wake on Wireless enabled ucode + * @IWL_UCODE_REGULAR_USNIFFER: Normal runtime ucode when using usniffer image + */ +enum iwl_ucode_type { + IWL_UCODE_REGULAR, + IWL_UCODE_INIT, + IWL_UCODE_WOWLAN, + IWL_UCODE_REGULAR_USNIFFER, + IWL_UCODE_TYPE_MAX, +}; + +/* + * enumeration of ucode section. + * This enumeration is used directly for older firmware (before 16.0). + * For new firmware, there can be up to 4 sections (see below) but the + * first one packaged into the firmware file is the DATA section and + * some debugging code accesses that. + */ +enum iwl_ucode_sec { + IWL_UCODE_SECTION_DATA, + IWL_UCODE_SECTION_INST, +}; + +struct iwl_ucode_capabilities { + u32 max_probe_length; + u32 n_scan_channels; + u32 standard_phy_calibration_size; + u32 flags; + u32 error_log_addr; + u32 error_log_size; + u32 num_stations; + unsigned long _api[BITS_TO_LONGS(NUM_IWL_UCODE_TLV_API)]; + unsigned long _capa[BITS_TO_LONGS(NUM_IWL_UCODE_TLV_CAPA)]; + + const struct iwl_fw_cmd_version *cmd_versions; + u32 n_cmd_versions; +}; + +static inline bool +fw_has_api(const struct iwl_ucode_capabilities *capabilities, + iwl_ucode_tlv_api_t api) +{ + return test_bit((__force long)api, capabilities->_api); +} + +static inline bool +fw_has_capa(const struct iwl_ucode_capabilities *capabilities, + iwl_ucode_tlv_capa_t capa) +{ + return test_bit((__force long)capa, capabilities->_capa); +} + +/* one for each uCode image (inst/data, init/runtime/wowlan) */ +struct fw_desc { + const void *data; /* vmalloc'ed data */ + u32 len; /* size in bytes */ + u32 offset; /* offset in the device */ +}; + +struct fw_img { + struct fw_desc *sec; + int num_sec; + bool is_dual_cpus; + u32 paging_mem_size; +}; + +/* + * Block paging calculations + */ +#define PAGE_2_EXP_SIZE 12 /* 4K == 2^12 */ +#define FW_PAGING_SIZE BIT(PAGE_2_EXP_SIZE) /* page size is 4KB */ +#define PAGE_PER_GROUP_2_EXP_SIZE 3 +/* 8 pages per group */ +#define NUM_OF_PAGE_PER_GROUP BIT(PAGE_PER_GROUP_2_EXP_SIZE) +/* don't change, support only 32KB size */ +#define PAGING_BLOCK_SIZE (NUM_OF_PAGE_PER_GROUP * FW_PAGING_SIZE) +/* 32K == 2^15 */ +#define BLOCK_2_EXP_SIZE (PAGE_2_EXP_SIZE + PAGE_PER_GROUP_2_EXP_SIZE) + +/* + * Image paging calculations + */ +#define BLOCK_PER_IMAGE_2_EXP_SIZE 5 +/* 2^5 == 32 blocks per image */ +#define NUM_OF_BLOCK_PER_IMAGE BIT(BLOCK_PER_IMAGE_2_EXP_SIZE) +/* maximum image size 1024KB */ +#define MAX_PAGING_IMAGE_SIZE (NUM_OF_BLOCK_PER_IMAGE * PAGING_BLOCK_SIZE) + +/* Virtual address signature */ +#define PAGING_ADDR_SIG 0xAA000000 + +#define PAGING_CMD_IS_SECURED BIT(9) +#define PAGING_CMD_IS_ENABLED BIT(8) +#define PAGING_CMD_NUM_OF_PAGES_IN_LAST_GRP_POS 0 +#define PAGING_TLV_SECURE_MASK 1 + +/* FW MSB Mask for regions/cache_control */ +#define FW_ADDR_CACHE_CONTROL 0xC0000000UL + +/** + * struct iwl_fw_paging + * @fw_paging_phys: page phy pointer + * @fw_paging_block: pointer to the allocated block + * @fw_paging_size: page size + * @fw_offs: offset in the device + */ +struct iwl_fw_paging { + dma_addr_t fw_paging_phys; + struct page *fw_paging_block; + u32 fw_paging_size; + u32 fw_offs; +}; + +/** + * struct iwl_fw_cscheme_list - a cipher scheme list + * @size: a number of entries + * @cs: cipher scheme entries + */ +struct iwl_fw_cscheme_list { + u8 size; + struct iwl_fw_cipher_scheme cs[]; +} __packed; + +/** + * enum iwl_fw_type - iwlwifi firmware type + * @IWL_FW_DVM: DVM firmware + * @IWL_FW_MVM: MVM firmware + */ +enum iwl_fw_type { + IWL_FW_DVM, + IWL_FW_MVM, +}; + +/** + * struct iwl_fw_dbg - debug data + * + * @dest_tlv: points to debug destination TLV (typically SRAM or DRAM) + * @n_dest_reg: num of reg_ops in dest_tlv + * @conf_tlv: array of pointers to configuration HCMDs + * @trigger_tlv: array of pointers to triggers TLVs + * @trigger_tlv_len: lengths of the @dbg_trigger_tlv entries + * @mem_tlv: Runtime addresses to dump + * @n_mem_tlv: number of runtime addresses + * @dump_mask: bitmask of dump regions +*/ +struct iwl_fw_dbg { + struct iwl_fw_dbg_dest_tlv_v1 *dest_tlv; + u8 n_dest_reg; + struct iwl_fw_dbg_conf_tlv *conf_tlv[FW_DBG_CONF_MAX]; + struct iwl_fw_dbg_trigger_tlv *trigger_tlv[FW_DBG_TRIGGER_MAX]; + size_t trigger_tlv_len[FW_DBG_TRIGGER_MAX]; + struct iwl_fw_dbg_mem_seg_tlv *mem_tlv; + size_t n_mem_tlv; + u32 dump_mask; +}; + +struct iwl_dump_exclude { + u32 addr, size; +}; + +/** + * struct iwl_fw - variables associated with the firmware + * + * @ucode_ver: ucode version from the ucode file + * @fw_version: firmware version string + * @img: ucode image like ucode_rt, ucode_init, ucode_wowlan. + * @iml_len: length of the image loader image + * @iml: image loader fw image + * @ucode_capa: capabilities parsed from the ucode file. + * @enhance_sensitivity_table: device can do enhanced sensitivity. + * @init_evtlog_ptr: event log offset for init ucode. + * @init_evtlog_size: event log size for init ucode. + * @init_errlog_ptr: error log offfset for init ucode. + * @inst_evtlog_ptr: event log offset for runtime ucode. + * @inst_evtlog_size: event log size for runtime ucode. + * @inst_errlog_ptr: error log offfset for runtime ucode. + * @type: firmware type (&enum iwl_fw_type) + * @cipher_scheme: optional external cipher scheme. + * @human_readable: human readable version + * we get the ALIVE from the uCode + * @phy_integration_ver: PHY integration version string + * @phy_integration_ver_len: length of @phy_integration_ver + * @dump_excl: image dump exclusion areas for RT image + * @dump_excl_wowlan: image dump exclusion areas for WoWLAN image + */ +struct iwl_fw { + u32 ucode_ver; + + char fw_version[64]; + + /* ucode images */ + struct fw_img img[IWL_UCODE_TYPE_MAX]; + size_t iml_len; + u8 *iml; + + struct iwl_ucode_capabilities ucode_capa; + bool enhance_sensitivity_table; + + u32 init_evtlog_ptr, init_evtlog_size, init_errlog_ptr; + u32 inst_evtlog_ptr, inst_evtlog_size, inst_errlog_ptr; + + struct iwl_tlv_calib_ctrl default_calib[IWL_UCODE_TYPE_MAX]; + u32 phy_config; + u8 valid_tx_ant; + u8 valid_rx_ant; + + enum iwl_fw_type type; + + struct iwl_fw_cipher_scheme cs[IWL_UCODE_MAX_CS]; + u8 human_readable[FW_VER_HUMAN_READABLE_SZ]; + + struct iwl_fw_dbg dbg; + + u8 *phy_integration_ver; + u32 phy_integration_ver_len; + + struct iwl_dump_exclude dump_excl[2], dump_excl_wowlan[2]; +}; + +static inline const char *get_fw_dbg_mode_string(int mode) +{ + switch (mode) { + case SMEM_MODE: + return "SMEM"; + case EXTERNAL_MODE: + return "EXTERNAL_DRAM"; + case MARBH_MODE: + return "MARBH"; + case MIPI_MODE: + return "MIPI"; + default: + return "UNKNOWN"; + } +} + +static inline bool +iwl_fw_dbg_conf_usniffer(const struct iwl_fw *fw, u8 id) +{ + const struct iwl_fw_dbg_conf_tlv *conf_tlv = fw->dbg.conf_tlv[id]; + + if (!conf_tlv) + return false; + + return conf_tlv->usniffer; +} + +static inline const struct fw_img * +iwl_get_ucode_image(const struct iwl_fw *fw, enum iwl_ucode_type ucode_type) +{ + if (ucode_type >= IWL_UCODE_TYPE_MAX) + return NULL; + + return &fw->img[ucode_type]; +} + +u8 iwl_fw_lookup_cmd_ver(const struct iwl_fw *fw, u8 grp, u8 cmd, u8 def); + +u8 iwl_fw_lookup_notif_ver(const struct iwl_fw *fw, u8 grp, u8 cmd, u8 def); +const char *iwl_fw_lookup_assert_desc(u32 num); +#endif /* __iwl_fw_img_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/init.c b/sys/contrib/dev/iwlwifi/fw/init.c new file mode 100644 index 00000000000..566957ac453 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/init.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2019-2021 Intel Corporation + */ +#include "iwl-drv.h" +#include "runtime.h" +#include "dbg.h" +#include "debugfs.h" + +#include "fw/api/soc.h" +#include "fw/api/commands.h" +#include "fw/api/rx.h" +#include "fw/api/datapath.h" + +void iwl_fw_runtime_init(struct iwl_fw_runtime *fwrt, struct iwl_trans *trans, + const struct iwl_fw *fw, + const struct iwl_fw_runtime_ops *ops, void *ops_ctx, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx, + struct dentry *dbgfs_dir) +{ + int i; + + memset(fwrt, 0, sizeof(*fwrt)); + fwrt->trans = trans; + fwrt->fw = fw; + fwrt->dev = trans->dev; + fwrt->dump.conf = FW_DBG_INVALID; + fwrt->ops = ops; + fwrt->sanitize_ops = sanitize_ops; + fwrt->sanitize_ctx = sanitize_ctx; + fwrt->ops_ctx = ops_ctx; + for (i = 0; i < IWL_FW_RUNTIME_DUMP_WK_NUM; i++) { + fwrt->dump.wks[i].idx = i; + INIT_DELAYED_WORK(&fwrt->dump.wks[i].wk, iwl_fw_error_dump_wk); + } + iwl_fwrt_dbgfs_register(fwrt, dbgfs_dir); +} +IWL_EXPORT_SYMBOL(iwl_fw_runtime_init); + +void iwl_fw_runtime_suspend(struct iwl_fw_runtime *fwrt) +{ + iwl_fw_suspend_timestamp(fwrt); + iwl_dbg_tlv_time_point(fwrt, IWL_FW_INI_TIME_POINT_HOST_D3_START, NULL); +} +IWL_EXPORT_SYMBOL(iwl_fw_runtime_suspend); + +void iwl_fw_runtime_resume(struct iwl_fw_runtime *fwrt) +{ + iwl_dbg_tlv_time_point(fwrt, IWL_FW_INI_TIME_POINT_HOST_D3_END, NULL); + iwl_fw_resume_timestamp(fwrt); +} +IWL_EXPORT_SYMBOL(iwl_fw_runtime_resume); + +/* set device type and latency */ +int iwl_set_soc_latency(struct iwl_fw_runtime *fwrt) +{ + struct iwl_soc_configuration_cmd cmd = {}; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(SOC_CONFIGURATION_CMD, SYSTEM_GROUP, 0), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + int ret; + + /* + * In VER_1 of this command, the discrete value is considered + * an integer; In VER_2, it's a bitmask. Since we have only 2 + * values in VER_1, this is backwards-compatible with VER_2, + * as long as we don't set any other bits. + */ + if (!fwrt->trans->trans_cfg->integrated) + cmd.flags = cpu_to_le32(SOC_CONFIG_CMD_FLAGS_DISCRETE); + + BUILD_BUG_ON(IWL_CFG_TRANS_LTR_DELAY_NONE != + SOC_FLAGS_LTR_APPLY_DELAY_NONE); + BUILD_BUG_ON(IWL_CFG_TRANS_LTR_DELAY_200US != + SOC_FLAGS_LTR_APPLY_DELAY_200); + BUILD_BUG_ON(IWL_CFG_TRANS_LTR_DELAY_2500US != + SOC_FLAGS_LTR_APPLY_DELAY_2500); + BUILD_BUG_ON(IWL_CFG_TRANS_LTR_DELAY_1820US != + SOC_FLAGS_LTR_APPLY_DELAY_1820); + + if (fwrt->trans->trans_cfg->ltr_delay != IWL_CFG_TRANS_LTR_DELAY_NONE && + !WARN_ON(!fwrt->trans->trans_cfg->integrated)) + cmd.flags |= le32_encode_bits(fwrt->trans->trans_cfg->ltr_delay, + SOC_FLAGS_LTR_APPLY_DELAY_MASK); + + if (iwl_fw_lookup_cmd_ver(fwrt->fw, IWL_ALWAYS_LONG_GROUP, + SCAN_REQ_UMAC, + IWL_FW_CMD_VER_UNKNOWN) >= 2 && + fwrt->trans->trans_cfg->low_latency_xtal) + cmd.flags |= cpu_to_le32(SOC_CONFIG_CMD_FLAGS_LOW_LATENCY); + + cmd.latency = cpu_to_le32(fwrt->trans->trans_cfg->xtal_latency); + + ret = iwl_trans_send_cmd(fwrt->trans, &hcmd); + if (ret) + IWL_ERR(fwrt, "Failed to set soc latency: %d\n", ret); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_set_soc_latency); + +int iwl_configure_rxq(struct iwl_fw_runtime *fwrt) +{ + int i, num_queues, size, ret; + struct iwl_rfh_queue_config *cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DATA_PATH_GROUP, RFH_QUEUE_CONFIG_CMD), + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + + /* + * The default queue is configured via context info, so if we + * have a single queue, there's nothing to do here. + */ + if (fwrt->trans->num_rx_queues == 1) + return 0; + + if (fwrt->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_22000) + return 0; + + /* skip the default queue */ + num_queues = fwrt->trans->num_rx_queues - 1; + + size = struct_size(cmd, data, num_queues); + + cmd = kzalloc(size, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd->num_queues = num_queues; + + for (i = 0; i < num_queues; i++) { + struct iwl_trans_rxq_dma_data data; + + cmd->data[i].q_num = i + 1; + iwl_trans_get_rxq_dma_data(fwrt->trans, i + 1, &data); + + cmd->data[i].fr_bd_cb = cpu_to_le64(data.fr_bd_cb); + cmd->data[i].urbd_stts_wrptr = + cpu_to_le64(data.urbd_stts_wrptr); + cmd->data[i].ur_bd_cb = cpu_to_le64(data.ur_bd_cb); + cmd->data[i].fr_bd_wid = cpu_to_le32(data.fr_bd_wid); + } + + hcmd.data[0] = cmd; + hcmd.len[0] = size; + + ret = iwl_trans_send_cmd(fwrt->trans, &hcmd); + + kfree(cmd); + + if (ret) + IWL_ERR(fwrt, "Failed to configure RX queues: %d\n", ret); + + return ret; +} +IWL_EXPORT_SYMBOL(iwl_configure_rxq); diff --git a/sys/contrib/dev/iwlwifi/fw/notif-wait.c b/sys/contrib/dev/iwlwifi/fw/notif-wait.c new file mode 100644 index 00000000000..231d2517f39 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/notif-wait.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2021 Intel Corporation + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include +#include + +#include "iwl-drv.h" +#include "notif-wait.h" + + +void iwl_notification_wait_init(struct iwl_notif_wait_data *notif_wait) +{ + spin_lock_init(¬if_wait->notif_wait_lock); + INIT_LIST_HEAD(¬if_wait->notif_waits); + init_waitqueue_head(¬if_wait->notif_waitq); +} +IWL_EXPORT_SYMBOL(iwl_notification_wait_init); + +bool iwl_notification_wait(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt) +{ + bool triggered = false; + + if (!list_empty(¬if_wait->notif_waits)) { + struct iwl_notification_wait *w; + + spin_lock_bh(¬if_wait->notif_wait_lock); + list_for_each_entry(w, ¬if_wait->notif_waits, list) { + int i; + bool found = false; + + /* + * If it already finished (triggered) or has been + * aborted then don't evaluate it again to avoid races, + * Otherwise the function could be called again even + * though it returned true before + */ + if (w->triggered || w->aborted) + continue; + + for (i = 0; i < w->n_cmds; i++) { + u16 rec_id = WIDE_ID(pkt->hdr.group_id, + pkt->hdr.cmd); + + if (w->cmds[i] == rec_id || + (!iwl_cmd_groupid(w->cmds[i]) && + DEF_ID(w->cmds[i]) == rec_id)) { + found = true; + break; + } + } + if (!found) + continue; + + if (!w->fn || w->fn(notif_wait, pkt, w->fn_data)) { + w->triggered = true; + triggered = true; + } + } + spin_unlock_bh(¬if_wait->notif_wait_lock); + } + + return triggered; +} +IWL_EXPORT_SYMBOL(iwl_notification_wait); + +void iwl_abort_notification_waits(struct iwl_notif_wait_data *notif_wait) +{ + struct iwl_notification_wait *wait_entry; + + spin_lock_bh(¬if_wait->notif_wait_lock); + list_for_each_entry(wait_entry, ¬if_wait->notif_waits, list) + wait_entry->aborted = true; + spin_unlock_bh(¬if_wait->notif_wait_lock); + + wake_up_all(¬if_wait->notif_waitq); +} +IWL_EXPORT_SYMBOL(iwl_abort_notification_waits); + +void +iwl_init_notification_wait(struct iwl_notif_wait_data *notif_wait, + struct iwl_notification_wait *wait_entry, + const u16 *cmds, int n_cmds, + bool (*fn)(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data), + void *fn_data) +{ + if (WARN_ON(n_cmds > MAX_NOTIF_CMDS)) + n_cmds = MAX_NOTIF_CMDS; + + wait_entry->fn = fn; + wait_entry->fn_data = fn_data; + wait_entry->n_cmds = n_cmds; + memcpy(wait_entry->cmds, cmds, n_cmds * sizeof(u16)); + wait_entry->triggered = false; + wait_entry->aborted = false; + + spin_lock_bh(¬if_wait->notif_wait_lock); + list_add(&wait_entry->list, ¬if_wait->notif_waits); + spin_unlock_bh(¬if_wait->notif_wait_lock); +} +IWL_EXPORT_SYMBOL(iwl_init_notification_wait); + +void iwl_remove_notification(struct iwl_notif_wait_data *notif_wait, + struct iwl_notification_wait *wait_entry) +{ + spin_lock_bh(¬if_wait->notif_wait_lock); + list_del(&wait_entry->list); + spin_unlock_bh(¬if_wait->notif_wait_lock); +} +IWL_EXPORT_SYMBOL(iwl_remove_notification); + +int iwl_wait_notification(struct iwl_notif_wait_data *notif_wait, + struct iwl_notification_wait *wait_entry, + unsigned long timeout) +{ + int ret; + + ret = wait_event_timeout(notif_wait->notif_waitq, + wait_entry->triggered || wait_entry->aborted, + timeout); + + iwl_remove_notification(notif_wait, wait_entry); + + if (wait_entry->aborted) + return -EIO; + + /* return value is always >= 0 */ + if (ret <= 0) + return -ETIMEDOUT; + return 0; +} +IWL_EXPORT_SYMBOL(iwl_wait_notification); diff --git a/sys/contrib/dev/iwlwifi/fw/notif-wait.h b/sys/contrib/dev/iwlwifi/fw/notif-wait.h new file mode 100644 index 00000000000..49e8ba11b6a --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/notif-wait.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_notif_wait_h__ +#define __iwl_notif_wait_h__ + +#include + +#include "iwl-trans.h" + +struct iwl_notif_wait_data { + struct list_head notif_waits; + spinlock_t notif_wait_lock; + wait_queue_head_t notif_waitq; +}; + +#define MAX_NOTIF_CMDS 5 + +/** + * struct iwl_notification_wait - notification wait entry + * @list: list head for global list + * @fn: Function called with the notification. If the function + * returns true, the wait is over, if it returns false then + * the waiter stays blocked. If no function is given, any + * of the listed commands will unblock the waiter. + * @cmds: command IDs + * @n_cmds: number of command IDs + * @triggered: waiter should be woken up + * @aborted: wait was aborted + * + * This structure is not used directly, to wait for a + * notification declare it on the stack, and call + * iwl_init_notification_wait() with appropriate + * parameters. Then do whatever will cause the ucode + * to notify the driver, and to wait for that then + * call iwl_wait_notification(). + * + * Each notification is one-shot. If at some point we + * need to support multi-shot notifications (which + * can't be allocated on the stack) we need to modify + * the code for them. + */ +struct iwl_notification_wait { + struct list_head list; + + bool (*fn)(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt, void *data); + void *fn_data; + + u16 cmds[MAX_NOTIF_CMDS]; + u8 n_cmds; + bool triggered, aborted; +}; + + +/* caller functions */ +void iwl_notification_wait_init(struct iwl_notif_wait_data *notif_data); +bool iwl_notification_wait(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt); +void iwl_abort_notification_waits(struct iwl_notif_wait_data *notif_data); + +static inline void +iwl_notification_notify(struct iwl_notif_wait_data *notif_data) +{ + wake_up_all(¬if_data->notif_waitq); +} + +static inline void +iwl_notification_wait_notify(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt) +{ + if (iwl_notification_wait(notif_data, pkt)) + iwl_notification_notify(notif_data); +} + +/* user functions */ +void __acquires(wait_entry) +iwl_init_notification_wait(struct iwl_notif_wait_data *notif_data, + struct iwl_notification_wait *wait_entry, + const u16 *cmds, int n_cmds, + bool (*fn)(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt, void *data), + void *fn_data); + +int __must_check __releases(wait_entry) +iwl_wait_notification(struct iwl_notif_wait_data *notif_data, + struct iwl_notification_wait *wait_entry, + unsigned long timeout); + +void __releases(wait_entry) +iwl_remove_notification(struct iwl_notif_wait_data *notif_data, + struct iwl_notification_wait *wait_entry); + +#endif /* __iwl_notif_wait_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/paging.c b/sys/contrib/dev/iwlwifi/fw/paging.c new file mode 100644 index 00000000000..6ed8b2e022b --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/paging.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include "iwl-drv.h" +#include "runtime.h" +#include "fw/api/commands.h" + +void iwl_free_fw_paging(struct iwl_fw_runtime *fwrt) +{ + int i; + + if (!fwrt->fw_paging_db[0].fw_paging_block) + return; + + for (i = 0; i < NUM_OF_FW_PAGING_BLOCKS; i++) { + struct iwl_fw_paging *paging = &fwrt->fw_paging_db[i]; + + if (!paging->fw_paging_block) { + IWL_DEBUG_FW(fwrt, + "Paging: block %d already freed, continue to next page\n", + i); + + continue; + } + dma_unmap_page(fwrt->trans->dev, paging->fw_paging_phys, + paging->fw_paging_size, DMA_BIDIRECTIONAL); + + __free_pages(paging->fw_paging_block, + get_order(paging->fw_paging_size)); + paging->fw_paging_block = NULL; + } + + memset(fwrt->fw_paging_db, 0, sizeof(fwrt->fw_paging_db)); +} +IWL_EXPORT_SYMBOL(iwl_free_fw_paging); + +static int iwl_alloc_fw_paging_mem(struct iwl_fw_runtime *fwrt, + const struct fw_img *image) +{ + struct page *block; + dma_addr_t phys = 0; + int blk_idx, order, num_of_pages, size; + + if (fwrt->fw_paging_db[0].fw_paging_block) + return 0; + + /* ensure BLOCK_2_EXP_SIZE is power of 2 of PAGING_BLOCK_SIZE */ + BUILD_BUG_ON(BIT(BLOCK_2_EXP_SIZE) != PAGING_BLOCK_SIZE); + + num_of_pages = image->paging_mem_size / FW_PAGING_SIZE; + fwrt->num_of_paging_blk = + DIV_ROUND_UP(num_of_pages, NUM_OF_PAGE_PER_GROUP); + fwrt->num_of_pages_in_last_blk = + num_of_pages - + NUM_OF_PAGE_PER_GROUP * (fwrt->num_of_paging_blk - 1); + + IWL_DEBUG_FW(fwrt, + "Paging: allocating mem for %d paging blocks, each block holds 8 pages, last block holds %d pages\n", + fwrt->num_of_paging_blk, + fwrt->num_of_pages_in_last_blk); + + /* + * Allocate CSS and paging blocks in dram. + */ + for (blk_idx = 0; blk_idx < fwrt->num_of_paging_blk + 1; blk_idx++) { + /* For CSS allocate 4KB, for others PAGING_BLOCK_SIZE (32K) */ + size = blk_idx ? PAGING_BLOCK_SIZE : FW_PAGING_SIZE; + order = get_order(size); + block = alloc_pages(GFP_KERNEL, order); + if (!block) { + /* free all the previous pages since we failed */ + iwl_free_fw_paging(fwrt); + return -ENOMEM; + } + + fwrt->fw_paging_db[blk_idx].fw_paging_block = block; + fwrt->fw_paging_db[blk_idx].fw_paging_size = size; + + phys = dma_map_page(fwrt->trans->dev, block, 0, + PAGE_SIZE << order, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(fwrt->trans->dev, phys)) { + /* + * free the previous pages and the current one + * since we failed to map_page. + */ + iwl_free_fw_paging(fwrt); + return -ENOMEM; + } + fwrt->fw_paging_db[blk_idx].fw_paging_phys = phys; + + if (!blk_idx) + IWL_DEBUG_FW(fwrt, + "Paging: allocated 4K(CSS) bytes (order %d) for firmware paging.\n", + order); + else + IWL_DEBUG_FW(fwrt, + "Paging: allocated 32K bytes (order %d) for firmware paging.\n", + order); + } + + return 0; +} + +static int iwl_fill_paging_mem(struct iwl_fw_runtime *fwrt, + const struct fw_img *image) +{ + int sec_idx, idx, ret; + u32 offset = 0; + + /* + * find where is the paging image start point: + * if CPU2 exist and it's in paging format, then the image looks like: + * CPU1 sections (2 or more) + * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between CPU1 to CPU2 + * CPU2 sections (not paged) + * PAGING_SEPARATOR_SECTION delimiter - separate between CPU2 + * non paged to CPU2 paging sec + * CPU2 paging CSS + * CPU2 paging image (including instruction and data) + */ + for (sec_idx = 0; sec_idx < image->num_sec; sec_idx++) { + if (image->sec[sec_idx].offset == PAGING_SEPARATOR_SECTION) { + sec_idx++; + break; + } + } + + /* + * If paging is enabled there should be at least 2 more sections left + * (one for CSS and one for Paging data) + */ + if (sec_idx >= image->num_sec - 1) { + IWL_ERR(fwrt, "Paging: Missing CSS and/or paging sections\n"); + ret = -EINVAL; + goto err; + } + + /* copy the CSS block to the dram */ + IWL_DEBUG_FW(fwrt, "Paging: load paging CSS to FW, sec = %d\n", + sec_idx); + + if (image->sec[sec_idx].len > fwrt->fw_paging_db[0].fw_paging_size) { + IWL_ERR(fwrt, "CSS block is larger than paging size\n"); + ret = -EINVAL; + goto err; + } + + memcpy(page_address(fwrt->fw_paging_db[0].fw_paging_block), + image->sec[sec_idx].data, + image->sec[sec_idx].len); + fwrt->fw_paging_db[0].fw_offs = image->sec[sec_idx].offset; + dma_sync_single_for_device(fwrt->trans->dev, + fwrt->fw_paging_db[0].fw_paging_phys, + fwrt->fw_paging_db[0].fw_paging_size, + DMA_BIDIRECTIONAL); + + IWL_DEBUG_FW(fwrt, + "Paging: copied %d CSS bytes to first block\n", + fwrt->fw_paging_db[0].fw_paging_size); + + sec_idx++; + + /* + * Copy the paging blocks to the dram. The loop index starts + * from 1 since the CSS block (index 0) was already copied to + * dram. We use num_of_paging_blk + 1 to account for that. + */ + for (idx = 1; idx < fwrt->num_of_paging_blk + 1; idx++) { + struct iwl_fw_paging *block = &fwrt->fw_paging_db[idx]; + int remaining = image->sec[sec_idx].len - offset; + int len = block->fw_paging_size; + + /* + * For the last block, we copy all that is remaining, + * for all other blocks, we copy fw_paging_size at a + * time. */ + if (idx == fwrt->num_of_paging_blk) { + len = remaining; + if (remaining != + fwrt->num_of_pages_in_last_blk * FW_PAGING_SIZE) { + IWL_ERR(fwrt, + "Paging: last block contains more data than expected %d\n", + remaining); + ret = -EINVAL; + goto err; + } + } else if (block->fw_paging_size > remaining) { + IWL_ERR(fwrt, + "Paging: not enough data in other in block %d (%d)\n", + idx, remaining); + ret = -EINVAL; + goto err; + } + + memcpy(page_address(block->fw_paging_block), + (const u8 *)image->sec[sec_idx].data + offset, len); + block->fw_offs = image->sec[sec_idx].offset + offset; + dma_sync_single_for_device(fwrt->trans->dev, + block->fw_paging_phys, + block->fw_paging_size, + DMA_BIDIRECTIONAL); + + IWL_DEBUG_FW(fwrt, + "Paging: copied %d paging bytes to block %d\n", + len, idx); + + offset += block->fw_paging_size; + } + + return 0; + +err: + iwl_free_fw_paging(fwrt); + return ret; +} + +static int iwl_save_fw_paging(struct iwl_fw_runtime *fwrt, + const struct fw_img *fw) +{ + int ret; + + ret = iwl_alloc_fw_paging_mem(fwrt, fw); + if (ret) + return ret; + + return iwl_fill_paging_mem(fwrt, fw); +} + +/* send paging cmd to FW in case CPU2 has paging image */ +static int iwl_send_paging_cmd(struct iwl_fw_runtime *fwrt, + const struct fw_img *fw) +{ + struct iwl_fw_paging_cmd paging_cmd = { + .flags = cpu_to_le32(PAGING_CMD_IS_SECURED | + PAGING_CMD_IS_ENABLED | + (fwrt->num_of_pages_in_last_blk << + PAGING_CMD_NUM_OF_PAGES_IN_LAST_GRP_POS)), + .block_size = cpu_to_le32(BLOCK_2_EXP_SIZE), + .block_num = cpu_to_le32(fwrt->num_of_paging_blk), + }; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(FW_PAGING_BLOCK_CMD, IWL_ALWAYS_LONG_GROUP, 0), + .len = { sizeof(paging_cmd), }, + .data = { &paging_cmd, }, + }; + int blk_idx; + + /* loop for for all paging blocks + CSS block */ + for (blk_idx = 0; blk_idx < fwrt->num_of_paging_blk + 1; blk_idx++) { + dma_addr_t addr = fwrt->fw_paging_db[blk_idx].fw_paging_phys; + __le32 phy_addr; + + addr = addr >> PAGE_2_EXP_SIZE; + phy_addr = cpu_to_le32(addr); + paging_cmd.device_phy_addr[blk_idx] = phy_addr; + } + + return iwl_trans_send_cmd(fwrt->trans, &hcmd); +} + +int iwl_init_paging(struct iwl_fw_runtime *fwrt, enum iwl_ucode_type type) +{ + const struct fw_img *fw = &fwrt->fw->img[type]; + int ret; + + if (fwrt->trans->trans_cfg->gen2) + return 0; + + /* + * Configure and operate fw paging mechanism. + * The driver configures the paging flow only once. + * The CPU2 paging image is included in the IWL_UCODE_INIT image. + */ + if (!fw->paging_mem_size) + return 0; + + ret = iwl_save_fw_paging(fwrt, fw); + if (ret) { + IWL_ERR(fwrt, "failed to save the FW paging image\n"); + return ret; + } + + ret = iwl_send_paging_cmd(fwrt, fw); + if (ret) { + IWL_ERR(fwrt, "failed to send the paging cmd\n"); + iwl_free_fw_paging(fwrt); + return ret; + } + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_init_paging); diff --git a/sys/contrib/dev/iwlwifi/fw/pnvm.c b/sys/contrib/dev/iwlwifi/fw/pnvm.c new file mode 100644 index 00000000000..b6d3ac6ed44 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/pnvm.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright(c) 2020-2021 Intel Corporation + */ + +#include "iwl-drv.h" +#include "pnvm.h" +#include "iwl-prph.h" +#include "iwl-io.h" +#include "fw/api/commands.h" +#include "fw/api/nvm-reg.h" +#include "fw/api/alive.h" +#include "fw/uefi.h" + +struct iwl_pnvm_section { + __le32 offset; + const u8 data[]; +} __packed; + +static bool iwl_pnvm_complete_fn(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_trans *trans = (struct iwl_trans *)data; + struct iwl_pnvm_init_complete_ntfy *pnvm_ntf = (void *)pkt->data; + + IWL_DEBUG_FW(trans, + "PNVM complete notification received with status 0x%0x\n", + le32_to_cpu(pnvm_ntf->status)); + + return true; +} + +static int iwl_pnvm_handle_section(struct iwl_trans *trans, const u8 *data, + size_t len) +{ + const struct iwl_ucode_tlv *tlv; + u32 sha1 = 0; + u16 mac_type = 0, rf_id = 0; + u8 *pnvm_data = NULL, *tmp; + bool hw_match = false; + u32 size = 0; + int ret; + + IWL_DEBUG_FW(trans, "Handling PNVM section\n"); + + while (len >= sizeof(*tlv)) { + u32 tlv_len, tlv_type; + + len -= sizeof(*tlv); + tlv = (const void *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + ret = -EINVAL; + goto out; + } + + data += sizeof(*tlv); + + switch (tlv_type) { + case IWL_UCODE_TLV_PNVM_VERSION: + if (tlv_len < sizeof(__le32)) { + IWL_DEBUG_FW(trans, + "Invalid size for IWL_UCODE_TLV_PNVM_VERSION (expected %zd, got %d)\n", + sizeof(__le32), tlv_len); + break; + } + + sha1 = le32_to_cpup((const __le32 *)data); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_PNVM_VERSION %0x\n", + sha1); + break; + case IWL_UCODE_TLV_HW_TYPE: + if (tlv_len < 2 * sizeof(__le16)) { + IWL_DEBUG_FW(trans, + "Invalid size for IWL_UCODE_TLV_HW_TYPE (expected %zd, got %d)\n", + 2 * sizeof(__le16), tlv_len); + break; + } + + if (hw_match) + break; + + mac_type = le16_to_cpup((const __le16 *)data); + rf_id = le16_to_cpup((const __le16 *)(data + sizeof(__le16))); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_HW_TYPE mac_type 0x%0x rf_id 0x%0x\n", + mac_type, rf_id); + + if (mac_type == CSR_HW_REV_TYPE(trans->hw_rev) && + rf_id == CSR_HW_RFID_TYPE(trans->hw_rf_id)) + hw_match = true; + break; + case IWL_UCODE_TLV_SEC_RT: { + const struct iwl_pnvm_section *section = (const void *)data; + u32 data_len = tlv_len - sizeof(*section); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_SEC_RT len %d\n", + tlv_len); + + /* TODO: remove, this is a deprecated separator */ + if (le32_to_cpup((const __le32 *)data) == 0xddddeeee) { + IWL_DEBUG_FW(trans, "Ignoring separator.\n"); + break; + } + + IWL_DEBUG_FW(trans, "Adding data (size %d)\n", + data_len); + + tmp = krealloc(pnvm_data, size + data_len, GFP_KERNEL); + if (!tmp) { + IWL_DEBUG_FW(trans, + "Couldn't allocate (more) pnvm_data\n"); + + ret = -ENOMEM; + goto out; + } + + pnvm_data = tmp; + + memcpy(pnvm_data + size, section->data, data_len); + + size += data_len; + + break; + } + case IWL_UCODE_TLV_PNVM_SKU: + IWL_DEBUG_FW(trans, + "New PNVM section started, stop parsing.\n"); + goto done; + default: + IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n", + tlv_type, tlv_len); + break; + } + + len -= ALIGN(tlv_len, 4); + data += ALIGN(tlv_len, 4); + } + +done: + if (!hw_match) { + IWL_DEBUG_FW(trans, + "HW mismatch, skipping PNVM section (need mac_type 0x%x rf_id 0x%x)\n", + CSR_HW_REV_TYPE(trans->hw_rev), + CSR_HW_RFID_TYPE(trans->hw_rf_id)); + ret = -ENOENT; + goto out; + } + + if (!size) { + IWL_DEBUG_FW(trans, "Empty PNVM, skipping.\n"); + ret = -ENOENT; + goto out; + } + + IWL_INFO(trans, "loaded PNVM version %08x\n", sha1); + + ret = iwl_trans_set_pnvm(trans, pnvm_data, size); +out: + kfree(pnvm_data); + return ret; +} + +static int iwl_pnvm_parse(struct iwl_trans *trans, const u8 *data, + size_t len) +{ + const struct iwl_ucode_tlv *tlv; + + IWL_DEBUG_FW(trans, "Parsing PNVM file\n"); + + while (len >= sizeof(*tlv)) { + u32 tlv_len, tlv_type; + + len -= sizeof(*tlv); + tlv = (const void *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + return -EINVAL; + } + + if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) { + const struct iwl_sku_id *sku_id = + (const void *)(data + sizeof(*tlv)); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", + tlv_len); + IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", + le32_to_cpu(sku_id->data[0]), + le32_to_cpu(sku_id->data[1]), + le32_to_cpu(sku_id->data[2])); + + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + len -= ALIGN(tlv_len, 4); + + if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) && + trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) && + trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) { + int ret; + + ret = iwl_pnvm_handle_section(trans, data, len); + if (!ret) + return 0; + } else { + IWL_DEBUG_FW(trans, "SKU ID didn't match!\n"); + } + } else { + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + len -= ALIGN(tlv_len, 4); + } + } + + return -ENOENT; +} + +static int iwl_pnvm_get_from_fs(struct iwl_trans *trans, u8 **data, size_t *len) +{ + const struct firmware *pnvm; + char pnvm_name[MAX_PNVM_NAME]; + size_t new_len; + int ret; + + iwl_pnvm_get_fs_name(trans, pnvm_name, sizeof(pnvm_name)); + + ret = firmware_request_nowarn(&pnvm, pnvm_name, trans->dev); + if (ret) { + IWL_DEBUG_FW(trans, "PNVM file %s not found %d\n", + pnvm_name, ret); + return ret; + } + + new_len = pnvm->size; + *data = kmemdup(pnvm->data, pnvm->size, GFP_KERNEL); + release_firmware(pnvm); + + if (!*data) + return -ENOMEM; + + *len = new_len; + + return 0; +} + +int iwl_pnvm_load(struct iwl_trans *trans, + struct iwl_notif_wait_data *notif_wait) +{ + u8 *data; + size_t len; + struct pnvm_sku_package *package; + struct iwl_notification_wait pnvm_wait; + static const u16 ntf_cmds[] = { WIDE_ID(REGULATORY_AND_NVM_GROUP, + PNVM_INIT_COMPLETE_NTFY) }; + int ret; + + /* if the SKU_ID is empty, there's nothing to do */ + if (!trans->sku_id[0] && !trans->sku_id[1] && !trans->sku_id[2]) + return 0; + + /* + * If we already loaded (or tried to load) it before, we just + * need to set it again. + */ + if (trans->pnvm_loaded) { + ret = iwl_trans_set_pnvm(trans, NULL, 0); + if (ret) + return ret; + goto skip_parse; + } + + /* First attempt to get the PNVM from BIOS */ + package = iwl_uefi_get_pnvm(trans, &len); + if (!IS_ERR_OR_NULL(package)) { + if (len >= sizeof(*package)) { + /* we need only the data */ + len -= sizeof(*package); + data = kmemdup(package->data, len, GFP_KERNEL); + } else { + data = NULL; + } + + /* free package regardless of whether kmemdup succeeded */ + kfree(package); + + if (data) + goto parse; + } + + /* If it's not available, try from the filesystem */ + ret = iwl_pnvm_get_from_fs(trans, &data, &len); + if (ret) { + /* + * Pretend we've loaded it - at least we've tried and + * couldn't load it at all, so there's no point in + * trying again over and over. + */ + trans->pnvm_loaded = true; + + goto skip_parse; + } + +parse: + iwl_pnvm_parse(trans, data, len); + + kfree(data); + +skip_parse: + data = NULL; + /* now try to get the reduce power table, if not loaded yet */ + if (!trans->reduce_power_loaded) { + data = iwl_uefi_get_reduced_power(trans, &len); + if (IS_ERR_OR_NULL(data)) { + /* + * Pretend we've loaded it - at least we've tried and + * couldn't load it at all, so there's no point in + * trying again over and over. + */ + trans->reduce_power_loaded = true; + + goto skip_reduce_power; + } + } + + ret = iwl_trans_set_reduce_power(trans, data, len); + if (ret) + IWL_DEBUG_FW(trans, + "Failed to set reduce power table %d\n", + ret); + kfree(data); + +skip_reduce_power: + iwl_init_notification_wait(notif_wait, &pnvm_wait, + ntf_cmds, ARRAY_SIZE(ntf_cmds), + iwl_pnvm_complete_fn, trans); + + /* kick the doorbell */ + iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6, + UREG_DOORBELL_TO_ISR6_PNVM); + + return iwl_wait_notification(notif_wait, &pnvm_wait, + MVM_UCODE_PNVM_TIMEOUT); +} +IWL_EXPORT_SYMBOL(iwl_pnvm_load); diff --git a/sys/contrib/dev/iwlwifi/fw/pnvm.h b/sys/contrib/dev/iwlwifi/fw/pnvm.h new file mode 100644 index 00000000000..203c367dd4d --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/pnvm.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/****************************************************************************** + * + * Copyright(c) 2020-2021 Intel Corporation + * + *****************************************************************************/ + +#ifndef __IWL_PNVM_H__ +#define __IWL_PNVM_H__ + +#include "fw/notif-wait.h" + +#define MVM_UCODE_PNVM_TIMEOUT (HZ / 4) + +#define MAX_PNVM_NAME 64 + +int iwl_pnvm_load(struct iwl_trans *trans, + struct iwl_notif_wait_data *notif_wait); + +static inline +void iwl_pnvm_get_fs_name(struct iwl_trans *trans, + u8 *pnvm_name, size_t max_len) +{ + int pre_len; + + /* + * The prefix unfortunately includes a hyphen at the end, so + * don't add the dot here... + */ + snprintf(pnvm_name, max_len, "%spnvm", trans->cfg->fw_name_pre); + + /* ...but replace the hyphen with the dot here. */ + pre_len = strlen(trans->cfg->fw_name_pre); + if (pre_len < max_len && pre_len > 0) + pnvm_name[pre_len - 1] = '.'; +} + +#endif /* __IWL_PNVM_H__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/rs.c b/sys/contrib/dev/iwlwifi/fw/rs.c new file mode 100644 index 00000000000..a21c3befd93 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/rs.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2021 Intel Corporation + */ + +#include +#include "fw/api/rs.h" +#include "iwl-drv.h" +#include "iwl-config.h" + +#define IWL_DECLARE_RATE_INFO(r) \ + [IWL_RATE_##r##M_INDEX] = IWL_RATE_##r##M_PLCP + +/* + * Translate from fw_rate_index (IWL_RATE_XXM_INDEX) to PLCP + * */ +static const u8 fw_rate_idx_to_plcp[IWL_RATE_COUNT] = { + IWL_DECLARE_RATE_INFO(1), + IWL_DECLARE_RATE_INFO(2), + IWL_DECLARE_RATE_INFO(5), + IWL_DECLARE_RATE_INFO(11), + IWL_DECLARE_RATE_INFO(6), + IWL_DECLARE_RATE_INFO(9), + IWL_DECLARE_RATE_INFO(12), + IWL_DECLARE_RATE_INFO(18), + IWL_DECLARE_RATE_INFO(24), + IWL_DECLARE_RATE_INFO(36), + IWL_DECLARE_RATE_INFO(48), + IWL_DECLARE_RATE_INFO(54), +}; + +/* mbps, mcs */ +static const struct iwl_rate_mcs_info rate_mcs[IWL_RATE_COUNT] = { + { "1", "BPSK DSSS"}, + { "2", "QPSK DSSS"}, + {"5.5", "BPSK CCK"}, + { "11", "QPSK CCK"}, + { "6", "BPSK 1/2"}, + { "9", "BPSK 1/2"}, + { "12", "QPSK 1/2"}, + { "18", "QPSK 3/4"}, + { "24", "16QAM 1/2"}, + { "36", "16QAM 3/4"}, + { "48", "64QAM 2/3"}, + { "54", "64QAM 3/4"}, + { "60", "64QAM 5/6"}, +}; + +static const char * const ant_name[] = { + [ANT_NONE] = "None", + [ANT_A] = "A", + [ANT_B] = "B", + [ANT_AB] = "AB", +}; + +static const char * const pretty_bw[] = { + "20Mhz", + "40Mhz", + "80Mhz", + "160 Mhz", + "320Mhz", +}; + +u8 iwl_fw_rate_idx_to_plcp(int idx) +{ + return fw_rate_idx_to_plcp[idx]; +} +IWL_EXPORT_SYMBOL(iwl_fw_rate_idx_to_plcp); + +const struct iwl_rate_mcs_info *iwl_rate_mcs(int idx) +{ + return &rate_mcs[idx]; +} +IWL_EXPORT_SYMBOL(iwl_rate_mcs); + +const char *iwl_rs_pretty_ant(u8 ant) +{ + if (ant >= ARRAY_SIZE(ant_name)) + return "UNKNOWN"; + + return ant_name[ant]; +} +IWL_EXPORT_SYMBOL(iwl_rs_pretty_ant); + +const char *iwl_rs_pretty_bw(int bw) +{ + if (bw >= ARRAY_SIZE(pretty_bw)) + return "unknown bw"; + + return pretty_bw[bw]; +} +IWL_EXPORT_SYMBOL(iwl_rs_pretty_bw); + +u32 iwl_new_rate_from_v1(u32 rate_v1) +{ + u32 rate_v2 = 0; + u32 dup = 0; + + if (rate_v1 == 0) + return rate_v1; + /* convert rate */ + if (rate_v1 & RATE_MCS_HT_MSK_V1) { + u32 nss = 0; + + rate_v2 |= RATE_MCS_HT_MSK; + rate_v2 |= + rate_v1 & RATE_HT_MCS_RATE_CODE_MSK_V1; + nss = (rate_v1 & RATE_HT_MCS_MIMO2_MSK) >> + RATE_HT_MCS_NSS_POS_V1; + rate_v2 |= nss << RATE_MCS_NSS_POS; + } else if (rate_v1 & RATE_MCS_VHT_MSK_V1 || + rate_v1 & RATE_MCS_HE_MSK_V1) { + rate_v2 |= rate_v1 & RATE_VHT_MCS_RATE_CODE_MSK; + + rate_v2 |= rate_v1 & RATE_VHT_MCS_MIMO2_MSK; + + if (rate_v1 & RATE_MCS_HE_MSK_V1) { + u32 he_type_bits = rate_v1 & RATE_MCS_HE_TYPE_MSK_V1; + u32 he_type = he_type_bits >> RATE_MCS_HE_TYPE_POS_V1; + u32 he_106t = (rate_v1 & RATE_MCS_HE_106T_MSK_V1) >> + RATE_MCS_HE_106T_POS_V1; + u32 he_gi_ltf = (rate_v1 & RATE_MCS_HE_GI_LTF_MSK_V1) >> + RATE_MCS_HE_GI_LTF_POS; + + if ((he_type_bits == RATE_MCS_HE_TYPE_SU || + he_type_bits == RATE_MCS_HE_TYPE_EXT_SU) && + he_gi_ltf == RATE_MCS_HE_SU_4_LTF) + /* the new rate have an additional bit to + * represent the value 4 rather then using SGI + * bit for this purpose - as it was done in the old + * rate */ + he_gi_ltf += (rate_v1 & RATE_MCS_SGI_MSK_V1) >> + RATE_MCS_SGI_POS_V1; + + rate_v2 |= he_gi_ltf << RATE_MCS_HE_GI_LTF_POS; + rate_v2 |= he_type << RATE_MCS_HE_TYPE_POS; + rate_v2 |= he_106t << RATE_MCS_HE_106T_POS; + rate_v2 |= rate_v1 & RATE_HE_DUAL_CARRIER_MODE_MSK; + rate_v2 |= RATE_MCS_HE_MSK; + } else { + rate_v2 |= RATE_MCS_VHT_MSK; + } + /* if legacy format */ + } else { + u32 legacy_rate = iwl_legacy_rate_to_fw_idx(rate_v1); + + WARN_ON(legacy_rate < 0); + rate_v2 |= legacy_rate; + if (!(rate_v1 & RATE_MCS_CCK_MSK_V1)) + rate_v2 |= RATE_MCS_LEGACY_OFDM_MSK; + } + + /* convert flags */ + if (rate_v1 & RATE_MCS_LDPC_MSK_V1) + rate_v2 |= RATE_MCS_LDPC_MSK; + rate_v2 |= (rate_v1 & RATE_MCS_CHAN_WIDTH_MSK_V1) | + (rate_v1 & RATE_MCS_ANT_AB_MSK) | + (rate_v1 & RATE_MCS_STBC_MSK) | + (rate_v1 & RATE_MCS_BF_MSK); + + dup = (rate_v1 & RATE_MCS_DUP_MSK_V1) >> RATE_MCS_DUP_POS_V1; + if (dup) { + rate_v2 |= RATE_MCS_DUP_MSK; + rate_v2 |= dup << RATE_MCS_CHAN_WIDTH_POS; + } + + if ((!(rate_v1 & RATE_MCS_HE_MSK_V1)) && + (rate_v1 & RATE_MCS_SGI_MSK_V1)) + rate_v2 |= RATE_MCS_SGI_MSK; + + return rate_v2; +} +IWL_EXPORT_SYMBOL(iwl_new_rate_from_v1); + +u32 iwl_legacy_rate_to_fw_idx(u32 rate_n_flags) +{ + int rate = rate_n_flags & RATE_LEGACY_RATE_MSK_V1; + int idx; + bool ofdm = !(rate_n_flags & RATE_MCS_CCK_MSK_V1); + int offset = ofdm ? IWL_FIRST_OFDM_RATE : 0; + int last = ofdm ? IWL_RATE_COUNT_LEGACY : IWL_FIRST_OFDM_RATE; + + for (idx = offset; idx < last; idx++) + if (iwl_fw_rate_idx_to_plcp(idx) == rate) + return idx - offset; + return -1; +} + +int rs_pretty_print_rate(char *buf, int bufsz, const u32 rate) +{ + char *type; + u8 mcs = 0, nss = 0; + u8 ant = (rate & RATE_MCS_ANT_AB_MSK) >> RATE_MCS_ANT_POS; + u32 bw = (rate & RATE_MCS_CHAN_WIDTH_MSK) >> + RATE_MCS_CHAN_WIDTH_POS; + u32 format = rate & RATE_MCS_MOD_TYPE_MSK; + bool sgi; + + if (format == RATE_MCS_CCK_MSK || + format == RATE_MCS_LEGACY_OFDM_MSK) { + int legacy_rate = rate & RATE_LEGACY_RATE_MSK; + int index = format == RATE_MCS_CCK_MSK ? + legacy_rate : + legacy_rate + IWL_FIRST_OFDM_RATE; + + return scnprintf(buf, bufsz, "Legacy | ANT: %s Rate: %s Mbps", + iwl_rs_pretty_ant(ant), + index == IWL_RATE_INVALID ? "BAD" : + iwl_rate_mcs(index)->mbps); + } + + if (format == RATE_MCS_VHT_MSK) + type = "VHT"; + else if (format == RATE_MCS_HT_MSK) + type = "HT"; + else if (format == RATE_MCS_HE_MSK) + type = "HE"; + else + type = "Unknown"; /* shouldn't happen */ + + mcs = format == RATE_MCS_HT_MSK ? + RATE_HT_MCS_INDEX(rate) : + rate & RATE_MCS_CODE_MSK; + nss = ((rate & RATE_MCS_NSS_MSK) + >> RATE_MCS_NSS_POS) + 1; + sgi = format == RATE_MCS_HE_MSK ? + iwl_he_is_sgi(rate) : + rate & RATE_MCS_SGI_MSK; + + return scnprintf(buf, bufsz, + "0x%x: %s | ANT: %s BW: %s MCS: %d NSS: %d %s%s%s%s%s", + rate, type, iwl_rs_pretty_ant(ant), iwl_rs_pretty_bw(bw), mcs, nss, + (sgi) ? "SGI " : "NGI ", + (rate & RATE_MCS_STBC_MSK) ? "STBC " : "", + (rate & RATE_MCS_LDPC_MSK) ? "LDPC " : "", + (rate & RATE_HE_DUAL_CARRIER_MODE_MSK) ? "DCM " : "", + (rate & RATE_MCS_BF_MSK) ? "BF " : ""); +} +IWL_EXPORT_SYMBOL(rs_pretty_print_rate); + +bool iwl_he_is_sgi(u32 rate_n_flags) +{ + u32 type = rate_n_flags & RATE_MCS_HE_TYPE_MSK; + u32 ltf_gi = rate_n_flags & RATE_MCS_HE_GI_LTF_MSK; + + if (type == RATE_MCS_HE_TYPE_SU || + type == RATE_MCS_HE_TYPE_EXT_SU) + return ltf_gi == RATE_MCS_HE_SU_4_LTF_08_GI; + return false; +} +IWL_EXPORT_SYMBOL(iwl_he_is_sgi); + diff --git a/sys/contrib/dev/iwlwifi/fw/runtime.h b/sys/contrib/dev/iwlwifi/fw/runtime.h new file mode 100644 index 00000000000..69799f1ed2c --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/runtime.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2020 Intel Corporation + */ +#ifndef __iwl_fw_runtime_h__ +#define __iwl_fw_runtime_h__ + +#include "iwl-config.h" +#include "iwl-trans.h" +#include "img.h" +#include "fw/api/debug.h" +#include "fw/api/paging.h" +#include "fw/api/power.h" +#include "iwl-eeprom-parse.h" +#include "fw/acpi.h" + +struct iwl_fw_runtime_ops { + int (*dump_start)(void *ctx); + void (*dump_end)(void *ctx); + bool (*fw_running)(void *ctx); + int (*send_hcmd)(void *ctx, struct iwl_host_cmd *host_cmd); + bool (*d3_debug_enable)(void *ctx); +}; + +#define MAX_NUM_LMAC 2 +struct iwl_fwrt_shared_mem_cfg { + int num_lmacs; + int num_txfifo_entries; + struct { + u32 txfifo_size[TX_FIFO_MAX_NUM]; + u32 rxfifo1_size; + } lmac[MAX_NUM_LMAC]; + u32 rxfifo2_size; + u32 rxfifo2_control_size; + u32 internal_txfifo_addr; + u32 internal_txfifo_size[TX_FIFO_INTERNAL_MAX_NUM]; +}; + +#define IWL_FW_RUNTIME_DUMP_WK_NUM 5 + +/** + * struct iwl_fwrt_dump_data - dump data + * @trig: trigger the worker was scheduled upon + * @fw_pkt: packet received from FW + */ +struct iwl_fwrt_dump_data { + union { + struct { + struct iwl_fw_ini_trigger_tlv *trig; + struct iwl_rx_packet *fw_pkt; + }; + struct { + const struct iwl_fw_dump_desc *desc; + bool monitor_only; + }; + }; +}; + +/** + * struct iwl_fwrt_wk_data - dump worker data struct + * @idx: index of the worker + * @wk: worker + */ +struct iwl_fwrt_wk_data { + u8 idx; + struct delayed_work wk; + struct iwl_fwrt_dump_data dump_data; +}; + +/** + * struct iwl_txf_iter_data - Tx fifo iterator data struct + * @fifo: fifo number + * @lmac: lmac number + * @fifo_size: fifo size + * @internal_txf: non zero if fifo is internal Tx fifo + */ +struct iwl_txf_iter_data { + int fifo; + int lmac; + u32 fifo_size; + u8 internal_txf; +}; + +/** + * struct iwl_fw_runtime - runtime data for firmware + * @fw: firmware image + * @cfg: NIC configuration + * @dev: device pointer + * @ops: user ops + * @ops_ctx: user ops context + * @fw_paging_db: paging database + * @num_of_paging_blk: number of paging blocks + * @num_of_pages_in_last_blk: number of pages in the last block + * @smem_cfg: saved firmware SMEM configuration + * @cur_fw_img: current firmware image, must be maintained by + * the driver by calling &iwl_fw_set_current_image() + * @dump: debug dump data + */ +struct iwl_fw_runtime { + struct iwl_trans *trans; + const struct iwl_fw *fw; + struct device *dev; + + const struct iwl_fw_runtime_ops *ops; + void *ops_ctx; + + const struct iwl_dump_sanitize_ops *sanitize_ops; + void *sanitize_ctx; + + /* Paging */ + struct iwl_fw_paging fw_paging_db[NUM_OF_FW_PAGING_BLOCKS]; + u16 num_of_paging_blk; + u16 num_of_pages_in_last_blk; + + enum iwl_ucode_type cur_fw_img; + + /* memory configuration */ + struct iwl_fwrt_shared_mem_cfg smem_cfg; + + /* debug */ + struct { + struct iwl_fwrt_wk_data wks[IWL_FW_RUNTIME_DUMP_WK_NUM]; + unsigned long active_wks; + + u8 conf; + + /* ts of the beginning of a non-collect fw dbg data period */ + unsigned long non_collect_ts_start[IWL_FW_INI_TIME_POINT_NUM]; + u32 *d3_debug_data; + u32 lmac_err_id[MAX_NUM_LMAC]; + u32 umac_err_id; + + struct iwl_txf_iter_data txf_iter_data; + + struct { + u8 type; + u8 subtype; + u32 lmac_major; + u32 lmac_minor; + u32 umac_major; + u32 umac_minor; + } fw_ver; + } dump; +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct { + struct delayed_work wk; + u32 delay; + u64 seq; + } timestamp; + bool tpc_enabled; +#endif /* CONFIG_IWLWIFI_DEBUGFS */ +#ifdef CONFIG_ACPI + struct iwl_sar_profile sar_profiles[ACPI_SAR_PROFILE_NUM]; + u8 sar_chain_a_profile; + u8 sar_chain_b_profile; + struct iwl_geo_profile geo_profiles[ACPI_NUM_GEO_PROFILES_REV3]; + u32 geo_rev; + union iwl_ppag_table_cmd ppag_table; + u32 ppag_ver; +#endif +}; + +void iwl_fw_runtime_init(struct iwl_fw_runtime *fwrt, struct iwl_trans *trans, + const struct iwl_fw *fw, + const struct iwl_fw_runtime_ops *ops, void *ops_ctx, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx, + struct dentry *dbgfs_dir); + +static inline void iwl_fw_runtime_free(struct iwl_fw_runtime *fwrt) +{ + int i; + + kfree(fwrt->dump.d3_debug_data); + fwrt->dump.d3_debug_data = NULL; + + iwl_dbg_tlv_del_timers(fwrt->trans); + for (i = 0; i < IWL_FW_RUNTIME_DUMP_WK_NUM; i++) + cancel_delayed_work_sync(&fwrt->dump.wks[i].wk); +} + +void iwl_fw_runtime_suspend(struct iwl_fw_runtime *fwrt); + +void iwl_fw_runtime_resume(struct iwl_fw_runtime *fwrt); + +static inline void iwl_fw_set_current_image(struct iwl_fw_runtime *fwrt, + enum iwl_ucode_type cur_fw_img) +{ + fwrt->cur_fw_img = cur_fw_img; +} + +int iwl_init_paging(struct iwl_fw_runtime *fwrt, enum iwl_ucode_type type); +void iwl_free_fw_paging(struct iwl_fw_runtime *fwrt); + +void iwl_get_shared_mem_conf(struct iwl_fw_runtime *fwrt); +int iwl_set_soc_latency(struct iwl_fw_runtime *fwrt); +int iwl_configure_rxq(struct iwl_fw_runtime *fwrt); + +#endif /* __iwl_fw_runtime_h__ */ diff --git a/sys/contrib/dev/iwlwifi/fw/smem.c b/sys/contrib/dev/iwlwifi/fw/smem.c new file mode 100644 index 00000000000..f2f1789f470 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/smem.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include "iwl-drv.h" +#include "runtime.h" +#include "fw/api/commands.h" + +static void iwl_parse_shared_mem_22000(struct iwl_fw_runtime *fwrt, + struct iwl_rx_packet *pkt) +{ + struct iwl_shared_mem_cfg *mem_cfg = (void *)pkt->data; + int i, lmac; + int lmac_num = le32_to_cpu(mem_cfg->lmac_num); + u8 api_ver = iwl_fw_lookup_notif_ver(fwrt->fw, SYSTEM_GROUP, + SHARED_MEM_CFG_CMD, 0); + + if (WARN_ON(lmac_num > ARRAY_SIZE(mem_cfg->lmac_smem))) + return; + + fwrt->smem_cfg.num_lmacs = lmac_num; + fwrt->smem_cfg.num_txfifo_entries = + ARRAY_SIZE(mem_cfg->lmac_smem[0].txfifo_size); + fwrt->smem_cfg.rxfifo2_size = le32_to_cpu(mem_cfg->rxfifo2_size); + + if (api_ver >= 4 && + !WARN_ON_ONCE(iwl_rx_packet_payload_len(pkt) < sizeof(*mem_cfg))) { + fwrt->smem_cfg.rxfifo2_control_size = + le32_to_cpu(mem_cfg->rxfifo2_control_size); + } + + for (lmac = 0; lmac < lmac_num; lmac++) { + struct iwl_shared_mem_lmac_cfg *lmac_cfg = + &mem_cfg->lmac_smem[lmac]; + + for (i = 0; i < ARRAY_SIZE(lmac_cfg->txfifo_size); i++) + fwrt->smem_cfg.lmac[lmac].txfifo_size[i] = + le32_to_cpu(lmac_cfg->txfifo_size[i]); + fwrt->smem_cfg.lmac[lmac].rxfifo1_size = + le32_to_cpu(lmac_cfg->rxfifo1_size); + } +} + +static void iwl_parse_shared_mem(struct iwl_fw_runtime *fwrt, + struct iwl_rx_packet *pkt) +{ + struct iwl_shared_mem_cfg_v2 *mem_cfg = (void *)pkt->data; + int i; + + fwrt->smem_cfg.num_lmacs = 1; + + fwrt->smem_cfg.num_txfifo_entries = ARRAY_SIZE(mem_cfg->txfifo_size); + for (i = 0; i < ARRAY_SIZE(mem_cfg->txfifo_size); i++) + fwrt->smem_cfg.lmac[0].txfifo_size[i] = + le32_to_cpu(mem_cfg->txfifo_size[i]); + + fwrt->smem_cfg.lmac[0].rxfifo1_size = + le32_to_cpu(mem_cfg->rxfifo_size[0]); + fwrt->smem_cfg.rxfifo2_size = le32_to_cpu(mem_cfg->rxfifo_size[1]); + + /* new API has more data, from rxfifo_addr field and on */ + if (fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) { + BUILD_BUG_ON(sizeof(fwrt->smem_cfg.internal_txfifo_size) != + sizeof(mem_cfg->internal_txfifo_size)); + + fwrt->smem_cfg.internal_txfifo_addr = + le32_to_cpu(mem_cfg->internal_txfifo_addr); + + for (i = 0; + i < ARRAY_SIZE(fwrt->smem_cfg.internal_txfifo_size); + i++) + fwrt->smem_cfg.internal_txfifo_size[i] = + le32_to_cpu(mem_cfg->internal_txfifo_size[i]); + } +} + +void iwl_get_shared_mem_conf(struct iwl_fw_runtime *fwrt) +{ + struct iwl_host_cmd cmd = { + .flags = CMD_WANT_SKB, + .data = { NULL, }, + .len = { 0, }, + }; + struct iwl_rx_packet *pkt; + int ret; + + if (fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) + cmd.id = iwl_cmd_id(SHARED_MEM_CFG_CMD, SYSTEM_GROUP, 0); + else + cmd.id = SHARED_MEM_CFG; + + ret = iwl_trans_send_cmd(fwrt->trans, &cmd); + + if (ret) { + WARN(ret != -ERFKILL, + "Could not send the SMEM command: %d\n", ret); + return; + } + + pkt = cmd.resp_pkt; + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000) + iwl_parse_shared_mem_22000(fwrt, pkt); + else + iwl_parse_shared_mem(fwrt, pkt); + + IWL_DEBUG_INFO(fwrt, "SHARED MEM CFG: got memory offsets/sizes\n"); + + iwl_free_resp(&cmd); +} +IWL_EXPORT_SYMBOL(iwl_get_shared_mem_conf); diff --git a/sys/contrib/dev/iwlwifi/fw/uefi.c b/sys/contrib/dev/iwlwifi/fw/uefi.c new file mode 100644 index 00000000000..c875bf35533 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/uefi.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright(c) 2021 Intel Corporation + */ + +#include "iwl-drv.h" +#include "pnvm.h" +#include "iwl-prph.h" +#include "iwl-io.h" + +#include "fw/uefi.h" +#include "fw/api/alive.h" +#include + +#define IWL_EFI_VAR_GUID EFI_GUID(0x92daaf2f, 0xc02b, 0x455b, \ + 0xb2, 0xec, 0xf5, 0xa3, \ + 0x59, 0x4f, 0x4a, 0xea) + +void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len) +{ + struct efivar_entry *pnvm_efivar; + void *data; + unsigned long package_size; + int err; + + *len = 0; + + pnvm_efivar = kzalloc(sizeof(*pnvm_efivar), GFP_KERNEL); + if (!pnvm_efivar) + return ERR_PTR(-ENOMEM); + + memcpy(&pnvm_efivar->var.VariableName, IWL_UEFI_OEM_PNVM_NAME, + sizeof(IWL_UEFI_OEM_PNVM_NAME)); + pnvm_efivar->var.VendorGuid = IWL_EFI_VAR_GUID; + + /* + * TODO: we hardcode a maximum length here, because reading + * from the UEFI is not working. To implement this properly, + * we have to call efivar_entry_size(). + */ + package_size = IWL_HARDCODED_PNVM_SIZE; + + data = kmalloc(package_size, GFP_KERNEL); + if (!data) { + data = ERR_PTR(-ENOMEM); + goto out; + } + + err = efivar_entry_get(pnvm_efivar, NULL, &package_size, data); + if (err) { + IWL_DEBUG_FW(trans, + "PNVM UEFI variable not found %d (len %lu)\n", + err, package_size); + kfree(data); + data = ERR_PTR(err); + goto out; + } + + IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %lu\n", package_size); + *len = package_size; + +out: + kfree(pnvm_efivar); + + return data; +} + +static void *iwl_uefi_reduce_power_section(struct iwl_trans *trans, + const u8 *data, size_t len) +{ + struct iwl_ucode_tlv *tlv; + u8 *reduce_power_data = NULL, *tmp; + u32 size = 0; + + IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n"); + + while (len >= sizeof(*tlv)) { + u32 tlv_len, tlv_type; + + len -= sizeof(*tlv); + tlv = (void *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + reduce_power_data = ERR_PTR(-EINVAL); + goto out; + } + + data += sizeof(*tlv); + + switch (tlv_type) { + case IWL_UCODE_TLV_MEM_DESC: { + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_MEM_DESC len %d\n", + tlv_len); + + IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len); + + tmp = krealloc(reduce_power_data, size + tlv_len, GFP_KERNEL); + if (!tmp) { + IWL_DEBUG_FW(trans, + "Couldn't allocate (more) reduce_power_data\n"); + + reduce_power_data = ERR_PTR(-ENOMEM); + goto out; + } + + reduce_power_data = tmp; + + memcpy(reduce_power_data + size, data, tlv_len); + + size += tlv_len; + + break; + } + case IWL_UCODE_TLV_PNVM_SKU: + IWL_DEBUG_FW(trans, + "New REDUCE_POWER section started, stop parsing.\n"); + goto done; + default: + IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n", + tlv_type, tlv_len); + break; + } + + len -= ALIGN(tlv_len, 4); + data += ALIGN(tlv_len, 4); + } + +done: + if (!size) { + IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n"); + reduce_power_data = ERR_PTR(-ENOENT); + goto out; + } + + IWL_INFO(trans, "loaded REDUCE_POWER\n"); + +out: + return reduce_power_data; +} + +static void *iwl_uefi_reduce_power_parse(struct iwl_trans *trans, + const u8 *data, size_t len) +{ + struct iwl_ucode_tlv *tlv; + void *sec_data; + + IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n"); + + while (len >= sizeof(*tlv)) { + u32 tlv_len, tlv_type; + + len -= sizeof(*tlv); + tlv = (void *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + return ERR_PTR(-EINVAL); + } + + if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) { + struct iwl_sku_id *sku_id = + (void *)(data + sizeof(*tlv)); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", + tlv_len); + IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", + le32_to_cpu(sku_id->data[0]), + le32_to_cpu(sku_id->data[1]), + le32_to_cpu(sku_id->data[2])); + + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + len -= ALIGN(tlv_len, 4); + + if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) && + trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) && + trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) { + sec_data = iwl_uefi_reduce_power_section(trans, + data, + len); + if (!IS_ERR(sec_data)) + return sec_data; + } else { + IWL_DEBUG_FW(trans, "SKU ID didn't match!\n"); + } + } else { + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + len -= ALIGN(tlv_len, 4); + } + } + + return ERR_PTR(-ENOENT); +} + +void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len) +{ + struct efivar_entry *reduce_power_efivar; + struct pnvm_sku_package *package; + void *data = NULL; + unsigned long package_size; + int err; + + *len = 0; + + reduce_power_efivar = kzalloc(sizeof(*reduce_power_efivar), GFP_KERNEL); + if (!reduce_power_efivar) + return ERR_PTR(-ENOMEM); + + memcpy(&reduce_power_efivar->var.VariableName, IWL_UEFI_REDUCED_POWER_NAME, + sizeof(IWL_UEFI_REDUCED_POWER_NAME)); + reduce_power_efivar->var.VendorGuid = IWL_EFI_VAR_GUID; + + /* + * TODO: we hardcode a maximum length here, because reading + * from the UEFI is not working. To implement this properly, + * we have to call efivar_entry_size(). + */ + package_size = IWL_HARDCODED_REDUCE_POWER_SIZE; + + package = kmalloc(package_size, GFP_KERNEL); + if (!package) { + package = ERR_PTR(-ENOMEM); + goto out; + } + + err = efivar_entry_get(reduce_power_efivar, NULL, &package_size, package); + if (err) { + IWL_DEBUG_FW(trans, + "Reduced Power UEFI variable not found %d (len %lu)\n", + err, package_size); + kfree(package); + data = ERR_PTR(err); + goto out; + } + + IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n", + package_size); + *len = package_size; + + IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n", + package->rev, package->total_size, package->n_skus); + + data = iwl_uefi_reduce_power_parse(trans, package->data, + *len - sizeof(*package)); + + kfree(package); + +out: + kfree(reduce_power_efivar); + + return data; +} diff --git a/sys/contrib/dev/iwlwifi/fw/uefi.h b/sys/contrib/dev/iwlwifi/fw/uefi.h new file mode 100644 index 00000000000..d552c656ac9 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/fw/uefi.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright(c) 2021 Intel Corporation + */ +#ifndef __iwl_fw_uefi__ +#define __iwl_fw_uefi__ + +#define IWL_UEFI_OEM_PNVM_NAME L"UefiCnvWlanOemSignedPnvm" +#define IWL_UEFI_REDUCED_POWER_NAME L"UefiCnvWlanReducedPower" + +/* + * TODO: we have these hardcoded values that the caller must pass, + * because reading from the UEFI is not working. To implement this + * properly, we have to change iwl_pnvm_get_from_uefi() to call + * efivar_entry_size() and return the value to the caller instead. + */ +#define IWL_HARDCODED_PNVM_SIZE 4096 +#define IWL_HARDCODED_REDUCE_POWER_SIZE 32768 + +struct pnvm_sku_package { + u8 rev; + u32 total_size; + u8 n_skus; + u32 reserved[2]; + u8 data[]; +} __packed; + +#ifdef CONFIG_EFI +void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len); +void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len); +#else /* CONFIG_EFI */ +static inline +void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static inline +void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len) +{ + return ERR_PTR(-EOPNOTSUPP); +} +#endif /* CONFIG_EFI */ + +#endif /* __iwl_fw_uefi__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-agn-hw.h b/sys/contrib/dev/iwlwifi/iwl-agn-hw.h new file mode 100644 index 00000000000..ee85f460ae2 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-agn-hw.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + */ +/* + * Please use this file (iwl-agn-hw.h) only for hardware-related definitions. + */ + +#ifndef __iwl_agn_hw_h__ +#define __iwl_agn_hw_h__ + +#define IWLAGN_RTC_INST_LOWER_BOUND (0x000000) +#define IWLAGN_RTC_INST_UPPER_BOUND (0x020000) + +#define IWLAGN_RTC_DATA_LOWER_BOUND (0x800000) +#define IWLAGN_RTC_DATA_UPPER_BOUND (0x80C000) + +#define IWLAGN_RTC_INST_SIZE (IWLAGN_RTC_INST_UPPER_BOUND - \ + IWLAGN_RTC_INST_LOWER_BOUND) +#define IWLAGN_RTC_DATA_SIZE (IWLAGN_RTC_DATA_UPPER_BOUND - \ + IWLAGN_RTC_DATA_LOWER_BOUND) + +#define IWL60_RTC_INST_LOWER_BOUND (0x000000) +#define IWL60_RTC_INST_UPPER_BOUND (0x040000) +#define IWL60_RTC_DATA_LOWER_BOUND (0x800000) +#define IWL60_RTC_DATA_UPPER_BOUND (0x814000) +#define IWL60_RTC_INST_SIZE \ + (IWL60_RTC_INST_UPPER_BOUND - IWL60_RTC_INST_LOWER_BOUND) +#define IWL60_RTC_DATA_SIZE \ + (IWL60_RTC_DATA_UPPER_BOUND - IWL60_RTC_DATA_LOWER_BOUND) + +/* RSSI to dBm */ +#define IWLAGN_RSSI_OFFSET 44 + +#define IWLAGN_DEFAULT_TX_RETRY 15 +#define IWLAGN_MGMT_DFAULT_RETRY_LIMIT 3 +#define IWLAGN_RTS_DFAULT_RETRY_LIMIT 60 +#define IWLAGN_BAR_DFAULT_RETRY_LIMIT 60 +#define IWLAGN_LOW_RETRY_LIMIT 7 + +/* Limit range of txpower output target to be between these values */ +#define IWLAGN_TX_POWER_TARGET_POWER_MIN (0) /* 0 dBm: 1 milliwatt */ +#define IWLAGN_TX_POWER_TARGET_POWER_MAX (16) /* 16 dBm */ + +/* EEPROM */ +#define IWLAGN_EEPROM_IMG_SIZE 2048 + +/* high blocks contain PAPD data */ +#define OTP_HIGH_IMAGE_SIZE_6x00 (6 * 512 * sizeof(u16)) /* 6 KB */ +#define OTP_HIGH_IMAGE_SIZE_1000 (0x200 * sizeof(u16)) /* 1024 bytes */ +#define OTP_MAX_LL_ITEMS_1000 (3) /* OTP blocks for 1000 */ +#define OTP_MAX_LL_ITEMS_6x00 (4) /* OTP blocks for 6x00 */ +#define OTP_MAX_LL_ITEMS_6x50 (7) /* OTP blocks for 6x50 */ +#define OTP_MAX_LL_ITEMS_2x00 (4) /* OTP blocks for 2x00 */ + + +#define IWLAGN_NUM_QUEUES 20 + +#endif /* __iwl_agn_hw_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-config.h b/sys/contrib/dev/iwlwifi/iwl-config.h new file mode 100644 index 00000000000..665167a223f --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-config.h @@ -0,0 +1,639 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __IWL_CONFIG_H__ +#define __IWL_CONFIG_H__ + +#include +#include +#include +#include +#include "iwl-csr.h" + +enum iwl_device_family { + IWL_DEVICE_FAMILY_UNDEFINED, + IWL_DEVICE_FAMILY_1000, + IWL_DEVICE_FAMILY_100, + IWL_DEVICE_FAMILY_2000, + IWL_DEVICE_FAMILY_2030, + IWL_DEVICE_FAMILY_105, + IWL_DEVICE_FAMILY_135, + IWL_DEVICE_FAMILY_5000, + IWL_DEVICE_FAMILY_5150, + IWL_DEVICE_FAMILY_6000, + IWL_DEVICE_FAMILY_6000i, + IWL_DEVICE_FAMILY_6005, + IWL_DEVICE_FAMILY_6030, + IWL_DEVICE_FAMILY_6050, + IWL_DEVICE_FAMILY_6150, + IWL_DEVICE_FAMILY_7000, + IWL_DEVICE_FAMILY_8000, + IWL_DEVICE_FAMILY_9000, + IWL_DEVICE_FAMILY_22000, + IWL_DEVICE_FAMILY_AX210, + IWL_DEVICE_FAMILY_BZ, +}; + +/* + * LED mode + * IWL_LED_DEFAULT: use device default + * IWL_LED_RF_STATE: turn LED on/off based on RF state + * LED ON = RF ON + * LED OFF = RF OFF + * IWL_LED_BLINK: adjust led blink rate based on blink table + * IWL_LED_DISABLE: led disabled + */ +enum iwl_led_mode { + IWL_LED_DEFAULT, + IWL_LED_RF_STATE, + IWL_LED_BLINK, + IWL_LED_DISABLE, +}; + +/** + * enum iwl_nvm_type - nvm formats + * @IWL_NVM: the regular format + * @IWL_NVM_EXT: extended NVM format + * @IWL_NVM_SDP: NVM format used by 3168 series + */ +enum iwl_nvm_type { + IWL_NVM, + IWL_NVM_EXT, + IWL_NVM_SDP, +}; + +/* + * This is the threshold value of plcp error rate per 100mSecs. It is + * used to set and check for the validity of plcp_delta. + */ +#define IWL_MAX_PLCP_ERR_THRESHOLD_MIN 1 +#define IWL_MAX_PLCP_ERR_THRESHOLD_DEF 50 +#define IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF 100 +#define IWL_MAX_PLCP_ERR_EXT_LONG_THRESHOLD_DEF 200 +#define IWL_MAX_PLCP_ERR_THRESHOLD_MAX 255 +#define IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE 0 + +/* TX queue watchdog timeouts in mSecs */ +#define IWL_WATCHDOG_DISABLED 0 +#define IWL_DEF_WD_TIMEOUT 2500 +#define IWL_LONG_WD_TIMEOUT 10000 +#define IWL_MAX_WD_TIMEOUT 120000 + +#define IWL_DEFAULT_MAX_TX_POWER 22 +#define IWL_TX_CSUM_NETIF_FLAGS (NETIF_F_IPV6_CSUM | NETIF_F_IP_CSUM |\ + NETIF_F_TSO | NETIF_F_TSO6) + +/* Antenna presence definitions */ +#define ANT_NONE 0x0 +#define ANT_INVALID 0xff +#define ANT_A BIT(0) +#define ANT_B BIT(1) +#define ANT_C BIT(2) +#define ANT_AB (ANT_A | ANT_B) +#define ANT_AC (ANT_A | ANT_C) +#define ANT_BC (ANT_B | ANT_C) +#define ANT_ABC (ANT_A | ANT_B | ANT_C) + + +static inline u8 num_of_ant(u8 mask) +{ + return !!((mask) & ANT_A) + + !!((mask) & ANT_B) + + !!((mask) & ANT_C); +} + +/** + * struct iwl_base_params - params not likely to change within a device family + * @max_ll_items: max number of OTP blocks + * @shadow_ram_support: shadow support for OTP memory + * @led_compensation: compensate on the led on/off time per HW according + * to the deviation to achieve the desired led frequency. + * The detail algorithm is described in iwl-led.c + * @wd_timeout: TX queues watchdog timeout + * @max_event_log_size: size of event log buffer size for ucode event logging + * @shadow_reg_enable: HW shadow register support + * @apmg_wake_up_wa: should the MAC access REQ be asserted when a command + * is in flight. This is due to a HW bug in 7260, 3160 and 7265. + * @scd_chain_ext_wa: should the chain extension feature in SCD be disabled. + * @max_tfd_queue_size: max number of entries in tfd queue. + */ +struct iwl_base_params { + unsigned int wd_timeout; + + u16 eeprom_size; + u16 max_event_log_size; + + u8 pll_cfg:1, /* for iwl_pcie_apm_init() */ + shadow_ram_support:1, + shadow_reg_enable:1, + pcie_l1_allowed:1, + apmg_wake_up_wa:1, + scd_chain_ext_wa:1; + + u16 num_of_queues; /* def: HW dependent */ + u32 max_tfd_queue_size; /* def: HW dependent */ + + u8 max_ll_items; + u8 led_compensation; +}; + +/* + * @stbc: support Tx STBC and 1*SS Rx STBC + * @ldpc: support Tx/Rx with LDPC + * @use_rts_for_aggregation: use rts/cts protection for HT traffic + * @ht40_bands: bitmap of bands (using %NL80211_BAND_*) that support HT40 + */ +struct iwl_ht_params { + u8 ht_greenfield_support:1, + stbc:1, + ldpc:1, + use_rts_for_aggregation:1; + u8 ht40_bands; +}; + +/* + * Tx-backoff threshold + * @temperature: The threshold in Celsius + * @backoff: The tx-backoff in uSec + */ +struct iwl_tt_tx_backoff { + s32 temperature; + u32 backoff; +}; + +#define TT_TX_BACKOFF_SIZE 6 + +/** + * struct iwl_tt_params - thermal throttling parameters + * @ct_kill_entry: CT Kill entry threshold + * @ct_kill_exit: CT Kill exit threshold + * @ct_kill_duration: The time intervals (in uSec) in which the driver needs + * to checks whether to exit CT Kill. + * @dynamic_smps_entry: Dynamic SMPS entry threshold + * @dynamic_smps_exit: Dynamic SMPS exit threshold + * @tx_protection_entry: TX protection entry threshold + * @tx_protection_exit: TX protection exit threshold + * @tx_backoff: Array of thresholds for tx-backoff , in ascending order. + * @support_ct_kill: Support CT Kill? + * @support_dynamic_smps: Support dynamic SMPS? + * @support_tx_protection: Support tx protection? + * @support_tx_backoff: Support tx-backoff? + */ +struct iwl_tt_params { + u32 ct_kill_entry; + u32 ct_kill_exit; + u32 ct_kill_duration; + u32 dynamic_smps_entry; + u32 dynamic_smps_exit; + u32 tx_protection_entry; + u32 tx_protection_exit; + struct iwl_tt_tx_backoff tx_backoff[TT_TX_BACKOFF_SIZE]; + u8 support_ct_kill:1, + support_dynamic_smps:1, + support_tx_protection:1, + support_tx_backoff:1; +}; + +/* + * information on how to parse the EEPROM + */ +#define EEPROM_REG_BAND_1_CHANNELS 0x08 +#define EEPROM_REG_BAND_2_CHANNELS 0x26 +#define EEPROM_REG_BAND_3_CHANNELS 0x42 +#define EEPROM_REG_BAND_4_CHANNELS 0x5C +#define EEPROM_REG_BAND_5_CHANNELS 0x74 +#define EEPROM_REG_BAND_24_HT40_CHANNELS 0x82 +#define EEPROM_REG_BAND_52_HT40_CHANNELS 0x92 +#define EEPROM_6000_REG_BAND_24_HT40_CHANNELS 0x80 +#define EEPROM_REGULATORY_BAND_NO_HT40 0 + +/* lower blocks contain EEPROM image and calibration data */ +#define OTP_LOW_IMAGE_SIZE_2K (2 * 512 * sizeof(u16)) /* 2 KB */ +#define OTP_LOW_IMAGE_SIZE_16K (16 * 512 * sizeof(u16)) /* 16 KB */ +#define OTP_LOW_IMAGE_SIZE_32K (32 * 512 * sizeof(u16)) /* 32 KB */ + +struct iwl_eeprom_params { + const u8 regulatory_bands[7]; + bool enhanced_txpower; +}; + +/* Tx-backoff power threshold + * @pwr: The power limit in mw + * @backoff: The tx-backoff in uSec + */ +struct iwl_pwr_tx_backoff { + u32 pwr; + u32 backoff; +}; + +enum iwl_cfg_trans_ltr_delay { + IWL_CFG_TRANS_LTR_DELAY_NONE = 0, + IWL_CFG_TRANS_LTR_DELAY_200US = 1, + IWL_CFG_TRANS_LTR_DELAY_2500US = 2, + IWL_CFG_TRANS_LTR_DELAY_1820US = 3, +}; + +/** + * struct iwl_cfg_trans - information needed to start the trans + * + * These values are specific to the device ID and do not change when + * multiple configs are used for a single device ID. They values are + * used, among other things, to boot the NIC so that the HW REV or + * RFID can be read before deciding the remaining parameters to use. + * + * @base_params: pointer to basic parameters + * @csr: csr flags and addresses that are different across devices + * @device_family: the device family + * @umac_prph_offset: offset to add to UMAC periphery address + * @xtal_latency: power up latency to get the xtal stabilized + * @extra_phy_cfg_flags: extra configuration flags to pass to the PHY + * @rf_id: need to read rf_id to determine the firmware image + * @use_tfh: use TFH + * @gen2: 22000 and on transport operation + * @mq_rx_supported: multi-queue rx support + * @integrated: discrete or integrated + * @low_latency_xtal: use the low latency xtal if supported + * @ltr_delay: LTR delay parameter, &enum iwl_cfg_trans_ltr_delay. + */ +struct iwl_cfg_trans_params { + const struct iwl_base_params *base_params; + enum iwl_device_family device_family; + u32 umac_prph_offset; + u32 xtal_latency; + u32 extra_phy_cfg_flags; + u32 rf_id:1, + use_tfh:1, + gen2:1, + mq_rx_supported:1, + integrated:1, + low_latency_xtal:1, + bisr_workaround:1, + ltr_delay:2; +}; + +/** + * struct iwl_fw_mon_reg - FW monitor register info + * @addr: register address + * @mask: register mask + */ +struct iwl_fw_mon_reg { + u32 addr; + u32 mask; +}; + +/** + * struct iwl_fw_mon_regs - FW monitor registers + * @write_ptr: write pointer register + * @cycle_cnt: cycle count register + * @cur_frag: current fragment in use + */ +struct iwl_fw_mon_regs { + struct iwl_fw_mon_reg write_ptr; + struct iwl_fw_mon_reg cycle_cnt; + struct iwl_fw_mon_reg cur_frag; +}; + +/** + * struct iwl_cfg + * @trans: the trans-specific configuration part + * @name: Official name of the device + * @fw_name_pre: Firmware filename prefix. The api version and extension + * (.ucode) will be added to filename before loading from disk. The + * filename is constructed as fw_name_pre.ucode. + * @ucode_api_max: Highest version of uCode API supported by driver. + * @ucode_api_min: Lowest version of uCode API supported by driver. + * @max_inst_size: The maximal length of the fw inst section (only DVM) + * @max_data_size: The maximal length of the fw data section (only DVM) + * @valid_tx_ant: valid transmit antenna + * @valid_rx_ant: valid receive antenna + * @non_shared_ant: the antenna that is for WiFi only + * @nvm_ver: NVM version + * @nvm_calib_ver: NVM calibration version + * @lib: pointer to the lib ops + * @ht_params: point to ht parameters + * @led_mode: 0=blinking, 1=On(RF On)/Off(RF Off) + * @rx_with_siso_diversity: 1x1 device with rx antenna diversity + * @tx_with_siso_diversity: 1x1 device with tx antenna diversity + * @internal_wimax_coex: internal wifi/wimax combo device + * @high_temp: Is this NIC is designated to be in high temperature. + * @host_interrupt_operation_mode: device needs host interrupt operation + * mode set + * @nvm_hw_section_num: the ID of the HW NVM section + * @mac_addr_from_csr: read HW address from CSR registers at this offset + * @features: hw features, any combination of feature_passlist + * @pwr_tx_backoffs: translation table between power limits and backoffs + * @max_tx_agg_size: max TX aggregation size of the ADDBA request/response + * @dccm_offset: offset from which DCCM begins + * @dccm_len: length of DCCM (including runtime stack CCM) + * @dccm2_offset: offset from which the second DCCM begins + * @dccm2_len: length of the second DCCM + * @smem_offset: offset from which the SMEM begins + * @smem_len: the length of SMEM + * @vht_mu_mimo_supported: VHT MU-MIMO support + * @cdb: CDB support + * @nvm_type: see &enum iwl_nvm_type + * @d3_debug_data_base_addr: base address where D3 debug data is stored + * @d3_debug_data_length: length of the D3 debug data + * @bisr_workaround: BISR hardware workaround (for 22260 series devices) + * @min_txq_size: minimum number of slots required in a TX queue + * @uhb_supported: ultra high band channels supported + * @min_256_ba_txq_size: minimum number of slots required in a TX queue which + * supports 256 BA aggregation + * @num_rbds: number of receive buffer descriptors to use + * (only used for multi-queue capable devices) + * @mac_addr_csr_base: CSR base register for MAC address access, if not set + * assume 0x380 + * + * We enable the driver to be backward compatible wrt. hardware features. + * API differences in uCode shouldn't be handled here but through TLVs + * and/or the uCode API version instead. + */ +struct iwl_cfg { + struct iwl_cfg_trans_params trans; + /* params specific to an individual device within a device family */ + const char *name; + const char *fw_name_pre; + /* params likely to change within a device family */ + const struct iwl_ht_params *ht_params; + const struct iwl_eeprom_params *eeprom_params; + const struct iwl_pwr_tx_backoff *pwr_tx_backoffs; + const char *default_nvm_file_C_step; + const struct iwl_tt_params *thermal_params; + enum iwl_led_mode led_mode; + enum iwl_nvm_type nvm_type; + u32 max_data_size; + u32 max_inst_size; + netdev_features_t features; + u32 dccm_offset; + u32 dccm_len; + u32 dccm2_offset; + u32 dccm2_len; + u32 smem_offset; + u32 smem_len; + u16 nvm_ver; + u16 nvm_calib_ver; + u32 rx_with_siso_diversity:1, + tx_with_siso_diversity:1, + bt_shared_single_ant:1, + internal_wimax_coex:1, + host_interrupt_operation_mode:1, + high_temp:1, + mac_addr_from_csr:10, + lp_xtal_workaround:1, + disable_dummy_notification:1, + apmg_not_supported:1, + vht_mu_mimo_supported:1, + cdb:1, + dbgc_supported:1, + uhb_supported:1; + u8 valid_tx_ant; + u8 valid_rx_ant; + u8 non_shared_ant; + u8 nvm_hw_section_num; + u8 max_tx_agg_size; + u8 ucode_api_max; + u8 ucode_api_min; + u16 num_rbds; + u32 min_umac_error_event_table; + u32 d3_debug_data_base_addr; + u32 d3_debug_data_length; + u32 min_txq_size; + u32 gp2_reg_addr; + u32 min_256_ba_txq_size; + const struct iwl_fw_mon_regs mon_dram_regs; + const struct iwl_fw_mon_regs mon_smem_regs; +}; + +#define IWL_CFG_ANY (~0) + +#define IWL_CFG_MAC_TYPE_PU 0x31 +#define IWL_CFG_MAC_TYPE_PNJ 0x32 +#define IWL_CFG_MAC_TYPE_TH 0x32 +#define IWL_CFG_MAC_TYPE_QU 0x33 +#define IWL_CFG_MAC_TYPE_QUZ 0x35 +#define IWL_CFG_MAC_TYPE_QNJ 0x36 +#define IWL_CFG_MAC_TYPE_SO 0x37 +#define IWL_CFG_MAC_TYPE_SNJ 0x42 +#define IWL_CFG_MAC_TYPE_SOF 0x43 +#define IWL_CFG_MAC_TYPE_MA 0x44 +#define IWL_CFG_MAC_TYPE_BZ 0x46 +#define IWL_CFG_MAC_TYPE_GL 0x47 + +#define IWL_CFG_RF_TYPE_TH 0x105 +#define IWL_CFG_RF_TYPE_TH1 0x108 +#define IWL_CFG_RF_TYPE_JF2 0x105 +#define IWL_CFG_RF_TYPE_JF1 0x108 +#define IWL_CFG_RF_TYPE_HR2 0x10A +#define IWL_CFG_RF_TYPE_HR1 0x10C +#define IWL_CFG_RF_TYPE_GF 0x10D +#define IWL_CFG_RF_TYPE_MR 0x110 +#define IWL_CFG_RF_TYPE_FM 0x112 + +#define IWL_CFG_RF_ID_TH 0x1 +#define IWL_CFG_RF_ID_TH1 0x1 +#define IWL_CFG_RF_ID_JF 0x3 +#define IWL_CFG_RF_ID_JF1 0x6 +#define IWL_CFG_RF_ID_JF1_DIV 0xA +#define IWL_CFG_RF_ID_HR 0x7 +#define IWL_CFG_RF_ID_HR1 0x4 + +#define IWL_CFG_NO_160 0x1 +#define IWL_CFG_160 0x0 + +#define IWL_CFG_CORES_BT 0x0 +#define IWL_CFG_CORES_BT_GNSS 0x5 + +#define IWL_CFG_NO_CDB 0x0 +#define IWL_CFG_CDB 0x1 + +#define IWL_SUBDEVICE_RF_ID(subdevice) ((u16)((subdevice) & 0x00F0) >> 4) +#define IWL_SUBDEVICE_NO_160(subdevice) ((u16)((subdevice) & 0x0200) >> 9) +#define IWL_SUBDEVICE_CORES(subdevice) ((u16)((subdevice) & 0x1C00) >> 10) + +struct iwl_dev_info { + u16 device; + u16 subdevice; + u16 mac_type; + u16 rf_type; + u8 mac_step; + u8 rf_id; + u8 no_160; + u8 cores; + u8 cdb; + const struct iwl_cfg *cfg; + const char *name; +}; + +/* + * This list declares the config structures for all devices. + */ +extern const struct iwl_cfg_trans_params iwl9000_trans_cfg; +extern const struct iwl_cfg_trans_params iwl9560_trans_cfg; +extern const struct iwl_cfg_trans_params iwl9560_long_latency_trans_cfg; +extern const struct iwl_cfg_trans_params iwl9560_shared_clk_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_qnj_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_qu_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_qu_medium_latency_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_qu_long_latency_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_ax200_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_snj_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_so_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_so_long_latency_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_ma_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_bz_trans_cfg; +extern const char iwl9162_name[]; +extern const char iwl9260_name[]; +extern const char iwl9260_1_name[]; +extern const char iwl9270_name[]; +extern const char iwl9461_name[]; +extern const char iwl9462_name[]; +extern const char iwl9560_name[]; +extern const char iwl9162_160_name[]; +extern const char iwl9260_160_name[]; +extern const char iwl9270_160_name[]; +extern const char iwl9461_160_name[]; +extern const char iwl9462_160_name[]; +extern const char iwl9560_160_name[]; +extern const char iwl9260_killer_1550_name[]; +extern const char iwl9560_killer_1550i_name[]; +extern const char iwl9560_killer_1550s_name[]; +extern const char iwl_ax200_name[]; +extern const char iwl_ax203_name[]; +extern const char iwl_ax201_name[]; +extern const char iwl_ax101_name[]; +extern const char iwl_ax200_killer_1650w_name[]; +extern const char iwl_ax200_killer_1650x_name[]; +extern const char iwl_ax201_killer_1650s_name[]; +extern const char iwl_ax201_killer_1650i_name[]; +extern const char iwl_ax210_killer_1675w_name[]; +extern const char iwl_ax210_killer_1675x_name[]; +extern const char iwl9560_killer_1550i_160_name[]; +extern const char iwl9560_killer_1550s_160_name[]; +extern const char iwl_ax211_killer_1675s_name[]; +extern const char iwl_ax211_killer_1675i_name[]; +extern const char iwl_ax411_killer_1690s_name[]; +extern const char iwl_ax411_killer_1690i_name[]; +extern const char iwl_ax211_name[]; +extern const char iwl_ax221_name[]; +extern const char iwl_ax231_name[]; +extern const char iwl_ax411_name[]; +extern const char iwl_bz_name[]; +#if IS_ENABLED(CONFIG_IWLDVM) +extern const struct iwl_cfg iwl5300_agn_cfg; +extern const struct iwl_cfg iwl5100_agn_cfg; +extern const struct iwl_cfg iwl5350_agn_cfg; +extern const struct iwl_cfg iwl5100_bgn_cfg; +extern const struct iwl_cfg iwl5100_abg_cfg; +extern const struct iwl_cfg iwl5150_agn_cfg; +extern const struct iwl_cfg iwl5150_abg_cfg; +extern const struct iwl_cfg iwl6005_2agn_cfg; +extern const struct iwl_cfg iwl6005_2abg_cfg; +extern const struct iwl_cfg iwl6005_2bg_cfg; +extern const struct iwl_cfg iwl6005_2agn_sff_cfg; +extern const struct iwl_cfg iwl6005_2agn_d_cfg; +extern const struct iwl_cfg iwl6005_2agn_mow1_cfg; +extern const struct iwl_cfg iwl6005_2agn_mow2_cfg; +extern const struct iwl_cfg iwl1030_bgn_cfg; +extern const struct iwl_cfg iwl1030_bg_cfg; +extern const struct iwl_cfg iwl6030_2agn_cfg; +extern const struct iwl_cfg iwl6030_2abg_cfg; +extern const struct iwl_cfg iwl6030_2bgn_cfg; +extern const struct iwl_cfg iwl6030_2bg_cfg; +extern const struct iwl_cfg iwl6000i_2agn_cfg; +extern const struct iwl_cfg iwl6000i_2abg_cfg; +extern const struct iwl_cfg iwl6000i_2bg_cfg; +extern const struct iwl_cfg iwl6000_3agn_cfg; +extern const struct iwl_cfg iwl6050_2agn_cfg; +extern const struct iwl_cfg iwl6050_2abg_cfg; +extern const struct iwl_cfg iwl6150_bgn_cfg; +extern const struct iwl_cfg iwl6150_bg_cfg; +extern const struct iwl_cfg iwl1000_bgn_cfg; +extern const struct iwl_cfg iwl1000_bg_cfg; +extern const struct iwl_cfg iwl100_bgn_cfg; +extern const struct iwl_cfg iwl100_bg_cfg; +extern const struct iwl_cfg iwl130_bgn_cfg; +extern const struct iwl_cfg iwl130_bg_cfg; +extern const struct iwl_cfg iwl2000_2bgn_cfg; +extern const struct iwl_cfg iwl2000_2bgn_d_cfg; +extern const struct iwl_cfg iwl2030_2bgn_cfg; +extern const struct iwl_cfg iwl6035_2agn_cfg; +extern const struct iwl_cfg iwl6035_2agn_sff_cfg; +extern const struct iwl_cfg iwl105_bgn_cfg; +extern const struct iwl_cfg iwl105_bgn_d_cfg; +extern const struct iwl_cfg iwl135_bgn_cfg; +#endif /* CONFIG_IWLDVM */ +#if IS_ENABLED(CONFIG_IWLMVM) +extern const struct iwl_cfg iwl7260_2ac_cfg; +extern const struct iwl_cfg iwl7260_2ac_cfg_high_temp; +extern const struct iwl_cfg iwl7260_2n_cfg; +extern const struct iwl_cfg iwl7260_n_cfg; +extern const struct iwl_cfg iwl3160_2ac_cfg; +extern const struct iwl_cfg iwl3160_2n_cfg; +extern const struct iwl_cfg iwl3160_n_cfg; +extern const struct iwl_cfg iwl3165_2ac_cfg; +extern const struct iwl_cfg iwl3168_2ac_cfg; +extern const struct iwl_cfg iwl7265_2ac_cfg; +extern const struct iwl_cfg iwl7265_2n_cfg; +extern const struct iwl_cfg iwl7265_n_cfg; +extern const struct iwl_cfg iwl7265d_2ac_cfg; +extern const struct iwl_cfg iwl7265d_2n_cfg; +extern const struct iwl_cfg iwl7265d_n_cfg; +extern const struct iwl_cfg iwl8260_2n_cfg; +extern const struct iwl_cfg iwl8260_2ac_cfg; +extern const struct iwl_cfg iwl8265_2ac_cfg; +extern const struct iwl_cfg iwl8275_2ac_cfg; +extern const struct iwl_cfg iwl4165_2ac_cfg; +extern const struct iwl_cfg iwl9260_2ac_cfg; +extern const struct iwl_cfg iwl9560_qu_b0_jf_b0_cfg; +extern const struct iwl_cfg iwl9560_qu_c0_jf_b0_cfg; +extern const struct iwl_cfg iwl9560_quz_a0_jf_b0_cfg; +extern const struct iwl_cfg iwl9560_qnj_b0_jf_b0_cfg; +extern const struct iwl_cfg iwl9560_2ac_cfg_soc; +extern const struct iwl_cfg iwl_qu_b0_hr1_b0; +extern const struct iwl_cfg iwl_qu_c0_hr1_b0; +extern const struct iwl_cfg iwl_quz_a0_hr1_b0; +extern const struct iwl_cfg iwl_qu_b0_hr_b0; +extern const struct iwl_cfg iwl_qu_c0_hr_b0; +extern const struct iwl_cfg iwl_ax200_cfg_cc; +extern const struct iwl_cfg iwl_ax201_cfg_qu_hr; +extern const struct iwl_cfg iwl_ax201_cfg_qu_c0_hr_b0; +extern const struct iwl_cfg iwl_ax201_cfg_quz_hr; +extern const struct iwl_cfg iwl_ax1650i_cfg_quz_hr; +extern const struct iwl_cfg iwl_ax1650s_cfg_quz_hr; +extern const struct iwl_cfg killer1650s_2ax_cfg_qu_b0_hr_b0; +extern const struct iwl_cfg killer1650i_2ax_cfg_qu_b0_hr_b0; +extern const struct iwl_cfg killer1650s_2ax_cfg_qu_c0_hr_b0; +extern const struct iwl_cfg killer1650i_2ax_cfg_qu_c0_hr_b0; +extern const struct iwl_cfg killer1650x_2ax_cfg; +extern const struct iwl_cfg killer1650w_2ax_cfg; +extern const struct iwl_cfg iwl_qnj_b0_hr_b0_cfg; +extern const struct iwl_cfg iwlax210_2ax_cfg_so_jf_b0; +extern const struct iwl_cfg iwlax210_2ax_cfg_so_hr_a0; +extern const struct iwl_cfg iwlax211_2ax_cfg_so_gf_a0; +extern const struct iwl_cfg iwlax211_2ax_cfg_so_gf_a0_long; +extern const struct iwl_cfg iwlax210_2ax_cfg_ty_gf_a0; +extern const struct iwl_cfg iwlax411_2ax_cfg_so_gf4_a0; +extern const struct iwl_cfg iwlax411_2ax_cfg_so_gf4_a0_long; +extern const struct iwl_cfg iwlax411_2ax_cfg_sosnj_gf4_a0; +extern const struct iwl_cfg iwlax211_cfg_snj_gf_a0; +extern const struct iwl_cfg iwl_cfg_snj_hr_b0; +extern const struct iwl_cfg iwl_cfg_snj_a0_jf_b0; +extern const struct iwl_cfg iwl_cfg_ma_a0_hr_b0; +extern const struct iwl_cfg iwl_cfg_ma_a0_gf_a0; +extern const struct iwl_cfg iwl_cfg_ma_a0_gf4_a0; +extern const struct iwl_cfg iwl_cfg_ma_a0_mr_a0; +extern const struct iwl_cfg iwl_cfg_ma_a0_fm_a0; +extern const struct iwl_cfg iwl_cfg_snj_a0_mr_a0; +extern const struct iwl_cfg iwl_cfg_so_a0_hr_a0; +extern const struct iwl_cfg iwl_cfg_quz_a0_hr_b0; +extern const struct iwl_cfg iwl_cfg_bz_a0_hr_b0; +extern const struct iwl_cfg iwl_cfg_bz_a0_gf_a0; +extern const struct iwl_cfg iwl_cfg_bz_a0_gf4_a0; +extern const struct iwl_cfg iwl_cfg_bz_a0_mr_a0; +extern const struct iwl_cfg iwl_cfg_bz_a0_fm_a0; +extern const struct iwl_cfg iwl_cfg_gl_a0_fm_a0; +#endif /* CONFIG_IWLMVM */ + +#endif /* __IWL_CONFIG_H__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-context-info-gen3.h b/sys/contrib/dev/iwlwifi/iwl-context-info-gen3.h new file mode 100644 index 00000000000..5adf485db38 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-context-info-gen3.h @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2018, 2020-2021 Intel Corporation + */ +#ifndef __iwl_context_info_file_gen3_h__ +#define __iwl_context_info_file_gen3_h__ + +#include "iwl-context-info.h" + +#define CSR_CTXT_INFO_BOOT_CTRL 0x0 +#define CSR_CTXT_INFO_ADDR 0x118 +#define CSR_IML_DATA_ADDR 0x120 +#define CSR_IML_SIZE_ADDR 0x128 +#define CSR_IML_RESP_ADDR 0x12c + +/* Set bit for enabling automatic function boot */ +#define CSR_AUTO_FUNC_BOOT_ENA BIT(1) +/* Set bit for initiating function boot */ +#define CSR_AUTO_FUNC_INIT BIT(7) + +/** + * enum iwl_prph_scratch_mtr_format - tfd size configuration + * @IWL_PRPH_MTR_FORMAT_16B: 16 bit tfd + * @IWL_PRPH_MTR_FORMAT_32B: 32 bit tfd + * @IWL_PRPH_MTR_FORMAT_64B: 64 bit tfd + * @IWL_PRPH_MTR_FORMAT_256B: 256 bit tfd + */ +enum iwl_prph_scratch_mtr_format { + IWL_PRPH_MTR_FORMAT_16B = 0x0, + IWL_PRPH_MTR_FORMAT_32B = 0x40000, + IWL_PRPH_MTR_FORMAT_64B = 0x80000, + IWL_PRPH_MTR_FORMAT_256B = 0xC0000, +}; + +/** + * enum iwl_prph_scratch_flags - PRPH scratch control flags + * @IWL_PRPH_SCRATCH_EARLY_DEBUG_EN: enable early debug conf + * @IWL_PRPH_SCRATCH_EDBG_DEST_DRAM: use DRAM, with size allocated + * in hwm config. + * @IWL_PRPH_SCRATCH_EDBG_DEST_INTERNAL: use buffer on SRAM + * @IWL_PRPH_SCRATCH_EDBG_DEST_ST_ARBITER: use st arbiter, mainly for + * multicomm. + * @IWL_PRPH_SCRATCH_EDBG_DEST_TB22DTF: route debug data to SoC HW + * @IWL_PRPH_SCTATCH_RB_SIZE_4K: Use 4K RB size (the default is 2K) + * @IWL_PRPH_SCRATCH_MTR_MODE: format used for completion - 0: for + * completion descriptor, 1 for responses (legacy) + * @IWL_PRPH_SCRATCH_MTR_FORMAT: a mask for the size of the tfd. + * There are 4 optional values: 0: 16 bit, 1: 32 bit, 2: 64 bit, + * 3: 256 bit. + * @IWL_PRPH_SCRATCH_RB_SIZE_EXT_MASK: RB size full information, ignored + * by older firmware versions, so set IWL_PRPH_SCRATCH_RB_SIZE_4K + * appropriately; use the below values for this. + * @IWL_PRPH_SCRATCH_RB_SIZE_EXT_8K: 8kB RB size + * @IWL_PRPH_SCRATCH_RB_SIZE_EXT_12K: 12kB RB size + * @IWL_PRPH_SCRATCH_RB_SIZE_EXT_16K: 16kB RB size + */ +enum iwl_prph_scratch_flags { + IWL_PRPH_SCRATCH_EARLY_DEBUG_EN = BIT(4), + IWL_PRPH_SCRATCH_EDBG_DEST_DRAM = BIT(8), + IWL_PRPH_SCRATCH_EDBG_DEST_INTERNAL = BIT(9), + IWL_PRPH_SCRATCH_EDBG_DEST_ST_ARBITER = BIT(10), + IWL_PRPH_SCRATCH_EDBG_DEST_TB22DTF = BIT(11), + IWL_PRPH_SCRATCH_RB_SIZE_4K = BIT(16), + IWL_PRPH_SCRATCH_MTR_MODE = BIT(17), + IWL_PRPH_SCRATCH_MTR_FORMAT = BIT(18) | BIT(19), + IWL_PRPH_SCRATCH_RB_SIZE_EXT_MASK = 0xf << 20, + IWL_PRPH_SCRATCH_RB_SIZE_EXT_8K = 8 << 20, + IWL_PRPH_SCRATCH_RB_SIZE_EXT_12K = 9 << 20, + IWL_PRPH_SCRATCH_RB_SIZE_EXT_16K = 10 << 20, +}; + +/* + * struct iwl_prph_scratch_version - version structure + * @mac_id: SKU and revision id + * @version: prph scratch information version id + * @size: the size of the context information in DWs + * @reserved: reserved + */ +struct iwl_prph_scratch_version { + __le16 mac_id; + __le16 version; + __le16 size; + __le16 reserved; +} __packed; /* PERIPH_SCRATCH_VERSION_S */ + +/* + * struct iwl_prph_scratch_control - control structure + * @control_flags: context information flags see &enum iwl_prph_scratch_flags + * @reserved: reserved + */ +struct iwl_prph_scratch_control { + __le32 control_flags; + __le32 reserved; +} __packed; /* PERIPH_SCRATCH_CONTROL_S */ + +/* + * struct iwl_prph_scratch_pnvm_cfg - ror config + * @pnvm_base_addr: PNVM start address + * @pnvm_size: PNVM size in DWs + * @reserved: reserved + */ +struct iwl_prph_scratch_pnvm_cfg { + __le64 pnvm_base_addr; + __le32 pnvm_size; + __le32 reserved; +} __packed; /* PERIPH_SCRATCH_PNVM_CFG_S */ + +/* + * struct iwl_prph_scratch_hwm_cfg - hwm config + * @hwm_base_addr: hwm start address + * @hwm_size: hwm size in DWs + * @debug_token_config: debug preset + */ +struct iwl_prph_scratch_hwm_cfg { + __le64 hwm_base_addr; + __le32 hwm_size; + __le32 debug_token_config; +} __packed; /* PERIPH_SCRATCH_HWM_CFG_S */ + +/* + * struct iwl_prph_scratch_rbd_cfg - RBDs configuration + * @free_rbd_addr: default queue free RB CB base address + * @reserved: reserved + */ +struct iwl_prph_scratch_rbd_cfg { + __le64 free_rbd_addr; + __le32 reserved; +} __packed; /* PERIPH_SCRATCH_RBD_CFG_S */ + +/* + * struct iwl_prph_scratch_uefi_cfg - prph scratch reduce power table + * @base_addr: reduce power table address + * @size: table size in dwords + */ +struct iwl_prph_scratch_uefi_cfg { + __le64 base_addr; + __le32 size; + __le32 reserved; +} __packed; /* PERIPH_SCRATCH_UEFI_CFG_S */ + +/* + * struct iwl_prph_scratch_ctrl_cfg - prph scratch ctrl and config + * @version: version information of context info and HW + * @control: control flags of FH configurations + * @pnvm_cfg: ror configuration + * @hwm_cfg: hwm configuration + * @rbd_cfg: default RX queue configuration + */ +struct iwl_prph_scratch_ctrl_cfg { + struct iwl_prph_scratch_version version; + struct iwl_prph_scratch_control control; + struct iwl_prph_scratch_pnvm_cfg pnvm_cfg; + struct iwl_prph_scratch_hwm_cfg hwm_cfg; + struct iwl_prph_scratch_rbd_cfg rbd_cfg; + struct iwl_prph_scratch_uefi_cfg reduce_power_cfg; +} __packed; /* PERIPH_SCRATCH_CTRL_CFG_S */ + +/* + * struct iwl_prph_scratch - peripheral scratch mapping + * @ctrl_cfg: control and configuration of prph scratch + * @dram: firmware images addresses in DRAM + * @reserved: reserved + */ +struct iwl_prph_scratch { + struct iwl_prph_scratch_ctrl_cfg ctrl_cfg; + __le32 reserved[12]; + struct iwl_context_info_dram dram; +} __packed; /* PERIPH_SCRATCH_S */ + +/* + * struct iwl_prph_info - peripheral information + * @boot_stage_mirror: reflects the value in the Boot Stage CSR register + * @ipc_status_mirror: reflects the value in the IPC Status CSR register + * @sleep_notif: indicates the peripheral sleep status + * @reserved: reserved + */ +struct iwl_prph_info { + __le32 boot_stage_mirror; + __le32 ipc_status_mirror; + __le32 sleep_notif; + __le32 reserved; +} __packed; /* PERIPH_INFO_S */ + +/* + * struct iwl_context_info_gen3 - device INIT configuration + * @version: version of the context information + * @size: size of context information in DWs + * @config: context in which the peripheral would execute - a subset of + * capability csr register published by the peripheral + * @prph_info_base_addr: the peripheral information structure start address + * @cr_head_idx_arr_base_addr: the completion ring head index array + * start address + * @tr_tail_idx_arr_base_addr: the transfer ring tail index array + * start address + * @cr_tail_idx_arr_base_addr: the completion ring tail index array + * start address + * @tr_head_idx_arr_base_addr: the transfer ring head index array + * start address + * @cr_idx_arr_size: number of entries in the completion ring index array + * @tr_idx_arr_size: number of entries in the transfer ring index array + * @mtr_base_addr: the message transfer ring start address + * @mcr_base_addr: the message completion ring start address + * @mtr_size: number of entries which the message transfer ring can hold + * @mcr_size: number of entries which the message completion ring can hold + * @mtr_doorbell_vec: the doorbell vector associated with the message + * transfer ring + * @mcr_doorbell_vec: the doorbell vector associated with the message + * completion ring + * @mtr_msi_vec: the MSI which shall be generated by the peripheral after + * completing a transfer descriptor in the message transfer ring + * @mcr_msi_vec: the MSI which shall be generated by the peripheral after + * completing a completion descriptor in the message completion ring + * @mtr_opt_header_size: the size of the optional header in the transfer + * descriptor associated with the message transfer ring in DWs + * @mtr_opt_footer_size: the size of the optional footer in the transfer + * descriptor associated with the message transfer ring in DWs + * @mcr_opt_header_size: the size of the optional header in the completion + * descriptor associated with the message completion ring in DWs + * @mcr_opt_footer_size: the size of the optional footer in the completion + * descriptor associated with the message completion ring in DWs + * @msg_rings_ctrl_flags: message rings control flags + * @prph_info_msi_vec: the MSI which shall be generated by the peripheral + * after updating the Peripheral Information structure + * @prph_scratch_base_addr: the peripheral scratch structure start address + * @prph_scratch_size: the size of the peripheral scratch structure in DWs + * @reserved: reserved + */ +struct iwl_context_info_gen3 { + __le16 version; + __le16 size; + __le32 config; + __le64 prph_info_base_addr; + __le64 cr_head_idx_arr_base_addr; + __le64 tr_tail_idx_arr_base_addr; + __le64 cr_tail_idx_arr_base_addr; + __le64 tr_head_idx_arr_base_addr; + __le16 cr_idx_arr_size; + __le16 tr_idx_arr_size; + __le64 mtr_base_addr; + __le64 mcr_base_addr; + __le16 mtr_size; + __le16 mcr_size; + __le16 mtr_doorbell_vec; + __le16 mcr_doorbell_vec; + __le16 mtr_msi_vec; + __le16 mcr_msi_vec; + u8 mtr_opt_header_size; + u8 mtr_opt_footer_size; + u8 mcr_opt_header_size; + u8 mcr_opt_footer_size; + __le16 msg_rings_ctrl_flags; + __le16 prph_info_msi_vec; + __le64 prph_scratch_base_addr; + __le32 prph_scratch_size; + __le32 reserved; +} __packed; /* IPC_CONTEXT_INFO_S */ + +int iwl_pcie_ctxt_info_gen3_init(struct iwl_trans *trans, + const struct fw_img *fw); +void iwl_pcie_ctxt_info_gen3_free(struct iwl_trans *trans, bool alive); + +int iwl_trans_pcie_ctx_info_gen3_set_pnvm(struct iwl_trans *trans, + const void *data, u32 len); +int iwl_trans_pcie_ctx_info_gen3_set_reduce_power(struct iwl_trans *trans, + const void *data, u32 len); + +#endif /* __iwl_context_info_file_gen3_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-context-info.h b/sys/contrib/dev/iwlwifi/iwl-context-info.h new file mode 100644 index 00000000000..4354d5acac9 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-context-info.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2020 Intel Corporation + */ +#ifndef __iwl_context_info_file_h__ +#define __iwl_context_info_file_h__ + +/* maximmum number of DRAM map entries supported by FW */ +#define IWL_MAX_DRAM_ENTRY 64 +#define CSR_CTXT_INFO_BA 0x40 + +/** + * enum iwl_context_info_flags - Context information control flags + * @IWL_CTXT_INFO_AUTO_FUNC_INIT: If set, FW will not wait before interrupting + * the init done for driver command that configures several system modes + * @IWL_CTXT_INFO_EARLY_DEBUG: enable early debug + * @IWL_CTXT_INFO_ENABLE_CDMP: enable core dump + * @IWL_CTXT_INFO_RB_CB_SIZE: mask of the RBD Cyclic Buffer Size + * exponent, the actual size is 2**value, valid sizes are 8-2048. + * The value is four bits long. Maximum valid exponent is 12 + * @IWL_CTXT_INFO_TFD_FORMAT_LONG: use long TFD Format (the + * default is short format - not supported by the driver) + * @IWL_CTXT_INFO_RB_SIZE: RB size mask + * (values are IWL_CTXT_INFO_RB_SIZE_*K) + * @IWL_CTXT_INFO_RB_SIZE_1K: Value for 1K RB size + * @IWL_CTXT_INFO_RB_SIZE_2K: Value for 2K RB size + * @IWL_CTXT_INFO_RB_SIZE_4K: Value for 4K RB size + * @IWL_CTXT_INFO_RB_SIZE_8K: Value for 8K RB size + * @IWL_CTXT_INFO_RB_SIZE_12K: Value for 12K RB size + * @IWL_CTXT_INFO_RB_SIZE_16K: Value for 16K RB size + * @IWL_CTXT_INFO_RB_SIZE_20K: Value for 20K RB size + * @IWL_CTXT_INFO_RB_SIZE_24K: Value for 24K RB size + * @IWL_CTXT_INFO_RB_SIZE_28K: Value for 28K RB size + * @IWL_CTXT_INFO_RB_SIZE_32K: Value for 32K RB size + */ +enum iwl_context_info_flags { + IWL_CTXT_INFO_AUTO_FUNC_INIT = 0x0001, + IWL_CTXT_INFO_EARLY_DEBUG = 0x0002, + IWL_CTXT_INFO_ENABLE_CDMP = 0x0004, + IWL_CTXT_INFO_RB_CB_SIZE = 0x00f0, + IWL_CTXT_INFO_TFD_FORMAT_LONG = 0x0100, + IWL_CTXT_INFO_RB_SIZE = 0x1e00, + IWL_CTXT_INFO_RB_SIZE_1K = 0x1, + IWL_CTXT_INFO_RB_SIZE_2K = 0x2, + IWL_CTXT_INFO_RB_SIZE_4K = 0x4, + IWL_CTXT_INFO_RB_SIZE_8K = 0x8, + IWL_CTXT_INFO_RB_SIZE_12K = 0x9, + IWL_CTXT_INFO_RB_SIZE_16K = 0xa, + IWL_CTXT_INFO_RB_SIZE_20K = 0xb, + IWL_CTXT_INFO_RB_SIZE_24K = 0xc, + IWL_CTXT_INFO_RB_SIZE_28K = 0xd, + IWL_CTXT_INFO_RB_SIZE_32K = 0xe, +}; + +/* + * struct iwl_context_info_version - version structure + * @mac_id: SKU and revision id + * @version: context information version id + * @size: the size of the context information in DWs + */ +struct iwl_context_info_version { + __le16 mac_id; + __le16 version; + __le16 size; + __le16 reserved; +} __packed; + +/* + * struct iwl_context_info_control - version structure + * @control_flags: context information flags see &enum iwl_context_info_flags + */ +struct iwl_context_info_control { + __le32 control_flags; + __le32 reserved; +} __packed; + +/* + * struct iwl_context_info_dram - images DRAM map + * each entry in the map represents a DRAM chunk of up to 32 KB + * @umac_img: UMAC image DRAM map + * @lmac_img: LMAC image DRAM map + * @virtual_img: paged image DRAM map + */ +struct iwl_context_info_dram { + __le64 umac_img[IWL_MAX_DRAM_ENTRY]; + __le64 lmac_img[IWL_MAX_DRAM_ENTRY]; + __le64 virtual_img[IWL_MAX_DRAM_ENTRY]; +} __packed; + +/* + * struct iwl_context_info_rbd_cfg - RBDs configuration + * @free_rbd_addr: default queue free RB CB base address + * @used_rbd_addr: default queue used RB CB base address + * @status_wr_ptr: default queue used RB status write pointer + */ +struct iwl_context_info_rbd_cfg { + __le64 free_rbd_addr; + __le64 used_rbd_addr; + __le64 status_wr_ptr; +} __packed; + +/* + * struct iwl_context_info_hcmd_cfg - command queue configuration + * @cmd_queue_addr: address of command queue + * @cmd_queue_size: number of entries + */ +struct iwl_context_info_hcmd_cfg { + __le64 cmd_queue_addr; + u8 cmd_queue_size; + u8 reserved[7]; +} __packed; + +/* + * struct iwl_context_info_dump_cfg - Core Dump configuration + * @core_dump_addr: core dump (debug DRAM address) start address + * @core_dump_size: size, in DWs + */ +struct iwl_context_info_dump_cfg { + __le64 core_dump_addr; + __le32 core_dump_size; + __le32 reserved; +} __packed; + +/* + * struct iwl_context_info_pnvm_cfg - platform NVM data configuration + * @platform_nvm_addr: Platform NVM data start address + * @platform_nvm_size: size in DWs + */ +struct iwl_context_info_pnvm_cfg { + __le64 platform_nvm_addr; + __le32 platform_nvm_size; + __le32 reserved; +} __packed; + +/* + * struct iwl_context_info_early_dbg_cfg - early debug configuration for + * dumping DRAM addresses + * @early_debug_addr: early debug start address + * @early_debug_size: size in DWs + */ +struct iwl_context_info_early_dbg_cfg { + __le64 early_debug_addr; + __le32 early_debug_size; + __le32 reserved; +} __packed; + +/* + * struct iwl_context_info - device INIT configuration + * @version: version information of context info and HW + * @control: control flags of FH configurations + * @rbd_cfg: default RX queue configuration + * @hcmd_cfg: command queue configuration + * @dump_cfg: core dump data + * @edbg_cfg: early debug configuration + * @pnvm_cfg: platform nvm configuration + * @dram: firmware image addresses in DRAM + */ +struct iwl_context_info { + struct iwl_context_info_version version; + struct iwl_context_info_control control; + __le64 reserved0; + struct iwl_context_info_rbd_cfg rbd_cfg; + struct iwl_context_info_hcmd_cfg hcmd_cfg; + __le32 reserved1[4]; + struct iwl_context_info_dump_cfg dump_cfg; + struct iwl_context_info_early_dbg_cfg edbg_cfg; + struct iwl_context_info_pnvm_cfg pnvm_cfg; + __le32 reserved2[16]; + struct iwl_context_info_dram dram; + __le32 reserved3[16]; +} __packed; + +int iwl_pcie_ctxt_info_init(struct iwl_trans *trans, const struct fw_img *fw); +void iwl_pcie_ctxt_info_free(struct iwl_trans *trans); +void iwl_pcie_ctxt_info_free_paging(struct iwl_trans *trans); +int iwl_pcie_init_fw_sec(struct iwl_trans *trans, + const struct fw_img *fw, + struct iwl_context_info_dram *ctxt_dram); +int iwl_pcie_ctxt_info_alloc_dma(struct iwl_trans *trans, + const void *data, u32 len, + struct iwl_dram_data *dram); + +#endif /* __iwl_context_info_file_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-csr.h b/sys/contrib/dev/iwlwifi/iwl-csr.h new file mode 100644 index 00000000000..ff79a2ecb24 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-csr.h @@ -0,0 +1,633 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#ifndef __iwl_csr_h__ +#define __iwl_csr_h__ +/* + * CSR (control and status registers) + * + * CSR registers are mapped directly into PCI bus space, and are accessible + * whenever platform supplies power to device, even when device is in + * low power states due to driver-invoked device resets + * (e.g. CSR_RESET_REG_FLAG_SW_RESET) or uCode-driven power-saving modes. + * + * Use iwl_write32() and iwl_read32() family to access these registers; + * these provide simple PCI bus access, without waking up the MAC. + * Do not use iwl_write_direct32() family for these registers; + * no need to "grab nic access" via CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ. + * The MAC (uCode processor, etc.) does not need to be powered up for accessing + * the CSR registers. + * + * NOTE: Device does need to be awake in order to read this memory + * via CSR_EEPROM and CSR_OTP registers + */ +#define CSR_BASE (0x000) + +#define CSR_HW_IF_CONFIG_REG (CSR_BASE+0x000) /* hardware interface config */ +#define CSR_INT_COALESCING (CSR_BASE+0x004) /* accum ints, 32-usec units */ +#define CSR_INT (CSR_BASE+0x008) /* host interrupt status/ack */ +#define CSR_INT_MASK (CSR_BASE+0x00c) /* host interrupt enable */ +#define CSR_FH_INT_STATUS (CSR_BASE+0x010) /* busmaster int status/ack*/ +#define CSR_GPIO_IN (CSR_BASE+0x018) /* read external chip pins */ +#define CSR_RESET (CSR_BASE+0x020) /* busmaster enable, NMI, etc*/ +#define CSR_GP_CNTRL (CSR_BASE+0x024) +#define CSR_FUNC_SCRATCH (CSR_BASE+0x02c) /* Scratch register - used for FW dbg */ + +/* 2nd byte of CSR_INT_COALESCING, not accessible via iwl_write32()! */ +#define CSR_INT_PERIODIC_REG (CSR_BASE+0x005) + +/* + * Hardware revision info + * Bit fields: + * 31-16: Reserved + * 15-4: Type of device: see CSR_HW_REV_TYPE_xxx definitions + * 3-2: Revision step: 0 = A, 1 = B, 2 = C, 3 = D + * 1-0: "Dash" (-) value, as in A-1, etc. + */ +#define CSR_HW_REV (CSR_BASE+0x028) + +/* + * RF ID revision info + * Bit fields: + * 31:24: Reserved (set to 0x0) + * 23:12: Type + * 11:8: Step (A - 0x0, B - 0x1, etc) + * 7:4: Dash + * 3:0: Flavor + */ +#define CSR_HW_RF_ID (CSR_BASE+0x09c) + +/* + * EEPROM and OTP (one-time-programmable) memory reads + * + * NOTE: Device must be awake, initialized via apm_ops.init(), + * in order to read. + */ +#define CSR_EEPROM_REG (CSR_BASE+0x02c) +#define CSR_EEPROM_GP (CSR_BASE+0x030) +#define CSR_OTP_GP_REG (CSR_BASE+0x034) + +#define CSR_GIO_REG (CSR_BASE+0x03C) +#define CSR_GP_UCODE_REG (CSR_BASE+0x048) +#define CSR_GP_DRIVER_REG (CSR_BASE+0x050) + +/* + * UCODE-DRIVER GP (general purpose) mailbox registers. + * SET/CLR registers set/clear bit(s) if "1" is written. + */ +#define CSR_UCODE_DRV_GP1 (CSR_BASE+0x054) +#define CSR_UCODE_DRV_GP1_SET (CSR_BASE+0x058) +#define CSR_UCODE_DRV_GP1_CLR (CSR_BASE+0x05c) +#define CSR_UCODE_DRV_GP2 (CSR_BASE+0x060) + +#define CSR_MBOX_SET_REG (CSR_BASE + 0x88) + +#define CSR_LED_REG (CSR_BASE+0x094) +#define CSR_DRAM_INT_TBL_REG (CSR_BASE+0x0A0) +#define CSR_MAC_SHADOW_REG_CTRL (CSR_BASE + 0x0A8) /* 6000 and up */ +#define CSR_MAC_SHADOW_REG_CTRL_RX_WAKE BIT(20) +#define CSR_MAC_SHADOW_REG_CTL2 (CSR_BASE + 0x0AC) +#define CSR_MAC_SHADOW_REG_CTL2_RX_WAKE 0xFFFF + +/* LTR control (since IWL_DEVICE_FAMILY_22000) */ +#define CSR_LTR_LONG_VAL_AD (CSR_BASE + 0x0D4) +#define CSR_LTR_LONG_VAL_AD_NO_SNOOP_REQ 0x80000000 +#define CSR_LTR_LONG_VAL_AD_NO_SNOOP_SCALE 0x1c000000 +#define CSR_LTR_LONG_VAL_AD_NO_SNOOP_VAL 0x03ff0000 +#define CSR_LTR_LONG_VAL_AD_SNOOP_REQ 0x00008000 +#define CSR_LTR_LONG_VAL_AD_SNOOP_SCALE 0x00001c00 +#define CSR_LTR_LONG_VAL_AD_SNOOP_VAL 0x000003ff +#define CSR_LTR_LONG_VAL_AD_SCALE_USEC 2 + +/* GIO Chicken Bits (PCI Express bus link power management) */ +#define CSR_GIO_CHICKEN_BITS (CSR_BASE+0x100) + +/* Doorbell NMI (since Bz) */ +#define CSR_DOORBELL_VECTOR (CSR_BASE + 0x130) +#define CSR_DOORBELL_VECTOR_NMI BIT(1) + +/* host chicken bits */ +#define CSR_HOST_CHICKEN (CSR_BASE + 0x204) +#define CSR_HOST_CHICKEN_PM_IDLE_SRC_DIS_SB_PME BIT(19) + +/* Analog phase-lock-loop configuration */ +#define CSR_ANA_PLL_CFG (CSR_BASE+0x20c) + +/* + * CSR HW resources monitor registers + */ +#define CSR_MONITOR_CFG_REG (CSR_BASE+0x214) +#define CSR_MONITOR_STATUS_REG (CSR_BASE+0x228) +#define CSR_MONITOR_XTAL_RESOURCES (0x00000010) + +/* + * CSR Hardware Revision Workaround Register. Indicates hardware rev; + * "step" determines CCK backoff for txpower calculation. + * See also CSR_HW_REV register. + * Bit fields: + * 3-2: 0 = A, 1 = B, 2 = C, 3 = D step + * 1-0: "Dash" (-) value, as in C-1, etc. + */ +#define CSR_HW_REV_WA_REG (CSR_BASE+0x22C) + +#define CSR_DBG_HPET_MEM_REG (CSR_BASE+0x240) +#define CSR_DBG_LINK_PWR_MGMT_REG (CSR_BASE+0x250) + +/* + * Scratch register initial configuration - this is set on init, and read + * during a error FW error. + */ +#define CSR_FUNC_SCRATCH_INIT_VALUE (0x01010101) + +/* Bits for CSR_HW_IF_CONFIG_REG */ +#define CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH (0x00000003) +#define CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP (0x0000000C) +#define CSR_HW_IF_CONFIG_REG_BIT_MONITOR_SRAM (0x00000080) +#define CSR_HW_IF_CONFIG_REG_MSK_BOARD_VER (0x000000C0) +#define CSR_HW_IF_CONFIG_REG_BIT_MAC_SI (0x00000100) +#define CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI (0x00000200) +#define CSR_HW_IF_CONFIG_REG_D3_DEBUG (0x00000200) +#define CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE (0x00000C00) +#define CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH (0x00003000) +#define CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP (0x0000C000) + +#define CSR_HW_IF_CONFIG_REG_POS_MAC_DASH (0) +#define CSR_HW_IF_CONFIG_REG_POS_MAC_STEP (2) +#define CSR_HW_IF_CONFIG_REG_POS_BOARD_VER (6) +#define CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE (10) +#define CSR_HW_IF_CONFIG_REG_POS_PHY_DASH (12) +#define CSR_HW_IF_CONFIG_REG_POS_PHY_STEP (14) + +#define CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A (0x00080000) +#define CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM (0x00200000) +#define CSR_HW_IF_CONFIG_REG_BIT_NIC_READY (0x00400000) /* PCI_OWN_SEM */ +#define CSR_HW_IF_CONFIG_REG_BIT_NIC_PREPARE_DONE (0x02000000) /* ME_OWN */ +#define CSR_HW_IF_CONFIG_REG_PREPARE (0x08000000) /* WAKE_ME */ +#define CSR_HW_IF_CONFIG_REG_ENABLE_PME (0x10000000) +#define CSR_HW_IF_CONFIG_REG_PERSIST_MODE (0x40000000) /* PERSISTENCE */ + +#define CSR_MBOX_SET_REG_OS_ALIVE BIT(5) + +#define CSR_INT_PERIODIC_DIS (0x00) /* disable periodic int*/ +#define CSR_INT_PERIODIC_ENA (0xFF) /* 255*32 usec ~ 8 msec*/ + +/* interrupt flags in INTA, set by uCode or hardware (e.g. dma), + * acknowledged (reset) by host writing "1" to flagged bits. */ +#define CSR_INT_BIT_FH_RX (1 << 31) /* Rx DMA, cmd responses, FH_INT[17:16] */ +#define CSR_INT_BIT_HW_ERR (1 << 29) /* DMA hardware error FH_INT[31] */ +#define CSR_INT_BIT_RX_PERIODIC (1 << 28) /* Rx periodic */ +#define CSR_INT_BIT_FH_TX (1 << 27) /* Tx DMA FH_INT[1:0] */ +#define CSR_INT_BIT_SCD (1 << 26) /* TXQ pointer advanced */ +#define CSR_INT_BIT_SW_ERR (1 << 25) /* uCode error */ +#define CSR_INT_BIT_RF_KILL (1 << 7) /* HW RFKILL switch GP_CNTRL[27] toggled */ +#define CSR_INT_BIT_CT_KILL (1 << 6) /* Critical temp (chip too hot) rfkill */ +#define CSR_INT_BIT_SW_RX (1 << 3) /* Rx, command responses */ +#define CSR_INT_BIT_WAKEUP (1 << 1) /* NIC controller waking up (pwr mgmt) */ +#define CSR_INT_BIT_ALIVE (1 << 0) /* uCode interrupts once it initializes */ + +#define CSR_INI_SET_MASK (CSR_INT_BIT_FH_RX | \ + CSR_INT_BIT_HW_ERR | \ + CSR_INT_BIT_FH_TX | \ + CSR_INT_BIT_SW_ERR | \ + CSR_INT_BIT_RF_KILL | \ + CSR_INT_BIT_SW_RX | \ + CSR_INT_BIT_WAKEUP | \ + CSR_INT_BIT_ALIVE | \ + CSR_INT_BIT_RX_PERIODIC) + +/* interrupt flags in FH (flow handler) (PCI busmaster DMA) */ +#define CSR_FH_INT_BIT_ERR (1 << 31) /* Error */ +#define CSR_FH_INT_BIT_HI_PRIOR (1 << 30) /* High priority Rx, bypass coalescing */ +#define CSR_FH_INT_BIT_RX_CHNL1 (1 << 17) /* Rx channel 1 */ +#define CSR_FH_INT_BIT_RX_CHNL0 (1 << 16) /* Rx channel 0 */ +#define CSR_FH_INT_BIT_TX_CHNL1 (1 << 1) /* Tx channel 1 */ +#define CSR_FH_INT_BIT_TX_CHNL0 (1 << 0) /* Tx channel 0 */ + +#define CSR_FH_INT_RX_MASK (CSR_FH_INT_BIT_HI_PRIOR | \ + CSR_FH_INT_BIT_RX_CHNL1 | \ + CSR_FH_INT_BIT_RX_CHNL0) + +#define CSR_FH_INT_TX_MASK (CSR_FH_INT_BIT_TX_CHNL1 | \ + CSR_FH_INT_BIT_TX_CHNL0) + +/* GPIO */ +#define CSR_GPIO_IN_BIT_AUX_POWER (0x00000200) +#define CSR_GPIO_IN_VAL_VAUX_PWR_SRC (0x00000000) +#define CSR_GPIO_IN_VAL_VMAIN_PWR_SRC (0x00000200) + +/* RESET */ +#define CSR_RESET_REG_FLAG_NEVO_RESET (0x00000001) +#define CSR_RESET_REG_FLAG_FORCE_NMI (0x00000002) +#define CSR_RESET_REG_FLAG_SW_RESET (0x00000080) +#define CSR_RESET_REG_FLAG_MASTER_DISABLED (0x00000100) +#define CSR_RESET_REG_FLAG_STOP_MASTER (0x00000200) +#define CSR_RESET_LINK_PWR_MGMT_DISABLED (0x80000000) + +/* + * GP (general purpose) CONTROL REGISTER + * Bit fields: + * 27: HW_RF_KILL_SW + * Indicates state of (platform's) hardware RF-Kill switch + * 26-24: POWER_SAVE_TYPE + * Indicates current power-saving mode: + * 000 -- No power saving + * 001 -- MAC power-down + * 010 -- PHY (radio) power-down + * 011 -- Error + * 10: XTAL ON request + * 9-6: SYS_CONFIG + * Indicates current system configuration, reflecting pins on chip + * as forced high/low by device circuit board. + * 4: GOING_TO_SLEEP + * Indicates MAC is entering a power-saving sleep power-down. + * Not a good time to access device-internal resources. + * 3: MAC_ACCESS_REQ + * Host sets this to request and maintain MAC wakeup, to allow host + * access to device-internal resources. Host must wait for + * MAC_CLOCK_READY (and !GOING_TO_SLEEP) before accessing non-CSR + * device registers. + * 2: INIT_DONE + * Host sets this to put device into fully operational D0 power mode. + * Host resets this after SW_RESET to put device into low power mode. + * 0: MAC_CLOCK_READY + * Indicates MAC (ucode processor, etc.) is powered up and can run. + * Internal resources are accessible. + * NOTE: This does not indicate that the processor is actually running. + * NOTE: This does not indicate that device has completed + * init or post-power-down restore of internal SRAM memory. + * Use CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP as indication that + * SRAM is restored and uCode is in normal operation mode. + * Later devices (5xxx/6xxx/1xxx) use non-volatile SRAM, and + * do not need to save/restore it. + * NOTE: After device reset, this bit remains "0" until host sets + * INIT_DONE + */ +#define CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY (0x00000001) +#define CSR_GP_CNTRL_REG_FLAG_INIT_DONE (0x00000004) +#define CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ (0x00000008) +#define CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP (0x00000010) +#define CSR_GP_CNTRL_REG_FLAG_XTAL_ON (0x00000400) + +#define CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN (0x00000001) + +#define CSR_GP_CNTRL_REG_MSK_POWER_SAVE_TYPE (0x07000000) +#define CSR_GP_CNTRL_REG_FLAG_RFKILL_WAKE_L1A_EN (0x04000000) +#define CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW (0x08000000) + +/* From Bz we use these instead during init/reset flow */ +#define CSR_GP_CNTRL_REG_FLAG_MAC_INIT BIT(6) +#define CSR_GP_CNTRL_REG_FLAG_ROM_START BIT(7) +#define CSR_GP_CNTRL_REG_FLAG_MAC_STATUS BIT(20) +#define CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ BIT(21) +#define CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_STATUS BIT(28) +#define CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_REQ BIT(29) +#define CSR_GP_CNTRL_REG_FLAG_SW_RESET BIT(31) + +/* HW REV */ +#define CSR_HW_REV_DASH(_val) (((_val) & 0x0000003) >> 0) +#define CSR_HW_REV_STEP(_val) (((_val) & 0x000000C) >> 2) +#define CSR_HW_REV_TYPE(_val) (((_val) & 0x000FFF0) >> 4) + +/* HW RFID */ +#define CSR_HW_RFID_FLAVOR(_val) (((_val) & 0x000000F) >> 0) +#define CSR_HW_RFID_DASH(_val) (((_val) & 0x00000F0) >> 4) +#define CSR_HW_RFID_STEP(_val) (((_val) & 0x0000F00) >> 8) +#define CSR_HW_RFID_TYPE(_val) (((_val) & 0x0FFF000) >> 12) +#define CSR_HW_RFID_IS_CDB(_val) (((_val) & 0x10000000) >> 28) +#define CSR_HW_RFID_IS_JACKET(_val) (((_val) & 0x20000000) >> 29) + +/** + * hw_rev values + */ +enum { + SILICON_A_STEP = 0, + SILICON_B_STEP, + SILICON_C_STEP, +}; + + +#define CSR_HW_REV_TYPE_MSK (0x000FFF0) +#define CSR_HW_REV_TYPE_5300 (0x0000020) +#define CSR_HW_REV_TYPE_5350 (0x0000030) +#define CSR_HW_REV_TYPE_5100 (0x0000050) +#define CSR_HW_REV_TYPE_5150 (0x0000040) +#define CSR_HW_REV_TYPE_1000 (0x0000060) +#define CSR_HW_REV_TYPE_6x00 (0x0000070) +#define CSR_HW_REV_TYPE_6x50 (0x0000080) +#define CSR_HW_REV_TYPE_6150 (0x0000084) +#define CSR_HW_REV_TYPE_6x05 (0x00000B0) +#define CSR_HW_REV_TYPE_6x30 CSR_HW_REV_TYPE_6x05 +#define CSR_HW_REV_TYPE_6x35 CSR_HW_REV_TYPE_6x05 +#define CSR_HW_REV_TYPE_2x30 (0x00000C0) +#define CSR_HW_REV_TYPE_2x00 (0x0000100) +#define CSR_HW_REV_TYPE_105 (0x0000110) +#define CSR_HW_REV_TYPE_135 (0x0000120) +#define CSR_HW_REV_TYPE_7265D (0x0000210) +#define CSR_HW_REV_TYPE_NONE (0x00001F0) +#define CSR_HW_REV_TYPE_QNJ (0x0000360) +#define CSR_HW_REV_TYPE_QNJ_B0 (0x0000364) +#define CSR_HW_REV_TYPE_QU_B0 (0x0000334) +#define CSR_HW_REV_TYPE_QU_C0 (0x0000338) +#define CSR_HW_REV_TYPE_QUZ (0x0000354) +#define CSR_HW_REV_TYPE_HR_CDB (0x0000340) +#define CSR_HW_REV_TYPE_SO (0x0000370) +#define CSR_HW_REV_TYPE_TY (0x0000420) + +/* RF_ID value */ +#define CSR_HW_RF_ID_TYPE_JF (0x00105100) +#define CSR_HW_RF_ID_TYPE_HR (0x0010A000) +#define CSR_HW_RF_ID_TYPE_HR1 (0x0010c100) +#define CSR_HW_RF_ID_TYPE_HRCDB (0x00109F00) +#define CSR_HW_RF_ID_TYPE_GF (0x0010D000) +#define CSR_HW_RF_ID_TYPE_GF4 (0x0010E000) + +/* HW_RF CHIP STEP */ +#define CSR_HW_RF_STEP(_val) (((_val) >> 8) & 0xF) + +/* EEPROM REG */ +#define CSR_EEPROM_REG_READ_VALID_MSK (0x00000001) +#define CSR_EEPROM_REG_BIT_CMD (0x00000002) +#define CSR_EEPROM_REG_MSK_ADDR (0x0000FFFC) +#define CSR_EEPROM_REG_MSK_DATA (0xFFFF0000) + +/* EEPROM GP */ +#define CSR_EEPROM_GP_VALID_MSK (0x00000007) /* signature */ +#define CSR_EEPROM_GP_IF_OWNER_MSK (0x00000180) +#define CSR_EEPROM_GP_BAD_SIGNATURE_BOTH_EEP_AND_OTP (0x00000000) +#define CSR_EEPROM_GP_BAD_SIG_EEP_GOOD_SIG_OTP (0x00000001) +#define CSR_EEPROM_GP_GOOD_SIG_EEP_LESS_THAN_4K (0x00000002) +#define CSR_EEPROM_GP_GOOD_SIG_EEP_MORE_THAN_4K (0x00000004) + +/* One-time-programmable memory general purpose reg */ +#define CSR_OTP_GP_REG_DEVICE_SELECT (0x00010000) /* 0 - EEPROM, 1 - OTP */ +#define CSR_OTP_GP_REG_OTP_ACCESS_MODE (0x00020000) /* 0 - absolute, 1 - relative */ +#define CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK (0x00100000) /* bit 20 */ +#define CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK (0x00200000) /* bit 21 */ + +/* GP REG */ +#define CSR_GP_REG_POWER_SAVE_STATUS_MSK (0x03000000) /* bit 24/25 */ +#define CSR_GP_REG_NO_POWER_SAVE (0x00000000) +#define CSR_GP_REG_MAC_POWER_SAVE (0x01000000) +#define CSR_GP_REG_PHY_POWER_SAVE (0x02000000) +#define CSR_GP_REG_POWER_SAVE_ERROR (0x03000000) + + +/* CSR GIO */ +#define CSR_GIO_REG_VAL_L0S_DISABLED (0x00000002) + +/* + * UCODE-DRIVER GP (general purpose) mailbox register 1 + * Host driver and uCode write and/or read this register to communicate with + * each other. + * Bit fields: + * 4: UCODE_DISABLE + * Host sets this to request permanent halt of uCode, same as + * sending CARD_STATE command with "halt" bit set. + * 3: CT_KILL_EXIT + * Host sets this to request exit from CT_KILL state, i.e. host thinks + * device temperature is low enough to continue normal operation. + * 2: CMD_BLOCKED + * Host sets this during RF KILL power-down sequence (HW, SW, CT KILL) + * to release uCode to clear all Tx and command queues, enter + * unassociated mode, and power down. + * NOTE: Some devices also use HBUS_TARG_MBX_C register for this bit. + * 1: SW_BIT_RFKILL + * Host sets this when issuing CARD_STATE command to request + * device sleep. + * 0: MAC_SLEEP + * uCode sets this when preparing a power-saving power-down. + * uCode resets this when power-up is complete and SRAM is sane. + * NOTE: device saves internal SRAM data to host when powering down, + * and must restore this data after powering back up. + * MAC_SLEEP is the best indication that restore is complete. + * Later devices (5xxx/6xxx/1xxx) use non-volatile SRAM, and + * do not need to save/restore it. + */ +#define CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP (0x00000001) +#define CSR_UCODE_SW_BIT_RFKILL (0x00000002) +#define CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED (0x00000004) +#define CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT (0x00000008) +#define CSR_UCODE_DRV_GP1_BIT_D3_CFG_COMPLETE (0x00000020) + +/* GP Driver */ +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_MSK (0x00000003) +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_3x3_HYB (0x00000000) +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_2x2_HYB (0x00000001) +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_2x2_IPA (0x00000002) +#define CSR_GP_DRIVER_REG_BIT_CALIB_VERSION6 (0x00000004) +#define CSR_GP_DRIVER_REG_BIT_6050_1x2 (0x00000008) + +#define CSR_GP_DRIVER_REG_BIT_RADIO_IQ_INVER (0x00000080) + +/* GIO Chicken Bits (PCI Express bus link power management) */ +#define CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX (0x00800000) +#define CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER (0x20000000) + +/* LED */ +#define CSR_LED_BSM_CTRL_MSK (0xFFFFFFDF) +#define CSR_LED_REG_TURN_ON (0x60) +#define CSR_LED_REG_TURN_OFF (0x20) + +/* ANA_PLL */ +#define CSR50_ANA_PLL_CFG_VAL (0x00880300) + +/* HPET MEM debug */ +#define CSR_DBG_HPET_MEM_REG_VAL (0xFFFF0000) + +/* DRAM INT TABLE */ +#define CSR_DRAM_INT_TBL_ENABLE (1 << 31) +#define CSR_DRAM_INIT_TBL_WRITE_POINTER (1 << 28) +#define CSR_DRAM_INIT_TBL_WRAP_CHECK (1 << 27) + +/* + * SHR target access (Shared block memory space) + * + * Shared internal registers can be accessed directly from PCI bus through SHR + * arbiter without need for the MAC HW to be powered up. This is possible due to + * indirect read/write via HEEP_CTRL_WRD_PCIEX_CTRL (0xEC) and + * HEEP_CTRL_WRD_PCIEX_DATA (0xF4) registers. + * + * Use iwl_write32()/iwl_read32() family to access these registers. The MAC HW + * need not be powered up so no "grab inc access" is required. + */ + +/* + * Registers for accessing shared registers (e.g. SHR_APMG_GP1, + * SHR_APMG_XTAL_CFG). For example, to read from SHR_APMG_GP1 register (0x1DC), + * first, write to the control register: + * HEEP_CTRL_WRD_PCIEX_CTRL[15:0] = 0x1DC (offset of the SHR_APMG_GP1 register) + * HEEP_CTRL_WRD_PCIEX_CTRL[29:28] = 2 (read access) + * second, read from the data register HEEP_CTRL_WRD_PCIEX_DATA[31:0]. + * + * To write the register, first, write to the data register + * HEEP_CTRL_WRD_PCIEX_DATA[31:0] and then: + * HEEP_CTRL_WRD_PCIEX_CTRL[15:0] = 0x1DC (offset of the SHR_APMG_GP1 register) + * HEEP_CTRL_WRD_PCIEX_CTRL[29:28] = 3 (write access) + */ +#define HEEP_CTRL_WRD_PCIEX_CTRL_REG (CSR_BASE+0x0ec) +#define HEEP_CTRL_WRD_PCIEX_DATA_REG (CSR_BASE+0x0f4) + +/* + * HBUS (Host-side Bus) + * + * HBUS registers are mapped directly into PCI bus space, but are used + * to indirectly access device's internal memory or registers that + * may be powered-down. + * + * Use iwl_write_direct32()/iwl_read_direct32() family for these registers; + * host must "grab nic access" via CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ + * to make sure the MAC (uCode processor, etc.) is powered up for accessing + * internal resources. + * + * Do not use iwl_write32()/iwl_read32() family to access these registers; + * these provide only simple PCI bus access, without waking up the MAC. + */ +#define HBUS_BASE (0x400) + +/* + * Registers for accessing device's internal SRAM memory (e.g. SCD SRAM + * structures, error log, event log, verifying uCode load). + * First write to address register, then read from or write to data register + * to complete the job. Once the address register is set up, accesses to + * data registers auto-increment the address by one dword. + * Bit usage for address registers (read or write): + * 0-31: memory address within device + */ +#define HBUS_TARG_MEM_RADDR (HBUS_BASE+0x00c) +#define HBUS_TARG_MEM_WADDR (HBUS_BASE+0x010) +#define HBUS_TARG_MEM_WDAT (HBUS_BASE+0x018) +#define HBUS_TARG_MEM_RDAT (HBUS_BASE+0x01c) + +/* Mailbox C, used as workaround alternative to CSR_UCODE_DRV_GP1 mailbox */ +#define HBUS_TARG_MBX_C (HBUS_BASE+0x030) +#define HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED (0x00000004) + +/* + * Registers for accessing device's internal peripheral registers + * (e.g. SCD, BSM, etc.). First write to address register, + * then read from or write to data register to complete the job. + * Bit usage for address registers (read or write): + * 0-15: register address (offset) within device + * 24-25: (# bytes - 1) to read or write (e.g. 3 for dword) + */ +#define HBUS_TARG_PRPH_WADDR (HBUS_BASE+0x044) +#define HBUS_TARG_PRPH_RADDR (HBUS_BASE+0x048) +#define HBUS_TARG_PRPH_WDAT (HBUS_BASE+0x04c) +#define HBUS_TARG_PRPH_RDAT (HBUS_BASE+0x050) + +/* Used to enable DBGM */ +#define HBUS_TARG_TEST_REG (HBUS_BASE+0x05c) + +/* + * Per-Tx-queue write pointer (index, really!) + * Indicates index to next TFD that driver will fill (1 past latest filled). + * Bit usage: + * 0-7: queue write index + * 11-8: queue selector + */ +#define HBUS_TARG_WRPTR (HBUS_BASE+0x060) + +/********************************************************** + * CSR values + **********************************************************/ + /* + * host interrupt timeout value + * used with setting interrupt coalescing timer + * the CSR_INT_COALESCING is an 8 bit register in 32-usec unit + * + * default interrupt coalescing timer is 64 x 32 = 2048 usecs + */ +#define IWL_HOST_INT_TIMEOUT_MAX (0xFF) +#define IWL_HOST_INT_TIMEOUT_DEF (0x40) +#define IWL_HOST_INT_TIMEOUT_MIN (0x0) +#define IWL_HOST_INT_OPER_MODE BIT(31) + +/***************************************************************************** + * 7000/3000 series SHR DTS addresses * + *****************************************************************************/ + +/* Diode Results Register Structure: */ +enum dtd_diode_reg { + DTS_DIODE_REG_DIG_VAL = 0x000000FF, /* bits [7:0] */ + DTS_DIODE_REG_VREF_LOW = 0x0000FF00, /* bits [15:8] */ + DTS_DIODE_REG_VREF_HIGH = 0x00FF0000, /* bits [23:16] */ + DTS_DIODE_REG_VREF_ID = 0x03000000, /* bits [25:24] */ + DTS_DIODE_REG_PASS_ONCE = 0x80000000, /* bits [31:31] */ + DTS_DIODE_REG_FLAGS_MSK = 0xFF000000, /* bits [31:24] */ +/* Those are the masks INSIDE the flags bit-field: */ + DTS_DIODE_REG_FLAGS_VREFS_ID_POS = 0, + DTS_DIODE_REG_FLAGS_VREFS_ID = 0x00000003, /* bits [1:0] */ + DTS_DIODE_REG_FLAGS_PASS_ONCE_POS = 7, + DTS_DIODE_REG_FLAGS_PASS_ONCE = 0x00000080, /* bits [7:7] */ +}; + +/***************************************************************************** + * MSIX related registers * + *****************************************************************************/ + +#define CSR_MSIX_BASE (0x2000) +#define CSR_MSIX_FH_INT_CAUSES_AD (CSR_MSIX_BASE + 0x800) +#define CSR_MSIX_FH_INT_MASK_AD (CSR_MSIX_BASE + 0x804) +#define CSR_MSIX_HW_INT_CAUSES_AD (CSR_MSIX_BASE + 0x808) +#define CSR_MSIX_HW_INT_MASK_AD (CSR_MSIX_BASE + 0x80C) +#define CSR_MSIX_AUTOMASK_ST_AD (CSR_MSIX_BASE + 0x810) +#define CSR_MSIX_RX_IVAR_AD_REG (CSR_MSIX_BASE + 0x880) +#define CSR_MSIX_IVAR_AD_REG (CSR_MSIX_BASE + 0x890) +#define CSR_MSIX_PENDING_PBA_AD (CSR_MSIX_BASE + 0x1000) +#define CSR_MSIX_RX_IVAR(cause) (CSR_MSIX_RX_IVAR_AD_REG + (cause)) +#define CSR_MSIX_IVAR(cause) (CSR_MSIX_IVAR_AD_REG + (cause)) + +#define MSIX_FH_INT_CAUSES_Q(q) (q) + +/* + * Causes for the FH register interrupts + */ +enum msix_fh_int_causes { + MSIX_FH_INT_CAUSES_Q0 = BIT(0), + MSIX_FH_INT_CAUSES_Q1 = BIT(1), + MSIX_FH_INT_CAUSES_D2S_CH0_NUM = BIT(16), + MSIX_FH_INT_CAUSES_D2S_CH1_NUM = BIT(17), + MSIX_FH_INT_CAUSES_S2D = BIT(19), + MSIX_FH_INT_CAUSES_FH_ERR = BIT(21), +}; + +/* The low 16 bits are for rx data queue indication */ +#define MSIX_FH_INT_CAUSES_DATA_QUEUE 0xffff + +/* + * Causes for the HW register interrupts + */ +enum msix_hw_int_causes { + MSIX_HW_INT_CAUSES_REG_ALIVE = BIT(0), + MSIX_HW_INT_CAUSES_REG_WAKEUP = BIT(1), + MSIX_HW_INT_CAUSES_REG_IML = BIT(1), + MSIX_HW_INT_CAUSES_REG_RESET_DONE = BIT(2), + MSIX_HW_INT_CAUSES_REG_SW_ERR_BZ = BIT(5), + MSIX_HW_INT_CAUSES_REG_CT_KILL = BIT(6), + MSIX_HW_INT_CAUSES_REG_RF_KILL = BIT(7), + MSIX_HW_INT_CAUSES_REG_PERIODIC = BIT(8), + MSIX_HW_INT_CAUSES_REG_SW_ERR = BIT(25), + MSIX_HW_INT_CAUSES_REG_SCD = BIT(26), + MSIX_HW_INT_CAUSES_REG_FH_TX = BIT(27), + MSIX_HW_INT_CAUSES_REG_HW_ERR = BIT(29), + MSIX_HW_INT_CAUSES_REG_HAP = BIT(30), +}; + +#define MSIX_MIN_INTERRUPT_VECTORS 2 +#define MSIX_AUTO_CLEAR_CAUSE 0 +#define MSIX_NON_AUTO_CLEAR_CAUSE BIT(7) + +/***************************************************************************** + * HW address related registers * + *****************************************************************************/ + +#define CSR_ADDR_BASE(trans) ((trans)->cfg->mac_addr_from_csr) +#define CSR_MAC_ADDR0_OTP(trans) (CSR_ADDR_BASE(trans) + 0x00) +#define CSR_MAC_ADDR1_OTP(trans) (CSR_ADDR_BASE(trans) + 0x04) +#define CSR_MAC_ADDR0_STRAP(trans) (CSR_ADDR_BASE(trans) + 0x08) +#define CSR_MAC_ADDR1_STRAP(trans) (CSR_ADDR_BASE(trans) + 0x0c) + +#endif /* !__iwl_csr_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-dbg-tlv.c b/sys/contrib/dev/iwlwifi/iwl-dbg-tlv.c new file mode 100644 index 00000000000..acb6baa68b0 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-dbg-tlv.c @@ -0,0 +1,1313 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2018-2021 Intel Corporation + */ +#include +#include "iwl-drv.h" +#include "iwl-trans.h" +#include "iwl-dbg-tlv.h" +#include "fw/dbg.h" +#include "fw/runtime.h" + +/** + * enum iwl_dbg_tlv_type - debug TLV types + * @IWL_DBG_TLV_TYPE_DEBUG_INFO: debug info TLV + * @IWL_DBG_TLV_TYPE_BUF_ALLOC: buffer allocation TLV + * @IWL_DBG_TLV_TYPE_HCMD: host command TLV + * @IWL_DBG_TLV_TYPE_REGION: region TLV + * @IWL_DBG_TLV_TYPE_TRIGGER: trigger TLV + * @IWL_DBG_TLV_TYPE_CONF_SET: conf set TLV + * @IWL_DBG_TLV_TYPE_NUM: number of debug TLVs + */ +enum iwl_dbg_tlv_type { + IWL_DBG_TLV_TYPE_DEBUG_INFO = + IWL_UCODE_TLV_TYPE_DEBUG_INFO - IWL_UCODE_TLV_DEBUG_BASE, + IWL_DBG_TLV_TYPE_BUF_ALLOC, + IWL_DBG_TLV_TYPE_HCMD, + IWL_DBG_TLV_TYPE_REGION, + IWL_DBG_TLV_TYPE_TRIGGER, + IWL_DBG_TLV_TYPE_CONF_SET, + IWL_DBG_TLV_TYPE_NUM, +}; + +/** + * struct iwl_dbg_tlv_ver_data - debug TLV version struct + * @min_ver: min version supported + * @max_ver: max version supported + */ +struct iwl_dbg_tlv_ver_data { + int min_ver; + int max_ver; +}; + +/** + * struct iwl_dbg_tlv_timer_node - timer node struct + * @list: list of &struct iwl_dbg_tlv_timer_node + * @timer: timer + * @fwrt: &struct iwl_fw_runtime + * @tlv: TLV attach to the timer node + */ +struct iwl_dbg_tlv_timer_node { + struct list_head list; + struct timer_list timer; + struct iwl_fw_runtime *fwrt; + struct iwl_ucode_tlv *tlv; +}; + +static const struct iwl_dbg_tlv_ver_data +dbg_ver_table[IWL_DBG_TLV_TYPE_NUM] = { + [IWL_DBG_TLV_TYPE_DEBUG_INFO] = {.min_ver = 1, .max_ver = 1,}, + [IWL_DBG_TLV_TYPE_BUF_ALLOC] = {.min_ver = 1, .max_ver = 1,}, + [IWL_DBG_TLV_TYPE_HCMD] = {.min_ver = 1, .max_ver = 1,}, + [IWL_DBG_TLV_TYPE_REGION] = {.min_ver = 1, .max_ver = 2,}, + [IWL_DBG_TLV_TYPE_TRIGGER] = {.min_ver = 1, .max_ver = 1,}, + [IWL_DBG_TLV_TYPE_CONF_SET] = {.min_ver = 1, .max_ver = 1,}, +}; + +static int iwl_dbg_tlv_add(const struct iwl_ucode_tlv *tlv, + struct list_head *list) +{ + u32 len = le32_to_cpu(tlv->length); + struct iwl_dbg_tlv_node *node; + + node = kzalloc(sizeof(*node) + len, GFP_KERNEL); + if (!node) + return -ENOMEM; + + memcpy(&node->tlv, tlv, sizeof(node->tlv) + len); + list_add_tail(&node->list, list); + + return 0; +} + +static bool iwl_dbg_tlv_ver_support(const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_header *hdr = (const void *)&tlv->data[0]; + u32 type = le32_to_cpu(tlv->type); + u32 tlv_idx = type - IWL_UCODE_TLV_DEBUG_BASE; + u32 ver = le32_to_cpu(hdr->version); + + if (ver < dbg_ver_table[tlv_idx].min_ver || + ver > dbg_ver_table[tlv_idx].max_ver) + return false; + + return true; +} + +static int iwl_dbg_tlv_alloc_debug_info(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_debug_info_tlv *debug_info = (const void *)tlv->data; + + if (le32_to_cpu(tlv->length) != sizeof(*debug_info)) + return -EINVAL; + + IWL_DEBUG_FW(trans, "WRT: Loading debug cfg: %s\n", + debug_info->debug_cfg_name); + + return iwl_dbg_tlv_add(tlv, &trans->dbg.debug_info_tlv_list); +} + +static int iwl_dbg_tlv_alloc_buf_alloc(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_allocation_tlv *alloc = (const void *)tlv->data; + u32 buf_location; + u32 alloc_id; + + if (le32_to_cpu(tlv->length) != sizeof(*alloc)) + return -EINVAL; + + buf_location = le32_to_cpu(alloc->buf_location); + alloc_id = le32_to_cpu(alloc->alloc_id); + + if (buf_location == IWL_FW_INI_LOCATION_INVALID || + buf_location >= IWL_FW_INI_LOCATION_NUM) + goto err; + + if (alloc_id == IWL_FW_INI_ALLOCATION_INVALID || + alloc_id >= IWL_FW_INI_ALLOCATION_NUM) + goto err; + + if (buf_location == IWL_FW_INI_LOCATION_NPK_PATH && + alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) + goto err; + + if (buf_location == IWL_FW_INI_LOCATION_SRAM_PATH && + alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) + goto err; + + trans->dbg.fw_mon_cfg[alloc_id] = *alloc; + + return 0; +err: + IWL_ERR(trans, + "WRT: Invalid allocation id %u and/or location id %u for allocation TLV\n", + alloc_id, buf_location); + return -EINVAL; +} + +static int iwl_dbg_tlv_alloc_hcmd(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_hcmd_tlv *hcmd = (const void *)tlv->data; + u32 tp = le32_to_cpu(hcmd->time_point); + + if (le32_to_cpu(tlv->length) <= sizeof(*hcmd)) + return -EINVAL; + + /* Host commands can not be sent in early time point since the FW + * is not ready + */ + if (tp == IWL_FW_INI_TIME_POINT_INVALID || + tp >= IWL_FW_INI_TIME_POINT_NUM || + tp == IWL_FW_INI_TIME_POINT_EARLY) { + IWL_ERR(trans, + "WRT: Invalid time point %u for host command TLV\n", + tp); + return -EINVAL; + } + + return iwl_dbg_tlv_add(tlv, &trans->dbg.time_point[tp].hcmd_list); +} + +static int iwl_dbg_tlv_alloc_region(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_region_tlv *reg = (const void *)tlv->data; + struct iwl_ucode_tlv **active_reg; + u32 id = le32_to_cpu(reg->id); + u32 type = le32_to_cpu(reg->type); + u32 tlv_len = sizeof(*tlv) + le32_to_cpu(tlv->length); + + /* + * The higher part of the ID in version 2 is irrelevant for + * us, so mask it out. + */ + if (le32_to_cpu(reg->hdr.version) == 2) + id &= IWL_FW_INI_REGION_V2_MASK; + + if (le32_to_cpu(tlv->length) < sizeof(*reg)) + return -EINVAL; + + /* for safe use of a string from FW, limit it to IWL_FW_INI_MAX_NAME */ + IWL_DEBUG_FW(trans, "WRT: parsing region: %.*s\n", + IWL_FW_INI_MAX_NAME, reg->name); + + if (id >= IWL_FW_INI_MAX_REGION_ID) { + IWL_ERR(trans, "WRT: Invalid region id %u\n", id); + return -EINVAL; + } + + if (type <= IWL_FW_INI_REGION_INVALID || + type >= IWL_FW_INI_REGION_NUM) { + IWL_ERR(trans, "WRT: Invalid region type %u\n", type); + return -EINVAL; + } + + if (type == IWL_FW_INI_REGION_PCI_IOSF_CONFIG && + !trans->ops->read_config32) { + IWL_ERR(trans, "WRT: Unsupported region type %u\n", type); + return -EOPNOTSUPP; + } + + active_reg = &trans->dbg.active_regions[id]; + if (*active_reg) { + IWL_WARN(trans, "WRT: Overriding region id %u\n", id); + + kfree(*active_reg); + } + + *active_reg = kmemdup(tlv, tlv_len, GFP_KERNEL); + if (!*active_reg) + return -ENOMEM; + + IWL_DEBUG_FW(trans, "WRT: Enabling region id %u type %u\n", id, type); + + return 0; +} + +static int iwl_dbg_tlv_alloc_trigger(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_trigger_tlv *trig = (const void *)tlv->data; + struct iwl_fw_ini_trigger_tlv *dup_trig; + u32 tp = le32_to_cpu(trig->time_point); + struct iwl_ucode_tlv *dup = NULL; + int ret; + + if (le32_to_cpu(tlv->length) < sizeof(*trig)) + return -EINVAL; + + if (tp <= IWL_FW_INI_TIME_POINT_INVALID || + tp >= IWL_FW_INI_TIME_POINT_NUM) { + IWL_ERR(trans, + "WRT: Invalid time point %u for trigger TLV\n", + tp); + return -EINVAL; + } + + if (!le32_to_cpu(trig->occurrences)) { + dup = kmemdup(tlv, sizeof(*tlv) + le32_to_cpu(tlv->length), + GFP_KERNEL); + if (!dup) + return -ENOMEM; + dup_trig = (void *)dup->data; + dup_trig->occurrences = cpu_to_le32(-1); + tlv = dup; + } + + ret = iwl_dbg_tlv_add(tlv, &trans->dbg.time_point[tp].trig_list); + kfree(dup); + + return ret; +} + +static int iwl_dbg_tlv_config_set(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_conf_set_tlv *conf_set = (const void *)tlv->data; + u32 tp = le32_to_cpu(conf_set->time_point); + u32 type = le32_to_cpu(conf_set->set_type); + + if (tp <= IWL_FW_INI_TIME_POINT_INVALID || + tp >= IWL_FW_INI_TIME_POINT_NUM) { + IWL_DEBUG_FW(trans, + "WRT: Invalid time point %u for config set TLV\n", tp); + return -EINVAL; + } + + if (type <= IWL_FW_INI_CONFIG_SET_TYPE_INVALID || + type >= IWL_FW_INI_CONFIG_SET_TYPE_MAX_NUM) { + IWL_DEBUG_FW(trans, + "WRT: Invalid config set type %u for config set TLV\n", type); + return -EINVAL; + } + + return iwl_dbg_tlv_add(tlv, &trans->dbg.time_point[tp].config_list); +} + +static int (*dbg_tlv_alloc[])(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) = { + [IWL_DBG_TLV_TYPE_DEBUG_INFO] = iwl_dbg_tlv_alloc_debug_info, + [IWL_DBG_TLV_TYPE_BUF_ALLOC] = iwl_dbg_tlv_alloc_buf_alloc, + [IWL_DBG_TLV_TYPE_HCMD] = iwl_dbg_tlv_alloc_hcmd, + [IWL_DBG_TLV_TYPE_REGION] = iwl_dbg_tlv_alloc_region, + [IWL_DBG_TLV_TYPE_TRIGGER] = iwl_dbg_tlv_alloc_trigger, + [IWL_DBG_TLV_TYPE_CONF_SET] = iwl_dbg_tlv_config_set, +}; + +void iwl_dbg_tlv_alloc(struct iwl_trans *trans, const struct iwl_ucode_tlv *tlv, + bool ext) +{ + const struct iwl_fw_ini_header *hdr = (const void *)&tlv->data[0]; + u32 type = le32_to_cpu(tlv->type); + u32 tlv_idx = type - IWL_UCODE_TLV_DEBUG_BASE; + u32 domain = le32_to_cpu(hdr->domain); + enum iwl_ini_cfg_state *cfg_state = ext ? + &trans->dbg.external_ini_cfg : &trans->dbg.internal_ini_cfg; + int ret; + + if (domain != IWL_FW_INI_DOMAIN_ALWAYS_ON && + !(domain & trans->dbg.domains_bitmap)) { + IWL_DEBUG_FW(trans, + "WRT: Skipping TLV with disabled domain 0x%0x (0x%0x)\n", + domain, trans->dbg.domains_bitmap); + return; + } + + if (tlv_idx >= ARRAY_SIZE(dbg_tlv_alloc) || !dbg_tlv_alloc[tlv_idx]) { + IWL_ERR(trans, "WRT: Unsupported TLV type 0x%x\n", type); + goto out_err; + } + + if (!iwl_dbg_tlv_ver_support(tlv)) { + IWL_ERR(trans, "WRT: Unsupported TLV 0x%x version %u\n", type, + le32_to_cpu(hdr->version)); + goto out_err; + } + + ret = dbg_tlv_alloc[tlv_idx](trans, tlv); + if (ret) { + IWL_ERR(trans, + "WRT: Failed to allocate TLV 0x%x, ret %d, (ext=%d)\n", + type, ret, ext); + goto out_err; + } + + if (*cfg_state == IWL_INI_CFG_STATE_NOT_LOADED) + *cfg_state = IWL_INI_CFG_STATE_LOADED; + + return; + +out_err: + *cfg_state = IWL_INI_CFG_STATE_CORRUPTED; +} + +void iwl_dbg_tlv_del_timers(struct iwl_trans *trans) +{ + struct list_head *timer_list = &trans->dbg.periodic_trig_list; + struct iwl_dbg_tlv_timer_node *node, *tmp; + + list_for_each_entry_safe(node, tmp, timer_list, list) { + del_timer(&node->timer); + list_del(&node->list); + kfree(node); + } +} +IWL_EXPORT_SYMBOL(iwl_dbg_tlv_del_timers); + +static void iwl_dbg_tlv_fragments_free(struct iwl_trans *trans, + enum iwl_fw_ini_allocation_id alloc_id) +{ + struct iwl_fw_mon *fw_mon; + int i; + + if (alloc_id <= IWL_FW_INI_ALLOCATION_INVALID || + alloc_id >= IWL_FW_INI_ALLOCATION_NUM) + return; + + fw_mon = &trans->dbg.fw_mon_ini[alloc_id]; + + for (i = 0; i < fw_mon->num_frags; i++) { + struct iwl_dram_data *frag = &fw_mon->frags[i]; + + dma_free_coherent(trans->dev, frag->size, frag->block, + frag->physical); + + frag->physical = 0; + frag->block = NULL; + frag->size = 0; + } + + kfree(fw_mon->frags); + fw_mon->frags = NULL; + fw_mon->num_frags = 0; +} + +void iwl_dbg_tlv_free(struct iwl_trans *trans) +{ + struct iwl_dbg_tlv_node *tlv_node, *tlv_node_tmp; + int i; + + iwl_dbg_tlv_del_timers(trans); + + for (i = 0; i < ARRAY_SIZE(trans->dbg.active_regions); i++) { + struct iwl_ucode_tlv **active_reg = + &trans->dbg.active_regions[i]; + + kfree(*active_reg); + *active_reg = NULL; + } + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, + &trans->dbg.debug_info_tlv_list, list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + for (i = 0; i < ARRAY_SIZE(trans->dbg.time_point); i++) { + struct iwl_dbg_tlv_time_point_data *tp = + &trans->dbg.time_point[i]; + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, &tp->trig_list, + list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, &tp->hcmd_list, + list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, + &tp->active_trig_list, list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, + &tp->config_list, list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + } + + for (i = 0; i < ARRAY_SIZE(trans->dbg.fw_mon_ini); i++) + iwl_dbg_tlv_fragments_free(trans, i); +} + +static int iwl_dbg_tlv_parse_bin(struct iwl_trans *trans, const u8 *data, + size_t len) +{ + const struct iwl_ucode_tlv *tlv; + u32 tlv_len; + + while (len >= sizeof(*tlv)) { + len -= sizeof(*tlv); + tlv = (const void *)data; + + tlv_len = le32_to_cpu(tlv->length); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + return -EINVAL; + } + len -= ALIGN(tlv_len, 4); + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + + iwl_dbg_tlv_alloc(trans, tlv, true); + } + + return 0; +} + +void iwl_dbg_tlv_load_bin(struct device *dev, struct iwl_trans *trans) +{ + const struct firmware *fw; + const char *yoyo_bin = "iwl-debug-yoyo.bin"; + int res; + + if (!iwlwifi_mod_params.enable_ini || + trans->trans_cfg->device_family <= IWL_DEVICE_FAMILY_9000) + return; + + res = firmware_request_nowarn(&fw, yoyo_bin, dev); + IWL_DEBUG_FW(trans, "%s %s\n", res ? "didn't load" : "loaded", yoyo_bin); + + if (res) + return; + + iwl_dbg_tlv_parse_bin(trans, fw->data, fw->size); + + release_firmware(fw); +} + +void iwl_dbg_tlv_init(struct iwl_trans *trans) +{ + int i; + + INIT_LIST_HEAD(&trans->dbg.debug_info_tlv_list); + INIT_LIST_HEAD(&trans->dbg.periodic_trig_list); + + for (i = 0; i < ARRAY_SIZE(trans->dbg.time_point); i++) { + struct iwl_dbg_tlv_time_point_data *tp = + &trans->dbg.time_point[i]; + + INIT_LIST_HEAD(&tp->trig_list); + INIT_LIST_HEAD(&tp->hcmd_list); + INIT_LIST_HEAD(&tp->active_trig_list); + INIT_LIST_HEAD(&tp->config_list); + } +} + +static int iwl_dbg_tlv_alloc_fragment(struct iwl_fw_runtime *fwrt, + struct iwl_dram_data *frag, u32 pages) +{ + void *block = NULL; + dma_addr_t physical; + + if (!frag || frag->size || !pages) + return -EIO; + + /* + * We try to allocate as many pages as we can, starting with + * the requested amount and going down until we can allocate + * something. Because of DIV_ROUND_UP(), pages will never go + * down to 0 and stop the loop, so stop when pages reaches 1, + * which is too small anyway. + */ + while (pages > 1) { + block = dma_alloc_coherent(fwrt->dev, pages * PAGE_SIZE, + &physical, + GFP_KERNEL | __GFP_NOWARN); + if (block) + break; + + IWL_WARN(fwrt, "WRT: Failed to allocate fragment size %lu\n", + pages * PAGE_SIZE); + + pages = DIV_ROUND_UP(pages, 2); + } + + if (!block) + return -ENOMEM; + + frag->physical = physical; + frag->block = block; + frag->size = pages * PAGE_SIZE; + + return pages; +} + +static int iwl_dbg_tlv_alloc_fragments(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_allocation_id alloc_id) +{ + struct iwl_fw_mon *fw_mon; + struct iwl_fw_ini_allocation_tlv *fw_mon_cfg; + u32 num_frags, remain_pages, frag_pages; + int i; + + if (alloc_id < IWL_FW_INI_ALLOCATION_INVALID || + alloc_id >= IWL_FW_INI_ALLOCATION_NUM) + return -EIO; + + fw_mon_cfg = &fwrt->trans->dbg.fw_mon_cfg[alloc_id]; + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + if (fw_mon->num_frags || + fw_mon_cfg->buf_location != + cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH)) + return 0; + + num_frags = le32_to_cpu(fw_mon_cfg->max_frags_num); + if (!fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DBG_BUF_ALLOC_CMD_SUPP)) { + if (alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) + return -EIO; + num_frags = 1; + } + + remain_pages = DIV_ROUND_UP(le32_to_cpu(fw_mon_cfg->req_size), + PAGE_SIZE); + num_frags = min_t(u32, num_frags, BUF_ALLOC_MAX_NUM_FRAGS); + num_frags = min_t(u32, num_frags, remain_pages); + frag_pages = DIV_ROUND_UP(remain_pages, num_frags); + + fw_mon->frags = kcalloc(num_frags, sizeof(*fw_mon->frags), GFP_KERNEL); + if (!fw_mon->frags) + return -ENOMEM; + + for (i = 0; i < num_frags; i++) { + int pages = min_t(u32, frag_pages, remain_pages); + + IWL_DEBUG_FW(fwrt, + "WRT: Allocating DRAM buffer (alloc_id=%u, fragment=%u, size=0x%lx)\n", + alloc_id, i, pages * PAGE_SIZE); + + pages = iwl_dbg_tlv_alloc_fragment(fwrt, &fw_mon->frags[i], + pages); + if (pages < 0) { + u32 alloc_size = le32_to_cpu(fw_mon_cfg->req_size) - + (remain_pages * PAGE_SIZE); + + if (alloc_size < le32_to_cpu(fw_mon_cfg->min_size)) { + iwl_dbg_tlv_fragments_free(fwrt->trans, + alloc_id); + return pages; + } + break; + } + + remain_pages -= pages; + fw_mon->num_frags++; + } + + return 0; +} + +static int iwl_dbg_tlv_apply_buffer(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_allocation_id alloc_id) +{ + struct iwl_fw_mon *fw_mon; + u32 remain_frags, num_commands; + int i, fw_mon_idx = 0; + + if (!fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DBG_BUF_ALLOC_CMD_SUPP)) + return 0; + + if (alloc_id < IWL_FW_INI_ALLOCATION_INVALID || + alloc_id >= IWL_FW_INI_ALLOCATION_NUM) + return -EIO; + + if (le32_to_cpu(fwrt->trans->dbg.fw_mon_cfg[alloc_id].buf_location) != + IWL_FW_INI_LOCATION_DRAM_PATH) + return 0; + + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + /* the first fragment of DBGC1 is given to the FW via register + * or context info + */ + if (alloc_id == IWL_FW_INI_ALLOCATION_ID_DBGC1) + fw_mon_idx++; + + remain_frags = fw_mon->num_frags - fw_mon_idx; + if (!remain_frags) + return 0; + + num_commands = DIV_ROUND_UP(remain_frags, BUF_ALLOC_MAX_NUM_FRAGS); + + IWL_DEBUG_FW(fwrt, "WRT: Applying DRAM destination (alloc_id=%u)\n", + alloc_id); + + for (i = 0; i < num_commands; i++) { + u32 num_frags = min_t(u32, remain_frags, + BUF_ALLOC_MAX_NUM_FRAGS); + struct iwl_buf_alloc_cmd data = { + .alloc_id = cpu_to_le32(alloc_id), + .num_frags = cpu_to_le32(num_frags), + .buf_location = + cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH), + }; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DEBUG_GROUP, BUFFER_ALLOCATION), + .data[0] = &data, + .len[0] = sizeof(data), + .flags = CMD_SEND_IN_RFKILL, + }; + int ret, j; + + for (j = 0; j < num_frags; j++) { + struct iwl_buf_alloc_frag *frag = &data.frags[j]; + struct iwl_dram_data *fw_mon_frag = + &fw_mon->frags[fw_mon_idx++]; + + frag->addr = cpu_to_le64(fw_mon_frag->physical); + frag->size = cpu_to_le32(fw_mon_frag->size); + } + ret = iwl_trans_send_cmd(fwrt->trans, &hcmd); + if (ret) + return ret; + + remain_frags -= num_frags; + } + + return 0; +} + +static void iwl_dbg_tlv_apply_buffers(struct iwl_fw_runtime *fwrt) +{ + int ret, i; + + if (fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DRAM_FRAG_SUPPORT)) + return; + + for (i = 0; i < IWL_FW_INI_ALLOCATION_NUM; i++) { + ret = iwl_dbg_tlv_apply_buffer(fwrt, i); + if (ret) + IWL_WARN(fwrt, + "WRT: Failed to apply DRAM buffer for allocation id %d, ret=%d\n", + i, ret); + } +} + +static int iwl_dbg_tlv_update_dram(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_allocation_id alloc_id, + struct iwl_dram_info *dram_info) +{ + struct iwl_fw_mon *fw_mon; + u32 remain_frags, num_frags; + int j, fw_mon_idx = 0; + struct iwl_buf_alloc_cmd *data; + + if (le32_to_cpu(fwrt->trans->dbg.fw_mon_cfg[alloc_id].buf_location) != + IWL_FW_INI_LOCATION_DRAM_PATH) { + IWL_DEBUG_FW(fwrt, "DRAM_PATH is not supported alloc_id %u\n", alloc_id); + return -1; + } + + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + /* the first fragment of DBGC1 is given to the FW via register + * or context info + */ + if (alloc_id == IWL_FW_INI_ALLOCATION_ID_DBGC1) + fw_mon_idx++; + + remain_frags = fw_mon->num_frags - fw_mon_idx; + if (!remain_frags) + return -1; + + num_frags = min_t(u32, remain_frags, BUF_ALLOC_MAX_NUM_FRAGS); + data = &dram_info->dram_frags[alloc_id - 1]; + data->alloc_id = cpu_to_le32(alloc_id); + data->num_frags = cpu_to_le32(num_frags); + data->buf_location = cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH); + + IWL_DEBUG_FW(fwrt, "WRT: DRAM buffer details alloc_id=%u, num_frags=%u\n", + cpu_to_le32(alloc_id), cpu_to_le32(num_frags)); + + for (j = 0; j < num_frags; j++) { + struct iwl_buf_alloc_frag *frag = &data->frags[j]; + struct iwl_dram_data *fw_mon_frag = &fw_mon->frags[fw_mon_idx++]; + + frag->addr = cpu_to_le64(fw_mon_frag->physical); + frag->size = cpu_to_le32(fw_mon_frag->size); + IWL_DEBUG_FW(fwrt, "WRT: DRAM fragment details\n"); + IWL_DEBUG_FW(fwrt, "frag=%u, addr=0x%016llx, size=0x%x)\n", + j, cpu_to_le64(fw_mon_frag->physical), + cpu_to_le32(fw_mon_frag->size)); + } + return 0; +} + +static void iwl_dbg_tlv_update_drams(struct iwl_fw_runtime *fwrt) +{ + int ret, i, dram_alloc = 0; + struct iwl_dram_info dram_info; + struct iwl_dram_data *frags = + &fwrt->trans->dbg.fw_mon_ini[IWL_FW_INI_ALLOCATION_ID_DBGC1].frags[0]; + + if (!fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DRAM_FRAG_SUPPORT)) + return; + + dram_info.first_word = cpu_to_le32(DRAM_INFO_FIRST_MAGIC_WORD); + dram_info.second_word = cpu_to_le32(DRAM_INFO_SECOND_MAGIC_WORD); + + for (i = IWL_FW_INI_ALLOCATION_ID_DBGC1; + i <= IWL_FW_INI_ALLOCATION_ID_DBGC3; i++) { + ret = iwl_dbg_tlv_update_dram(fwrt, i, &dram_info); + if (!ret) + dram_alloc++; + else + IWL_WARN(fwrt, + "WRT: Failed to set DRAM buffer for alloc id %d, ret=%d\n", + i, ret); + } + if (dram_alloc) { + memcpy(frags->block, &dram_info, sizeof(dram_info)); + IWL_DEBUG_FW(fwrt, "block data after %016x\n", + *((int *)fwrt->trans->dbg.fw_mon_ini[1].frags[0].block)); + } +} + +static void iwl_dbg_tlv_send_hcmds(struct iwl_fw_runtime *fwrt, + struct list_head *hcmd_list) +{ + struct iwl_dbg_tlv_node *node; + + list_for_each_entry(node, hcmd_list, list) { + struct iwl_fw_ini_hcmd_tlv *hcmd = (void *)node->tlv.data; + struct iwl_fw_ini_hcmd *hcmd_data = &hcmd->hcmd; + u16 hcmd_len = le32_to_cpu(node->tlv.length) - sizeof(*hcmd); + struct iwl_host_cmd cmd = { + .id = WIDE_ID(hcmd_data->group, hcmd_data->id), + .len = { hcmd_len, }, + .data = { hcmd_data->data, }, + }; + + iwl_trans_send_cmd(fwrt->trans, &cmd); + } +} + +static void iwl_dbg_tlv_apply_config(struct iwl_fw_runtime *fwrt, + struct list_head *config_list) +{ + struct iwl_dbg_tlv_node *node; + + list_for_each_entry(node, config_list, list) { + struct iwl_fw_ini_conf_set_tlv *config_list = (void *)node->tlv.data; + u32 count, address, value; + u32 len = (le32_to_cpu(node->tlv.length) - sizeof(*config_list)) / 8; + u32 type = le32_to_cpu(config_list->set_type); + u32 offset = le32_to_cpu(config_list->addr_offset); + + switch (type) { + case IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_MAC: { + if (!iwl_trans_grab_nic_access(fwrt->trans)) { + IWL_DEBUG_FW(fwrt, "WRT: failed to get nic access\n"); + IWL_DEBUG_FW(fwrt, "WRT: skipping MAC PERIPHERY config\n"); + continue; + } + IWL_DEBUG_FW(fwrt, "WRT: MAC PERIPHERY config len: len %u\n", len); + for (count = 0; count < len; count++) { + address = le32_to_cpu(config_list->addr_val[count].address); + value = le32_to_cpu(config_list->addr_val[count].value); + iwl_trans_write_prph(fwrt->trans, address + offset, value); + } + iwl_trans_release_nic_access(fwrt->trans); + break; + } + case IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_MEMORY: { + for (count = 0; count < len; count++) { + address = le32_to_cpu(config_list->addr_val[count].address); + value = le32_to_cpu(config_list->addr_val[count].value); + iwl_trans_write_mem32(fwrt->trans, address + offset, value); + IWL_DEBUG_FW(fwrt, "WRT: DEV_MEM: count %u, add: %u val: %u\n", + count, address, value); + } + break; + } + case IWL_FW_INI_CONFIG_SET_TYPE_CSR: { + for (count = 0; count < len; count++) { + address = le32_to_cpu(config_list->addr_val[count].address); + value = le32_to_cpu(config_list->addr_val[count].value); + iwl_write32(fwrt->trans, address + offset, value); + IWL_DEBUG_FW(fwrt, "WRT: CSR: count %u, add: %u val: %u\n", + count, address, value); + } + break; + } + case IWL_FW_INI_CONFIG_SET_TYPE_DBGC_DRAM_ADDR: { + struct iwl_dbgc1_info dram_info = {}; + struct iwl_dram_data *frags = &fwrt->trans->dbg.fw_mon_ini[1].frags[0]; + __le64 dram_base_addr = cpu_to_le64(frags->physical); + __le32 dram_size = cpu_to_le32(frags->size); + u64 dram_addr = le64_to_cpu(dram_base_addr); + u32 ret; + + IWL_DEBUG_FW(fwrt, "WRT: dram_base_addr 0x%016llx, dram_size 0x%x\n", + dram_base_addr, dram_size); + IWL_DEBUG_FW(fwrt, "WRT: config_list->addr_offset: %u\n", + le32_to_cpu(config_list->addr_offset)); + for (count = 0; count < len; count++) { + address = le32_to_cpu(config_list->addr_val[count].address); + dram_info.dbgc1_add_lsb = + cpu_to_le32((dram_addr & 0x00000000FFFFFFFFULL) + 0x400); + dram_info.dbgc1_add_msb = + cpu_to_le32((dram_addr & 0xFFFFFFFF00000000ULL) >> 32); + dram_info.dbgc1_size = cpu_to_le32(le32_to_cpu(dram_size) - 0x400); + ret = iwl_trans_write_mem(fwrt->trans, + address + offset, &dram_info, 4); + if (ret) { + IWL_ERR(fwrt, "Failed to write dram_info to HW_SMEM\n"); + break; + } + } + break; + } + case IWL_FW_INI_CONFIG_SET_TYPE_PERIPH_SCRATCH_HWM: { + u32 debug_token_config = + le32_to_cpu(config_list->addr_val[0].value); + + IWL_DEBUG_FW(fwrt, "WRT: Setting HWM debug token config: %u\n", + debug_token_config); + fwrt->trans->dbg.ucode_preset = debug_token_config; + break; + } + default: + break; + } + } +} + +static void iwl_dbg_tlv_periodic_trig_handler(struct timer_list *t) +{ + struct iwl_dbg_tlv_timer_node *timer_node = + from_timer(timer_node, t, timer); + struct iwl_fwrt_dump_data dump_data = { + .trig = (void *)timer_node->tlv->data, + }; + int ret; + + ret = iwl_fw_dbg_ini_collect(timer_node->fwrt, &dump_data, false); + if (!ret || ret == -EBUSY) { + u32 occur = le32_to_cpu(dump_data.trig->occurrences); + u32 collect_interval = le32_to_cpu(dump_data.trig->data[0]); + + if (!occur) + return; + + mod_timer(t, jiffies + msecs_to_jiffies(collect_interval)); + } +} + +static void iwl_dbg_tlv_set_periodic_trigs(struct iwl_fw_runtime *fwrt) +{ + struct iwl_dbg_tlv_node *node; + struct list_head *trig_list = + &fwrt->trans->dbg.time_point[IWL_FW_INI_TIME_POINT_PERIODIC].active_trig_list; + + list_for_each_entry(node, trig_list, list) { + struct iwl_fw_ini_trigger_tlv *trig = (void *)node->tlv.data; + struct iwl_dbg_tlv_timer_node *timer_node; + u32 occur = le32_to_cpu(trig->occurrences), collect_interval; + u32 min_interval = 100; + + if (!occur) + continue; + + /* make sure there is at least one dword of data for the + * interval value + */ + if (le32_to_cpu(node->tlv.length) < + sizeof(*trig) + sizeof(__le32)) { + IWL_ERR(fwrt, + "WRT: Invalid periodic trigger data was not given\n"); + continue; + } + + if (le32_to_cpu(trig->data[0]) < min_interval) { + IWL_WARN(fwrt, + "WRT: Override min interval from %u to %u msec\n", + le32_to_cpu(trig->data[0]), min_interval); + trig->data[0] = cpu_to_le32(min_interval); + } + + collect_interval = le32_to_cpu(trig->data[0]); + + timer_node = kzalloc(sizeof(*timer_node), GFP_KERNEL); + if (!timer_node) { + IWL_ERR(fwrt, + "WRT: Failed to allocate periodic trigger\n"); + continue; + } + + timer_node->fwrt = fwrt; + timer_node->tlv = &node->tlv; + timer_setup(&timer_node->timer, + iwl_dbg_tlv_periodic_trig_handler, 0); + + list_add_tail(&timer_node->list, + &fwrt->trans->dbg.periodic_trig_list); + + IWL_DEBUG_FW(fwrt, "WRT: Enabling periodic trigger\n"); + + mod_timer(&timer_node->timer, + jiffies + msecs_to_jiffies(collect_interval)); + } +} + +static bool is_trig_data_contained(const struct iwl_ucode_tlv *new, + const struct iwl_ucode_tlv *old) +{ + const struct iwl_fw_ini_trigger_tlv *new_trig = (const void *)new->data; + const struct iwl_fw_ini_trigger_tlv *old_trig = (const void *)old->data; + const __le32 *new_data = new_trig->data, *old_data = old_trig->data; + u32 new_dwords_num = iwl_tlv_array_len(new, new_trig, data); + u32 old_dwords_num = iwl_tlv_array_len(old, old_trig, data); + int i, j; + + for (i = 0; i < new_dwords_num; i++) { + bool match = false; + + for (j = 0; j < old_dwords_num; j++) { + if (new_data[i] == old_data[j]) { + match = true; + break; + } + } + if (!match) + return false; + } + + return true; +} + +static int iwl_dbg_tlv_override_trig_node(struct iwl_fw_runtime *fwrt, + struct iwl_ucode_tlv *trig_tlv, + struct iwl_dbg_tlv_node *node) +{ + struct iwl_ucode_tlv *node_tlv = &node->tlv; + struct iwl_fw_ini_trigger_tlv *node_trig = (void *)node_tlv->data; + struct iwl_fw_ini_trigger_tlv *trig = (void *)trig_tlv->data; + u32 policy = le32_to_cpu(trig->apply_policy); + u32 size = le32_to_cpu(trig_tlv->length); + u32 trig_data_len = size - sizeof(*trig); + u32 offset = 0; + + if (!(policy & IWL_FW_INI_APPLY_POLICY_OVERRIDE_DATA)) { + u32 data_len = le32_to_cpu(node_tlv->length) - + sizeof(*node_trig); + + IWL_DEBUG_FW(fwrt, + "WRT: Appending trigger data (time point %u)\n", + le32_to_cpu(trig->time_point)); + + offset += data_len; + size += data_len; + } else { + IWL_DEBUG_FW(fwrt, + "WRT: Overriding trigger data (time point %u)\n", + le32_to_cpu(trig->time_point)); + } + + if (size != le32_to_cpu(node_tlv->length)) { + struct list_head *prev = node->list.prev; + struct iwl_dbg_tlv_node *tmp; + + list_del(&node->list); + + tmp = krealloc(node, sizeof(*node) + size, GFP_KERNEL); + if (!tmp) { + IWL_WARN(fwrt, + "WRT: No memory to override trigger (time point %u)\n", + le32_to_cpu(trig->time_point)); + + list_add(&node->list, prev); + + return -ENOMEM; + } + + list_add(&tmp->list, prev); + node_tlv = &tmp->tlv; + node_trig = (void *)node_tlv->data; + } + + memcpy(node_trig->data + offset, trig->data, trig_data_len); + node_tlv->length = cpu_to_le32(size); + + if (policy & IWL_FW_INI_APPLY_POLICY_OVERRIDE_CFG) { + IWL_DEBUG_FW(fwrt, + "WRT: Overriding trigger configuration (time point %u)\n", + le32_to_cpu(trig->time_point)); + + /* the first 11 dwords are configuration related */ + memcpy(node_trig, trig, sizeof(__le32) * 11); + } + + if (policy & IWL_FW_INI_APPLY_POLICY_OVERRIDE_REGIONS) { + IWL_DEBUG_FW(fwrt, + "WRT: Overriding trigger regions (time point %u)\n", + le32_to_cpu(trig->time_point)); + + node_trig->regions_mask = trig->regions_mask; + } else { + IWL_DEBUG_FW(fwrt, + "WRT: Appending trigger regions (time point %u)\n", + le32_to_cpu(trig->time_point)); + + node_trig->regions_mask |= trig->regions_mask; + } + + return 0; +} + +static int +iwl_dbg_tlv_add_active_trigger(struct iwl_fw_runtime *fwrt, + struct list_head *trig_list, + struct iwl_ucode_tlv *trig_tlv) +{ + struct iwl_fw_ini_trigger_tlv *trig = (void *)trig_tlv->data; + struct iwl_dbg_tlv_node *node, *match = NULL; + u32 policy = le32_to_cpu(trig->apply_policy); + + list_for_each_entry(node, trig_list, list) { + if (!(policy & IWL_FW_INI_APPLY_POLICY_MATCH_TIME_POINT)) + break; + + if (!(policy & IWL_FW_INI_APPLY_POLICY_MATCH_DATA) || + is_trig_data_contained(trig_tlv, &node->tlv)) { + match = node; + break; + } + } + + if (!match) { + IWL_DEBUG_FW(fwrt, "WRT: Enabling trigger (time point %u)\n", + le32_to_cpu(trig->time_point)); + return iwl_dbg_tlv_add(trig_tlv, trig_list); + } + + return iwl_dbg_tlv_override_trig_node(fwrt, trig_tlv, match); +} + +static void +iwl_dbg_tlv_gen_active_trig_list(struct iwl_fw_runtime *fwrt, + struct iwl_dbg_tlv_time_point_data *tp) +{ + struct iwl_dbg_tlv_node *node; + struct list_head *trig_list = &tp->trig_list; + struct list_head *active_trig_list = &tp->active_trig_list; + + list_for_each_entry(node, trig_list, list) { + struct iwl_ucode_tlv *tlv = &node->tlv; + + iwl_dbg_tlv_add_active_trigger(fwrt, active_trig_list, tlv); + } +} + +static bool iwl_dbg_tlv_check_fw_pkt(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + union iwl_dbg_tlv_tp_data *tp_data, + u32 trig_data) +{ + struct iwl_rx_packet *pkt = tp_data->fw_pkt; + struct iwl_cmd_header *wanted_hdr = (void *)&trig_data; + + if (pkt && (pkt->hdr.cmd == wanted_hdr->cmd && + pkt->hdr.group_id == wanted_hdr->group_id)) { + struct iwl_rx_packet *fw_pkt = + kmemdup(pkt, + sizeof(*pkt) + iwl_rx_packet_payload_len(pkt), + GFP_ATOMIC); + + if (!fw_pkt) + return false; + + dump_data->fw_pkt = fw_pkt; + + return true; + } + + return false; +} + +static int +iwl_dbg_tlv_tp_trigger(struct iwl_fw_runtime *fwrt, bool sync, + struct list_head *active_trig_list, + union iwl_dbg_tlv_tp_data *tp_data, + bool (*data_check)(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + union iwl_dbg_tlv_tp_data *tp_data, + u32 trig_data)) +{ + struct iwl_dbg_tlv_node *node; + + list_for_each_entry(node, active_trig_list, list) { + struct iwl_fwrt_dump_data dump_data = { + .trig = (void *)node->tlv.data, + }; + u32 num_data = iwl_tlv_array_len(&node->tlv, dump_data.trig, + data); + int ret, i; + + if (!num_data) { + ret = iwl_fw_dbg_ini_collect(fwrt, &dump_data, sync); + if (ret) + return ret; + } + + for (i = 0; i < num_data; i++) { + if (!data_check || + data_check(fwrt, &dump_data, tp_data, + le32_to_cpu(dump_data.trig->data[i]))) { + ret = iwl_fw_dbg_ini_collect(fwrt, &dump_data, sync); + if (ret) + return ret; + + break; + } + } + } + + return 0; +} + +static void iwl_dbg_tlv_init_cfg(struct iwl_fw_runtime *fwrt) +{ + enum iwl_fw_ini_buffer_location *ini_dest = &fwrt->trans->dbg.ini_dest; + int ret, i; + u32 failed_alloc = 0; + + if (*ini_dest != IWL_FW_INI_LOCATION_INVALID) + return; + + IWL_DEBUG_FW(fwrt, + "WRT: Generating active triggers list, domain 0x%x\n", + fwrt->trans->dbg.domains_bitmap); + + for (i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.time_point); i++) { + struct iwl_dbg_tlv_time_point_data *tp = + &fwrt->trans->dbg.time_point[i]; + + iwl_dbg_tlv_gen_active_trig_list(fwrt, tp); + } + + *ini_dest = IWL_FW_INI_LOCATION_INVALID; + for (i = 0; i < IWL_FW_INI_ALLOCATION_NUM; i++) { + struct iwl_fw_ini_allocation_tlv *fw_mon_cfg = + &fwrt->trans->dbg.fw_mon_cfg[i]; + u32 dest = le32_to_cpu(fw_mon_cfg->buf_location); + + if (dest == IWL_FW_INI_LOCATION_INVALID) { + failed_alloc |= BIT(i); + continue; + } + + if (*ini_dest == IWL_FW_INI_LOCATION_INVALID) + *ini_dest = dest; + + if (dest != *ini_dest) + continue; + + ret = iwl_dbg_tlv_alloc_fragments(fwrt, i); + + if (ret) { + IWL_WARN(fwrt, + "WRT: Failed to allocate DRAM buffer for allocation id %d, ret=%d\n", + i, ret); + failed_alloc |= BIT(i); + } + } + + if (!failed_alloc) + return; + + for (i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.active_regions) && failed_alloc; i++) { + struct iwl_fw_ini_region_tlv *reg; + struct iwl_ucode_tlv **active_reg = + &fwrt->trans->dbg.active_regions[i]; + u32 reg_type; + + if (!*active_reg) { + fwrt->trans->dbg.unsupported_region_msk |= BIT(i); + continue; + } + + reg = (void *)(*active_reg)->data; + reg_type = le32_to_cpu(reg->type); + + if (reg_type != IWL_FW_INI_REGION_DRAM_BUFFER || + !(BIT(le32_to_cpu(reg->dram_alloc_id)) & failed_alloc)) + continue; + + IWL_DEBUG_FW(fwrt, + "WRT: removing allocation id %d from region id %d\n", + le32_to_cpu(reg->dram_alloc_id), i); + + failed_alloc &= ~le32_to_cpu(reg->dram_alloc_id); + fwrt->trans->dbg.unsupported_region_msk |= BIT(i); + + kfree(*active_reg); + *active_reg = NULL; + } +} + +void _iwl_dbg_tlv_time_point(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data, + bool sync) +{ + struct list_head *hcmd_list, *trig_list, *conf_list; + + if (!iwl_trans_dbg_ini_valid(fwrt->trans) || + tp_id == IWL_FW_INI_TIME_POINT_INVALID || + tp_id >= IWL_FW_INI_TIME_POINT_NUM) + return; + + hcmd_list = &fwrt->trans->dbg.time_point[tp_id].hcmd_list; + trig_list = &fwrt->trans->dbg.time_point[tp_id].active_trig_list; + conf_list = &fwrt->trans->dbg.time_point[tp_id].config_list; + + switch (tp_id) { + case IWL_FW_INI_TIME_POINT_EARLY: + iwl_dbg_tlv_init_cfg(fwrt); + iwl_dbg_tlv_apply_config(fwrt, conf_list); + iwl_dbg_tlv_update_drams(fwrt); + iwl_dbg_tlv_tp_trigger(fwrt, sync, trig_list, tp_data, NULL); + break; + case IWL_FW_INI_TIME_POINT_AFTER_ALIVE: + iwl_dbg_tlv_apply_buffers(fwrt); + iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); + iwl_dbg_tlv_apply_config(fwrt, conf_list); + iwl_dbg_tlv_tp_trigger(fwrt, sync, trig_list, tp_data, NULL); + break; + case IWL_FW_INI_TIME_POINT_PERIODIC: + iwl_dbg_tlv_set_periodic_trigs(fwrt); + iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); + break; + case IWL_FW_INI_TIME_POINT_FW_RSP_OR_NOTIF: + case IWL_FW_INI_TIME_POINT_MISSED_BEACONS: + case IWL_FW_INI_TIME_POINT_FW_DHC_NOTIFICATION: + iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); + iwl_dbg_tlv_apply_config(fwrt, conf_list); + iwl_dbg_tlv_tp_trigger(fwrt, sync, trig_list, tp_data, + iwl_dbg_tlv_check_fw_pkt); + break; + default: + iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); + iwl_dbg_tlv_apply_config(fwrt, conf_list); + iwl_dbg_tlv_tp_trigger(fwrt, sync, trig_list, tp_data, NULL); + break; + } +} +IWL_EXPORT_SYMBOL(_iwl_dbg_tlv_time_point); diff --git a/sys/contrib/dev/iwlwifi/iwl-dbg-tlv.h b/sys/contrib/dev/iwlwifi/iwl-dbg-tlv.h new file mode 100644 index 00000000000..79287708bd6 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-dbg-tlv.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2018-2021 Intel Corporation + */ +#ifndef __iwl_dbg_tlv_h__ +#define __iwl_dbg_tlv_h__ + +#include +#include +#include +#include + +/** + * struct iwl_dbg_tlv_node - debug TLV node + * @list: list of &struct iwl_dbg_tlv_node + * @tlv: debug TLV + */ +struct iwl_dbg_tlv_node { + struct list_head list; + struct iwl_ucode_tlv tlv; +}; + +/** + * union iwl_dbg_tlv_tp_data - data that is given in a time point + * @fw_pkt: a packet received from the FW + */ +union iwl_dbg_tlv_tp_data { + struct iwl_rx_packet *fw_pkt; +}; + +/** + * struct iwl_dbg_tlv_time_point_data + * @trig_list: list of triggers + * @active_trig_list: list of active triggers + * @hcmd_list: list of host commands + * @config_list: list of configuration + */ +struct iwl_dbg_tlv_time_point_data { + struct list_head trig_list; + struct list_head active_trig_list; + struct list_head hcmd_list; + struct list_head config_list; +}; + +struct iwl_trans; +struct iwl_fw_runtime; + +void iwl_dbg_tlv_load_bin(struct device *dev, struct iwl_trans *trans); +void iwl_dbg_tlv_free(struct iwl_trans *trans); +void iwl_dbg_tlv_alloc(struct iwl_trans *trans, const struct iwl_ucode_tlv *tlv, + bool ext); +void iwl_dbg_tlv_init(struct iwl_trans *trans); +void _iwl_dbg_tlv_time_point(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data, + bool sync); + +static inline void iwl_dbg_tlv_time_point(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data) +{ + _iwl_dbg_tlv_time_point(fwrt, tp_id, tp_data, false); +} + +static inline void iwl_dbg_tlv_time_point_sync(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data) +{ + _iwl_dbg_tlv_time_point(fwrt, tp_id, tp_data, true); +} + +void iwl_dbg_tlv_del_timers(struct iwl_trans *trans); + +#endif /* __iwl_dbg_tlv_h__*/ diff --git a/sys/contrib/dev/iwlwifi/iwl-debug.c b/sys/contrib/dev/iwlwifi/iwl-debug.c new file mode 100644 index 00000000000..4a121a1a435 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-debug.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2011, 2021 Intel Corporation + */ +#include +#include +#include +#if defined(CONFIG_IWLWIFI_DEBUG) +#include +#endif +#include "iwl-drv.h" +#include "iwl-debug.h" +#include "iwl-modparams.h" +#include "iwl-devtrace.h" + +#if defined(__FreeBSD__) +#if defined(CONFIG_IWLWIFI_DEBUG) +#include /* hexdump(9) */ +#include +#endif +#endif + +#if defined(__linux__) +#define __iwl_fn(fn) \ +void __iwl_ ##fn(struct device *dev, const char *fmt, ...) \ +{ \ + struct va_format vaf = { \ + .fmt = fmt, \ + }; \ + va_list args; \ + \ + va_start(args, fmt); \ + vaf.va = &args; \ + dev_ ##fn(dev, "%pV", &vaf); \ + trace_iwlwifi_ ##fn(&vaf); \ + va_end(args); \ +} +#elif defined(__FreeBSD__) +#define __iwl_fn(fn) \ +void __iwl_ ##fn(struct device *dev, const char *fmt, ...) \ +{ \ + struct va_format vaf = { \ + .fmt = fmt, \ + }; \ + va_list args; \ + char *str; \ + \ + va_start(args, fmt); \ + vaf.va = &args; \ + vasprintf(&str, M_KMALLOC, fmt, args); \ + dev_ ##fn(dev, "%s", str); \ + trace_iwlwifi_ ##fn(&vaf); \ + free(str, M_KMALLOC); \ + va_end(args); \ +} +#endif + +__iwl_fn(warn) +IWL_EXPORT_SYMBOL(__iwl_warn); +__iwl_fn(info) +IWL_EXPORT_SYMBOL(__iwl_info); +__iwl_fn(crit) +IWL_EXPORT_SYMBOL(__iwl_crit); + +void __iwl_err(struct device *dev, enum iwl_err_mode mode, const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args, args2; + + va_start(args, fmt); + switch (mode) { + case IWL_ERR_MODE_RATELIMIT: + if (net_ratelimit()) + break; + fallthrough; + case IWL_ERR_MODE_REGULAR: + case IWL_ERR_MODE_RFKILL: + va_copy(args2, args); + vaf.va = &args2; +#if defined(__linux_) + if (mode == IWL_ERR_MODE_RFKILL) + dev_err(dev, "(RFKILL) %pV", &vaf); + else + dev_err(dev, "%pV", &vaf); +#elif defined(__FreeBSD__) + char *str; + vasprintf(&str, M_KMALLOC, fmt, args2); + dev_err(dev, "%s%s", (mode == IWL_ERR_MODE_RFKILL) ? "(RFKILL)" : "", str); + free(str, M_KMALLOC); +#endif + va_end(args2); + break; + default: + break; + } + trace_iwlwifi_err(&vaf); + va_end(args); +} +IWL_EXPORT_SYMBOL(__iwl_err); + +#if defined(CONFIG_IWLWIFI_DEBUG) || defined(CONFIG_IWLWIFI_DEVICE_TRACING) + +#ifdef CONFIG_IWLWIFI_DEBUG +bool +iwl_have_debug_level(enum iwl_dl level) +{ + + return (iwlwifi_mod_params.debug_level & level || level == IWL_DL_ANY); +} + +/* Passing the iwl_drv * in seems pointless. */ +void +iwl_print_hex_dump(void *drv __unused, enum iwl_dl level, + const char *prefix, uint8_t *data, size_t len) +{ + + /* Given we have a level, check for it. */ + if (!iwl_have_debug_level(level)) + return; + +#if defined(__linux_) + /* XXX I am cluseless in my editor. pcie/trans.c to the rescue. */ + print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, + 32, 4, data, len, 0); +#elif defined(__FreeBSD__) + hexdump(data, len, prefix, 0); +#endif +} +#endif + +void __iwl_dbg(struct device *dev, + u32 level, bool limit, const char *function, + const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + + va_start(args, fmt); + vaf.va = &args; +#if defined(CONFIG_IWLWIFI_DEBUG) + if (iwl_have_debug_level(level) && + (!limit || net_ratelimit())) { +#if defined(__linux_) + dev_printk(KERN_DEBUG, dev, "%s %pV", function, &vaf); +#elif defined(__FreeBSD__) + char *str; + vasprintf(&str, M_KMALLOC, fmt, args); + dev_printk(KERN_DEBUG, dev, "%d %u %s %s", + curthread->td_tid, (unsigned int)ticks, function, str); + free(str, M_KMALLOC); +#endif + } + +#endif + trace_iwlwifi_dbg(level, function, &vaf); + va_end(args); +} +IWL_EXPORT_SYMBOL(__iwl_dbg); +#endif diff --git a/sys/contrib/dev/iwlwifi/iwl-debug.h b/sys/contrib/dev/iwlwifi/iwl-debug.h new file mode 100644 index 00000000000..e1316925442 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-debug.h @@ -0,0 +1,193 @@ +/*- + * Copyright (c) 2020-2021 The FreeBSD Foundation + * + * This software was developed by Björn Zeeb under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _IWL_DEBUG_H +#define _IWL_DEBUG_H + +#if defined(__FreeBSD__) +#ifdef CONFIG_IWLWIFI_DEBUG +#include +#endif +#endif + +#include + +enum iwl_dl { + IWL_DL_ASSOC = 0x00000001, + IWL_DL_CALIB = 0x00000002, + IWL_DL_COEX = 0x00000004, + IWL_DL_WOWLAN = 0x00000008, + IWL_DL_DROP = 0x00000010, + IWL_DL_EEPROM = 0x00000020, + IWL_DL_FW = 0x00000040, + /* = 0x00000080, */ + IWL_DL_HC = 0x00000100, + IWL_DL_HT = 0x00000200, + IWL_DL_INFO = 0x00000400, + IWL_DL_ISR = 0x00000800, + IWL_DL_LAR = 0x00001000, + IWL_DL_MAC80211 = 0x00002000, + IWL_DL_POWER = 0x00004000, + IWL_DL_QUOTA = 0x00008000, + IWL_DL_RADIO = 0x00010000, + IWL_DL_RATE = 0x00020000, + IWL_DL_RF_KILL = 0x00040000, + IWL_DL_RX = 0x00080000, + IWL_DL_SCAN = 0x00100000, + IWL_DL_STATS = 0x00200000, + /* = 0x00400000, */ + IWL_DL_TDLS = 0x00800000, + IWL_DL_TE = 0x01000000, + IWL_DL_TEMP = 0x02000000, + IWL_DL_TPT = 0x04000000, + IWL_DL_TX = 0x08000000, + IWL_DL_TX_QUEUES = 0x10000000, + IWL_DL_TX_REPLY = 0x20000000, + IWL_DL_WEP = 0x40000000, + + IWL_DL_PCI_RW = 0x80000000, + + IWL_DL_ANY = 0x7fffffff, +}; + +enum iwl_err_mode { + IWL_ERR_MODE_RATELIMIT, + IWL_ERR_MODE_REGULAR, + IWL_ERR_MODE_RFKILL, /* XXX we do not pass that from anywhere? */ +}; + +void __iwl_crit(struct device *, const char *, ...); +void __iwl_info(struct device *, const char *, ...); +void __iwl_warn(struct device *, const char *, ...); +void __iwl_err(struct device *, enum iwl_err_mode, const char *, ...); + +#define IWL_CRIT(_subsys, _fmt, ...) \ + __iwl_crit((_subsys)->dev, (_fmt), ##__VA_ARGS__) +#define IWL_INFO(_subsys, _fmt, ...) \ + __iwl_info((_subsys)->dev, (_fmt), ##__VA_ARGS__) +#define IWL_WARN(_subsys, _fmt, ...) \ + __iwl_warn((_subsys)->dev, (_fmt), ##__VA_ARGS__) +/* XXX Not sure what the two bools are good for if never passed. */ +#define __IWL_ERR_DEV(_dev, _mode, _fmt, ...) \ + __iwl_err((_dev), IWL_ERR_MODE_REGULAR, _fmt, ##__VA_ARGS__) +#define IWL_ERR_DEV(_dev, _fmt, ...) \ + __IWL_ERR_DEV(_dev, IWL_ERR_MODE_REGULAR, _fmt, ##__VA_ARGS__) +#define IWL_ERR(_subsys, _fmt, ...) \ + IWL_ERR_DEV((_subsys)->dev, _fmt, ##__VA_ARGS__) +#define IWL_ERR_LIMIT(_subsys, _fmt, ...) \ + __IWL_ERR_DEV((_subsys)->dev, IWL_ERR_MODE_RATELIMIT, \ + _fmt, ##__VA_ARGS__) + +#define iwl_print_hex_error(_subsys, _pkt, _n) /* XXX-BZ TODO */ + +#ifdef CONFIG_IWLWIFI_DEBUG +bool iwl_have_debug_level(enum iwl_dl); +void iwl_print_hex_dump(void *, enum iwl_dl, const char *, uint8_t *, size_t); +void __iwl_dbg(struct device *, u32, bool, const char *, const char *fmt, ...); + +#define IWL_DPRINTF_DEV(_dev, _e, _fmt, ...) \ + __iwl_dbg((_dev), (_e), false, __func__, (_fmt), ##__VA_ARGS__) +#define IWL_DPRINTF(_subsys, _e, _fmt, ...) \ + IWL_DPRINTF_DEV((_subsys)->dev, (_e), (_fmt), ##__VA_ARGS__) + +#else /* !CONFIG_IWLWIFI_DEBUG */ +#define IWL_DPRINTF_DEV(_dev, _e, _fmt, ...) +#define IWL_DPRINTF(_subsys, _e, _fmt, ...) +#endif /* CONFIG_IWLWIFI_DEBUG */ + +#define IWL_DEBUG_ASSOC(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_ASSOC, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_CALIB(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_CALIB, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_COEX(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_COEX, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_DEV(_dev, _level, _fmt, ...) \ + IWL_DPRINTF_DEV((_dev), (_level), (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_DROP(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_DROP, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_EEPROM(_dev, _fmt, ...) \ + IWL_DPRINTF_DEV((_dev), IWL_DL_EEPROM, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_FW(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_FW, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_FW_INFO(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_FW | IWL_DL_INFO, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_HC(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_HC, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_HT(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_HT, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_INFO(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_INFO, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_ISR(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_ISR, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_LAR(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_LAR, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_MAC80211(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_MAC80211, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_POWER(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_POWER, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_QUOTA(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_QUOTA, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_RADIO(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_RADIO, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_RATE(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_RATE, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_RF_KILL(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_RF_KILL, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_RX(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_RX, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_SCAN(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_SCAN, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_STATS(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_STATS, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_STATS_LIMIT(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_STATS, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_TDLS(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_TDLS, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_TE(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_TE, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_TEMP(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_TEMP, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_TPT(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_TPT, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_TX(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_TX, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_TX_QUEUES(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_TX_QUEUES, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_TX_REPLY(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_TX_REPLY, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_WEP(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_WEP, (_fmt), ##__VA_ARGS__) +#define IWL_DEBUG_WOWLAN(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_WOWLAN, (_fmt), ##__VA_ARGS__) + +#define IWL_DEBUG_PCI_RW(_subsys, _fmt, ...) \ + IWL_DPRINTF((_subsys), IWL_DL_PCI_RW, (_fmt), ##__VA_ARGS__) + +#endif /* _IWL_DEBUG_H */ diff --git a/sys/contrib/dev/iwlwifi/iwl-devtrace.c b/sys/contrib/dev/iwlwifi/iwl-devtrace.c new file mode 100644 index 00000000000..8617ab6cd21 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-devtrace.c @@ -0,0 +1,82 @@ +/*- + * Copyright (c) 2021 The FreeBSD Foundation + * + * This software was developed by Björn Zeeb under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "iwl-devtrace.h" + +#include + +SDT_PROVIDER_DEFINE(iwlwifi); + +SDT_PROBE_DEFINE4(iwlwifi, trace, dev_hcmd, , + "const struct device *dev", + "struct iwl_host_cmd *", "uint16_t", + "struct iwl_cmd_header_wide *"); + +void +trace_iwlwifi_dev_hcmd(const struct device *dev, + struct iwl_host_cmd *cmd, uint16_t cmd_size, + struct iwl_cmd_header_wide *hdr_wide) +{ + + SDT_PROBE4(iwlwifi, trace, dev_hcmd, , + dev, cmd, cmd_size, hdr_wide); +} + +SDT_PROBE_DEFINE4(iwlwifi, trace, dev_rx, , + "const struct device *dev", + "const struct iwl_trans *", + "struct iwl_rx_packet *", "size_t"); + +void +trace_iwlwifi_dev_rx(const struct device *dev, + const struct iwl_trans *trans, + struct iwl_rx_packet *pkt, size_t len) +{ + + SDT_PROBE4(iwlwifi, trace, dev_rx, , + dev, trans, pkt, len); +} + +SDT_PROBE_DEFINE4(iwlwifi, trace, dev_rx_data, , + "const struct device *dev", + "const struct iwl_trans *", + "struct iwl_rx_packet *", "size_t"); + +void +trace_iwlwifi_dev_rx_data(const struct device *dev, + const struct iwl_trans *trans, + struct iwl_rx_packet *pkt, size_t len) +{ + + SDT_PROBE4(iwlwifi, trace, dev_rx_data, , + dev, trans, pkt, len); +} + diff --git a/sys/contrib/dev/iwlwifi/iwl-devtrace.h b/sys/contrib/dev/iwlwifi/iwl-devtrace.h new file mode 100644 index 00000000000..1a0d6a242b2 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-devtrace.h @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 2020-2021 The FreeBSD Foundation + * + * This software was developed by Björn Zeeb under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _IWL_DEVTRACE_H +#define _IWL_DEVTRACE_H + +#include +#include + +#include "iwl-trans.h" + +void trace_iwlwifi_dev_hcmd(const struct device *, + struct iwl_host_cmd *, uint16_t, + struct iwl_cmd_header_wide *); + +void trace_iwlwifi_dev_rx(const struct device *, + const struct iwl_trans *, + struct iwl_rx_packet *, size_t); + +void trace_iwlwifi_dev_rx_data(const struct device *, + const struct iwl_trans *, + struct iwl_rx_packet *, size_t); + +#define trace_iwlwifi_dev_ict_read(...) +#define trace_iwlwifi_dev_ioread32(...) +#define trace_iwlwifi_dev_ioread_prph32(...) +#define trace_iwlwifi_dev_iowrite32(...) +#define trace_iwlwifi_dev_iowrite64(...) +#define trace_iwlwifi_dev_iowrite8(...) +#define trace_iwlwifi_dev_iowrite_prph32(...) +#define trace_iwlwifi_dev_iowrite_prph64(...) +#define trace_iwlwifi_dev_irq(...) +#define trace_iwlwifi_dev_irq_msix(...) +#define trace_iwlwifi_dev_tx(...) +#define trace_iwlwifi_dev_tx_tb(...) + +#define trace_iwlwifi_crit(...) +#define trace_iwlwifi_dbg(...) +#define trace_iwlwifi_err(...) +#define trace_iwlwifi_info(...) +#define trace_iwlwifi_warn(...) + +#endif /* _IWL_DEVTRACE_H */ diff --git a/sys/contrib/dev/iwlwifi/iwl-drv.c b/sys/contrib/dev/iwlwifi/iwl-drv.c new file mode 100644 index 00000000000..4486920f59e --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-drv.c @@ -0,0 +1,1926 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#if defined(__FreeBSD__) +#define LINUXKPI_PARAM_PREFIX iwlwifi_ +#endif +#include +#include +#include +#include +#include + +#include "iwl-drv.h" +#include "iwl-csr.h" +#include "iwl-debug.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-agn-hw.h" +#include "fw/img.h" +#include "iwl-dbg-tlv.h" +#include "iwl-config.h" +#include "iwl-modparams.h" +#include "fw/api/alive.h" +#include "fw/api/mac.h" + +/****************************************************************************** + * + * module boiler plate + * + ******************************************************************************/ + +#if defined(__linux__) +#define DRV_DESCRIPTION "Intel(R) Wireless WiFi driver for Linux" +MODULE_LICENSE("GPL"); +#elif defined(__FreeBSD__) +#define DRV_DESCRIPTION "Intel(R) Wireless WiFi based driver for FreeBSD" +MODULE_LICENSE("BSD"); +MODULE_VERSION(iwl, 1); +MODULE_DEPEND(iwl, linuxkpi, 1, 1, 1); +#endif +MODULE_DESCRIPTION(DRV_DESCRIPTION); + +#ifdef CONFIG_IWLWIFI_DEBUGFS +static struct dentry *iwl_dbgfs_root; +#endif + +/** + * struct iwl_drv - drv common data + * @list: list of drv structures using this opmode + * @fw: the iwl_fw structure + * @op_mode: the running op_mode + * @trans: transport layer + * @dev: for debug prints only + * @fw_index: firmware revision to try loading + * @firmware_name: composite filename of ucode file to load + * @request_firmware_complete: the firmware has been obtained from user space + * @dbgfs_drv: debugfs root directory entry + * @dbgfs_trans: debugfs transport directory entry + * @dbgfs_op_mode: debugfs op_mode directory entry + */ +struct iwl_drv { + struct list_head list; + struct iwl_fw fw; + + struct iwl_op_mode *op_mode; + struct iwl_trans *trans; + struct device *dev; + + int fw_index; /* firmware we're trying to load */ + char firmware_name[64]; /* name of firmware file to load */ + + struct completion request_firmware_complete; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct dentry *dbgfs_drv; + struct dentry *dbgfs_trans; + struct dentry *dbgfs_op_mode; +#endif +}; + +enum { + DVM_OP_MODE, + MVM_OP_MODE, +}; + +/* Protects the table contents, i.e. the ops pointer & drv list */ +static DEFINE_MUTEX(iwlwifi_opmode_table_mtx); +static struct iwlwifi_opmode_table { + const char *name; /* name: iwldvm, iwlmvm, etc */ + const struct iwl_op_mode_ops *ops; /* pointer to op_mode ops */ + struct list_head drv; /* list of devices using this op_mode */ +} iwlwifi_opmode_table[] = { /* ops set when driver is initialized */ + [DVM_OP_MODE] = { .name = "iwldvm", .ops = NULL }, + [MVM_OP_MODE] = { .name = "iwlmvm", .ops = NULL }, +}; + +#define IWL_DEFAULT_SCAN_CHANNELS 40 + +/* + * struct fw_sec: Just for the image parsing process. + * For the fw storage we are using struct fw_desc. + */ +struct fw_sec { + const void *data; /* the sec data */ + size_t size; /* section size */ + u32 offset; /* offset of writing in the device */ +}; + +static void iwl_free_fw_desc(struct iwl_drv *drv, struct fw_desc *desc) +{ + vfree(desc->data); + desc->data = NULL; + desc->len = 0; +} + +static void iwl_free_fw_img(struct iwl_drv *drv, struct fw_img *img) +{ + int i; + for (i = 0; i < img->num_sec; i++) + iwl_free_fw_desc(drv, &img->sec[i]); + kfree(img->sec); +} + +static void iwl_dealloc_ucode(struct iwl_drv *drv) +{ + int i; + + kfree(drv->fw.dbg.dest_tlv); + for (i = 0; i < ARRAY_SIZE(drv->fw.dbg.conf_tlv); i++) + kfree(drv->fw.dbg.conf_tlv[i]); + for (i = 0; i < ARRAY_SIZE(drv->fw.dbg.trigger_tlv); i++) + kfree(drv->fw.dbg.trigger_tlv[i]); + kfree(drv->fw.dbg.mem_tlv); + kfree(drv->fw.iml); + kfree(drv->fw.ucode_capa.cmd_versions); + kfree(drv->fw.phy_integration_ver); + + for (i = 0; i < IWL_UCODE_TYPE_MAX; i++) + iwl_free_fw_img(drv, drv->fw.img + i); +} + +static int iwl_alloc_fw_desc(struct iwl_drv *drv, struct fw_desc *desc, + struct fw_sec *sec) +{ + void *data; + + desc->data = NULL; + + if (!sec || !sec->size) + return -EINVAL; + + data = vmalloc(sec->size); + if (!data) + return -ENOMEM; + + desc->len = sec->size; + desc->offset = sec->offset; + memcpy(data, sec->data, desc->len); + desc->data = data; + + return 0; +} + +static void iwl_req_fw_callback(const struct firmware *ucode_raw, + void *context); + +static int iwl_request_firmware(struct iwl_drv *drv, bool first) +{ + const struct iwl_cfg *cfg = drv->trans->cfg; + char tag[8]; + + if (drv->trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_9000 && + (CSR_HW_REV_STEP(drv->trans->hw_rev) != SILICON_B_STEP && + CSR_HW_REV_STEP(drv->trans->hw_rev) != SILICON_C_STEP)) { + IWL_ERR(drv, + "Only HW steps B and C are currently supported (0x%0x)\n", + drv->trans->hw_rev); + return -EINVAL; + } + + if (first) { + drv->fw_index = cfg->ucode_api_max; + sprintf(tag, "%d", drv->fw_index); + } else { + drv->fw_index--; + sprintf(tag, "%d", drv->fw_index); + } + + if (drv->fw_index < cfg->ucode_api_min) { + IWL_ERR(drv, "no suitable firmware found!\n"); + + if (cfg->ucode_api_min == cfg->ucode_api_max) { + IWL_ERR(drv, "%s%d is required\n", cfg->fw_name_pre, + cfg->ucode_api_max); + } else { + IWL_ERR(drv, "minimum version required: %s%d\n", + cfg->fw_name_pre, cfg->ucode_api_min); + IWL_ERR(drv, "maximum version supported: %s%d\n", + cfg->fw_name_pre, cfg->ucode_api_max); + } + + IWL_ERR(drv, + "check git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git\n"); + return -ENOENT; + } + + snprintf(drv->firmware_name, sizeof(drv->firmware_name), "%s%s.ucode", + cfg->fw_name_pre, tag); + + IWL_DEBUG_FW_INFO(drv, "attempting to load firmware '%s'\n", + drv->firmware_name); + + return request_firmware_nowait(THIS_MODULE, 1, drv->firmware_name, + drv->trans->dev, + GFP_KERNEL, drv, iwl_req_fw_callback); +} + +struct fw_img_parsing { + struct fw_sec *sec; + int sec_counter; +}; + +/* + * struct fw_sec_parsing: to extract fw section and it's offset from tlv + */ +struct fw_sec_parsing { + __le32 offset; + const u8 data[]; +} __packed; + +/** + * struct iwl_tlv_calib_data - parse the default calib data from TLV + * + * @ucode_type: the uCode to which the following default calib relates. + * @calib: default calibrations. + */ +struct iwl_tlv_calib_data { + __le32 ucode_type; + struct iwl_tlv_calib_ctrl calib; +} __packed; + +struct iwl_firmware_pieces { + struct fw_img_parsing img[IWL_UCODE_TYPE_MAX]; + + u32 init_evtlog_ptr, init_evtlog_size, init_errlog_ptr; + u32 inst_evtlog_ptr, inst_evtlog_size, inst_errlog_ptr; + + /* FW debug data parsed for driver usage */ + bool dbg_dest_tlv_init; + const u8 *dbg_dest_ver; + union { + const struct iwl_fw_dbg_dest_tlv *dbg_dest_tlv; + const struct iwl_fw_dbg_dest_tlv_v1 *dbg_dest_tlv_v1; + }; + const struct iwl_fw_dbg_conf_tlv *dbg_conf_tlv[FW_DBG_CONF_MAX]; + size_t dbg_conf_tlv_len[FW_DBG_CONF_MAX]; + const struct iwl_fw_dbg_trigger_tlv *dbg_trigger_tlv[FW_DBG_TRIGGER_MAX]; + size_t dbg_trigger_tlv_len[FW_DBG_TRIGGER_MAX]; + struct iwl_fw_dbg_mem_seg_tlv *dbg_mem_tlv; + size_t n_mem_tlv; +}; + +/* + * These functions are just to extract uCode section data from the pieces + * structure. + */ +static struct fw_sec *get_sec(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec) +{ + return &pieces->img[type].sec[sec]; +} + +static void alloc_sec_data(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec) +{ + struct fw_img_parsing *img = &pieces->img[type]; + struct fw_sec *sec_memory; + int size = sec + 1; + size_t alloc_size = sizeof(*img->sec) * size; + + if (img->sec && img->sec_counter >= size) + return; + + sec_memory = krealloc(img->sec, alloc_size, GFP_KERNEL); + if (!sec_memory) + return; + + img->sec = sec_memory; + img->sec_counter = size; +} + +static void set_sec_data(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec, + const void *data) +{ + alloc_sec_data(pieces, type, sec); + + pieces->img[type].sec[sec].data = data; +} + +static void set_sec_size(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec, + size_t size) +{ + alloc_sec_data(pieces, type, sec); + + pieces->img[type].sec[sec].size = size; +} + +static size_t get_sec_size(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec) +{ + return pieces->img[type].sec[sec].size; +} + +static void set_sec_offset(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec, + u32 offset) +{ + alloc_sec_data(pieces, type, sec); + + pieces->img[type].sec[sec].offset = offset; +} + +static int iwl_store_cscheme(struct iwl_fw *fw, const u8 *data, const u32 len) +{ + int i, j; + const struct iwl_fw_cscheme_list *l = + (const struct iwl_fw_cscheme_list *)data; + const struct iwl_fw_cipher_scheme *fwcs; + + if (len < sizeof(*l) || + len < sizeof(l->size) + l->size * sizeof(l->cs[0])) + return -EINVAL; + + for (i = 0, j = 0; i < IWL_UCODE_MAX_CS && i < l->size; i++) { + fwcs = &l->cs[j]; + + /* we skip schemes with zero cipher suite selector */ + if (!fwcs->cipher) + continue; + + fw->cs[j++] = *fwcs; + } + + return 0; +} + +/* + * Gets uCode section from tlv. + */ +static int iwl_store_ucode_sec(struct iwl_firmware_pieces *pieces, + const void *data, enum iwl_ucode_type type, + int size) +{ + struct fw_img_parsing *img; + struct fw_sec *sec; + const struct fw_sec_parsing *sec_parse; + size_t alloc_size; + + if (WARN_ON(!pieces || !data || type >= IWL_UCODE_TYPE_MAX)) + return -1; + + sec_parse = (const struct fw_sec_parsing *)data; + + img = &pieces->img[type]; + + alloc_size = sizeof(*img->sec) * (img->sec_counter + 1); + sec = krealloc(img->sec, alloc_size, GFP_KERNEL); + if (!sec) + return -ENOMEM; + img->sec = sec; + + sec = &img->sec[img->sec_counter]; + + sec->offset = le32_to_cpu(sec_parse->offset); + sec->data = sec_parse->data; + sec->size = size - sizeof(sec_parse->offset); + + ++img->sec_counter; + + return 0; +} + +static int iwl_set_default_calib(struct iwl_drv *drv, const u8 *data) +{ + const struct iwl_tlv_calib_data *def_calib = + (const struct iwl_tlv_calib_data *)data; + u32 ucode_type = le32_to_cpu(def_calib->ucode_type); + if (ucode_type >= IWL_UCODE_TYPE_MAX) { + IWL_ERR(drv, "Wrong ucode_type %u for default calibration.\n", + ucode_type); + return -EINVAL; + } + drv->fw.default_calib[ucode_type].flow_trigger = + def_calib->calib.flow_trigger; + drv->fw.default_calib[ucode_type].event_trigger = + def_calib->calib.event_trigger; + + return 0; +} + +static void iwl_set_ucode_api_flags(struct iwl_drv *drv, const u8 *data, + struct iwl_ucode_capabilities *capa) +{ + const struct iwl_ucode_api *ucode_api = (const void *)data; + u32 api_index = le32_to_cpu(ucode_api->api_index); + u32 api_flags = le32_to_cpu(ucode_api->api_flags); + int i; + + if (api_index >= DIV_ROUND_UP(NUM_IWL_UCODE_TLV_API, 32)) { + IWL_WARN(drv, + "api flags index %d larger than supported by driver\n", + api_index); + return; + } + + for (i = 0; i < 32; i++) { + if (api_flags & BIT(i)) + __set_bit(i + 32 * api_index, capa->_api); + } +} + +static void iwl_set_ucode_capabilities(struct iwl_drv *drv, const u8 *data, + struct iwl_ucode_capabilities *capa) +{ + const struct iwl_ucode_capa *ucode_capa = (const void *)data; + u32 api_index = le32_to_cpu(ucode_capa->api_index); + u32 api_flags = le32_to_cpu(ucode_capa->api_capa); + int i; + + if (api_index >= DIV_ROUND_UP(NUM_IWL_UCODE_TLV_CAPA, 32)) { + IWL_WARN(drv, + "capa flags index %d larger than supported by driver\n", + api_index); + return; + } + + for (i = 0; i < 32; i++) { + if (api_flags & BIT(i)) + __set_bit(i + 32 * api_index, capa->_capa); + } +} + +static const char *iwl_reduced_fw_name(struct iwl_drv *drv) +{ + const char *name = drv->firmware_name; + + if (strncmp(name, "iwlwifi-", 8) == 0) + name += 8; + + return name; +} + +static int iwl_parse_v1_v2_firmware(struct iwl_drv *drv, + const struct firmware *ucode_raw, + struct iwl_firmware_pieces *pieces) +{ + const struct iwl_ucode_header *ucode = (const void *)ucode_raw->data; + u32 api_ver, hdr_size, build; + char buildstr[25]; + const u8 *src; + + drv->fw.ucode_ver = le32_to_cpu(ucode->ver); + api_ver = IWL_UCODE_API(drv->fw.ucode_ver); + + switch (api_ver) { + default: + hdr_size = 28; + if (ucode_raw->size < hdr_size) { + IWL_ERR(drv, "File size too small!\n"); + return -EINVAL; + } + build = le32_to_cpu(ucode->u.v2.build); + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v2.inst_size)); + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v2.data_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v2.init_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v2.init_data_size)); + src = ucode->u.v2.data; + break; + case 0: + case 1: + case 2: + hdr_size = 24; + if (ucode_raw->size < hdr_size) { + IWL_ERR(drv, "File size too small!\n"); + return -EINVAL; + } + build = 0; + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v1.inst_size)); + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v1.data_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v1.init_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v1.init_data_size)); + src = ucode->u.v1.data; + break; + } + + if (build) + sprintf(buildstr, " build %u", build); + else + buildstr[0] = '\0'; + + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%u.%u.%u%s %s", + IWL_UCODE_MAJOR(drv->fw.ucode_ver), + IWL_UCODE_MINOR(drv->fw.ucode_ver), + IWL_UCODE_API(drv->fw.ucode_ver), + IWL_UCODE_SERIAL(drv->fw.ucode_ver), + buildstr, iwl_reduced_fw_name(drv)); + + /* Verify size of file vs. image size info in file's header */ + + if (ucode_raw->size != hdr_size + + get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST) + + get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA) + + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST) + + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA)) { + + IWL_ERR(drv, + "uCode file size %d does not match expected size\n", + (int)ucode_raw->size); + return -EINVAL; + } + + + set_sec_data(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, src); + src += get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST); + set_sec_offset(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + set_sec_data(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, src); + src += get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA); + set_sec_offset(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + set_sec_data(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, src); + src += get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST); + set_sec_offset(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + set_sec_data(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, src); + src += get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA); + set_sec_offset(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + return 0; +} + +static void iwl_drv_set_dump_exclude(struct iwl_drv *drv, + enum iwl_ucode_tlv_type tlv_type, + const void *tlv_data, u32 tlv_len) +{ + const struct iwl_fw_dump_exclude *fw = tlv_data; + struct iwl_dump_exclude *excl; + + if (tlv_len < sizeof(*fw)) + return; + + if (tlv_type == IWL_UCODE_TLV_SEC_TABLE_ADDR) { + excl = &drv->fw.dump_excl[0]; + + /* second time we find this, it's for WoWLAN */ + if (excl->addr) + excl = &drv->fw.dump_excl_wowlan[0]; + } else if (fw_has_capa(&drv->fw.ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG)) { + /* IWL_UCODE_TLV_D3_KEK_KCK_ADDR is regular image */ + excl = &drv->fw.dump_excl[0]; + } else { + /* IWL_UCODE_TLV_D3_KEK_KCK_ADDR is WoWLAN image */ + excl = &drv->fw.dump_excl_wowlan[0]; + } + + if (excl->addr) + excl++; + + if (excl->addr) { + IWL_DEBUG_FW_INFO(drv, "found too many excludes in fw file\n"); + return; + } + + excl->addr = le32_to_cpu(fw->addr) & ~FW_ADDR_CACHE_CONTROL; + excl->size = le32_to_cpu(fw->size); +} + +static int iwl_parse_tlv_firmware(struct iwl_drv *drv, + const struct firmware *ucode_raw, + struct iwl_firmware_pieces *pieces, + struct iwl_ucode_capabilities *capa, + bool *usniffer_images) +{ + const struct iwl_tlv_ucode_header *ucode = (const void *)ucode_raw->data; + const struct iwl_ucode_tlv *tlv; + size_t len = ucode_raw->size; + const u8 *data; + u32 tlv_len; + u32 usniffer_img; + enum iwl_ucode_tlv_type tlv_type; + const u8 *tlv_data; + char buildstr[25]; + u32 build, paging_mem_size; + int num_of_cpus; + bool usniffer_req = false; + + if (len < sizeof(*ucode)) { + IWL_ERR(drv, "uCode has invalid length: %zd\n", len); + return -EINVAL; + } + + if (ucode->magic != cpu_to_le32(IWL_TLV_UCODE_MAGIC)) { + IWL_ERR(drv, "invalid uCode magic: 0X%x\n", + le32_to_cpu(ucode->magic)); + return -EINVAL; + } + + drv->fw.ucode_ver = le32_to_cpu(ucode->ver); + memcpy(drv->fw.human_readable, ucode->human_readable, + sizeof(drv->fw.human_readable)); + build = le32_to_cpu(ucode->build); + + if (build) + sprintf(buildstr, " build %u", build); + else + buildstr[0] = '\0'; + + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%u.%u.%u%s %s", + IWL_UCODE_MAJOR(drv->fw.ucode_ver), + IWL_UCODE_MINOR(drv->fw.ucode_ver), + IWL_UCODE_API(drv->fw.ucode_ver), + IWL_UCODE_SERIAL(drv->fw.ucode_ver), + buildstr, iwl_reduced_fw_name(drv)); + + data = ucode->data; + + len -= sizeof(*ucode); + + while (len >= sizeof(*tlv)) { + len -= sizeof(*tlv); + tlv = (const struct iwl_ucode_tlv *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + tlv_data = tlv->data; + + if (len < tlv_len) { + IWL_ERR(drv, "invalid TLV len: %zd/%u\n", + len, tlv_len); + return -EINVAL; + } + len -= ALIGN(tlv_len, 4); + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + + switch (tlv_type) { + case IWL_UCODE_TLV_INST: + set_sec_data(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST, tlv_data); + set_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST, tlv_len); + set_sec_offset(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + break; + case IWL_UCODE_TLV_DATA: + set_sec_data(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA, tlv_data); + set_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA, tlv_len); + set_sec_offset(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + break; + case IWL_UCODE_TLV_INIT: + set_sec_data(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST, tlv_data); + set_sec_size(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST, tlv_len); + set_sec_offset(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + break; + case IWL_UCODE_TLV_INIT_DATA: + set_sec_data(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_DATA, tlv_data); + set_sec_size(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_DATA, tlv_len); + set_sec_offset(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + break; + case IWL_UCODE_TLV_BOOT: + IWL_ERR(drv, "Found unexpected BOOT ucode\n"); + break; + case IWL_UCODE_TLV_PROBE_MAX_LEN: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + capa->max_probe_length = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_PAN: + if (tlv_len) + goto invalid_tlv_len; + capa->flags |= IWL_UCODE_TLV_FLAGS_PAN; + break; + case IWL_UCODE_TLV_FLAGS: + /* must be at least one u32 */ + if (tlv_len < sizeof(u32)) + goto invalid_tlv_len; + /* and a proper number of u32s */ + if (tlv_len % sizeof(u32)) + goto invalid_tlv_len; + /* + * This driver only reads the first u32 as + * right now no more features are defined, + * if that changes then either the driver + * will not work with the new firmware, or + * it'll not take advantage of new features. + */ + capa->flags = le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_API_CHANGES_SET: + if (tlv_len != sizeof(struct iwl_ucode_api)) + goto invalid_tlv_len; + iwl_set_ucode_api_flags(drv, tlv_data, capa); + break; + case IWL_UCODE_TLV_ENABLED_CAPABILITIES: + if (tlv_len != sizeof(struct iwl_ucode_capa)) + goto invalid_tlv_len; + iwl_set_ucode_capabilities(drv, tlv_data, capa); + break; + case IWL_UCODE_TLV_INIT_EVTLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->init_evtlog_ptr = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_INIT_EVTLOG_SIZE: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->init_evtlog_size = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_INIT_ERRLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->init_errlog_ptr = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_RUNT_EVTLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->inst_evtlog_ptr = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_RUNT_EVTLOG_SIZE: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->inst_evtlog_size = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_RUNT_ERRLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->inst_errlog_ptr = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_ENHANCE_SENS_TBL: + if (tlv_len) + goto invalid_tlv_len; + drv->fw.enhance_sensitivity_table = true; + break; + case IWL_UCODE_TLV_WOWLAN_INST: + set_sec_data(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_INST, tlv_data); + set_sec_size(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_INST, tlv_len); + set_sec_offset(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + break; + case IWL_UCODE_TLV_WOWLAN_DATA: + set_sec_data(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_DATA, tlv_data); + set_sec_size(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_DATA, tlv_len); + set_sec_offset(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + break; + case IWL_UCODE_TLV_PHY_CALIBRATION_SIZE: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + capa->standard_phy_calibration_size = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_SEC_RT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_REGULAR, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_SEC_INIT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_INIT, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_SEC_WOWLAN: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_WOWLAN, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_DEF_CALIB: + if (tlv_len != sizeof(struct iwl_tlv_calib_data)) + goto invalid_tlv_len; + if (iwl_set_default_calib(drv, tlv_data)) + goto tlv_error; + break; + case IWL_UCODE_TLV_PHY_SKU: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + drv->fw.phy_config = le32_to_cpup((const __le32 *)tlv_data); + drv->fw.valid_tx_ant = (drv->fw.phy_config & + FW_PHY_CFG_TX_CHAIN) >> + FW_PHY_CFG_TX_CHAIN_POS; + drv->fw.valid_rx_ant = (drv->fw.phy_config & + FW_PHY_CFG_RX_CHAIN) >> + FW_PHY_CFG_RX_CHAIN_POS; + break; + case IWL_UCODE_TLV_SECURE_SEC_RT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_REGULAR, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_SECURE_SEC_INIT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_INIT, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_SECURE_SEC_WOWLAN: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_WOWLAN, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_NUM_OF_CPU: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + num_of_cpus = + le32_to_cpup((const __le32 *)tlv_data); + + if (num_of_cpus == 2) { + drv->fw.img[IWL_UCODE_REGULAR].is_dual_cpus = + true; + drv->fw.img[IWL_UCODE_INIT].is_dual_cpus = + true; + drv->fw.img[IWL_UCODE_WOWLAN].is_dual_cpus = + true; + } else if ((num_of_cpus > 2) || (num_of_cpus < 1)) { + IWL_ERR(drv, "Driver support upto 2 CPUs\n"); + return -EINVAL; + } + break; + case IWL_UCODE_TLV_CSCHEME: + if (iwl_store_cscheme(&drv->fw, tlv_data, tlv_len)) + goto invalid_tlv_len; + break; + case IWL_UCODE_TLV_N_SCAN_CHANNELS: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + capa->n_scan_channels = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_FW_VERSION: { + const __le32 *ptr = (const __le32 *)tlv_data; + u32 major, minor; + u8 local_comp; + + if (tlv_len != sizeof(u32) * 3) + goto invalid_tlv_len; + + major = le32_to_cpup(ptr++); + minor = le32_to_cpup(ptr++); + local_comp = le32_to_cpup(ptr); + + if (major >= 35) + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%08x.%u %s", major, minor, + local_comp, iwl_reduced_fw_name(drv)); + else + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%u.%u %s", major, minor, + local_comp, iwl_reduced_fw_name(drv)); + break; + } + case IWL_UCODE_TLV_FW_DBG_DEST: { + const struct iwl_fw_dbg_dest_tlv *dest = NULL; + const struct iwl_fw_dbg_dest_tlv_v1 *dest_v1 = NULL; + u8 mon_mode; + + pieces->dbg_dest_ver = (const u8 *)tlv_data; + if (*pieces->dbg_dest_ver == 1) { + dest = (const void *)tlv_data; + } else if (*pieces->dbg_dest_ver == 0) { + dest_v1 = (const void *)tlv_data; + } else { + IWL_ERR(drv, + "The version is %d, and it is invalid\n", + *pieces->dbg_dest_ver); + break; + } + + if (pieces->dbg_dest_tlv_init) { + IWL_ERR(drv, + "dbg destination ignored, already exists\n"); + break; + } + + pieces->dbg_dest_tlv_init = true; + + if (dest_v1) { + pieces->dbg_dest_tlv_v1 = dest_v1; + mon_mode = dest_v1->monitor_mode; + } else { + pieces->dbg_dest_tlv = dest; + mon_mode = dest->monitor_mode; + } + + IWL_INFO(drv, "Found debug destination: %s\n", + get_fw_dbg_mode_string(mon_mode)); + + drv->fw.dbg.n_dest_reg = (dest_v1) ? + tlv_len - + offsetof(struct iwl_fw_dbg_dest_tlv_v1, + reg_ops) : + tlv_len - + offsetof(struct iwl_fw_dbg_dest_tlv, + reg_ops); + + drv->fw.dbg.n_dest_reg /= + sizeof(drv->fw.dbg.dest_tlv->reg_ops[0]); + + break; + } + case IWL_UCODE_TLV_FW_DBG_CONF: { + const struct iwl_fw_dbg_conf_tlv *conf = + (const void *)tlv_data; + + if (!pieces->dbg_dest_tlv_init) { + IWL_ERR(drv, + "Ignore dbg config %d - no destination configured\n", + conf->id); + break; + } + + if (conf->id >= ARRAY_SIZE(drv->fw.dbg.conf_tlv)) { + IWL_ERR(drv, + "Skip unknown configuration: %d\n", + conf->id); + break; + } + + if (pieces->dbg_conf_tlv[conf->id]) { + IWL_ERR(drv, + "Ignore duplicate dbg config %d\n", + conf->id); + break; + } + + if (conf->usniffer) + usniffer_req = true; + + IWL_INFO(drv, "Found debug configuration: %d\n", + conf->id); + + pieces->dbg_conf_tlv[conf->id] = conf; + pieces->dbg_conf_tlv_len[conf->id] = tlv_len; + break; + } + case IWL_UCODE_TLV_FW_DBG_TRIGGER: { + const struct iwl_fw_dbg_trigger_tlv *trigger = + (const void *)tlv_data; + u32 trigger_id = le32_to_cpu(trigger->id); + + if (trigger_id >= ARRAY_SIZE(drv->fw.dbg.trigger_tlv)) { + IWL_ERR(drv, + "Skip unknown trigger: %u\n", + trigger->id); + break; + } + + if (pieces->dbg_trigger_tlv[trigger_id]) { + IWL_ERR(drv, + "Ignore duplicate dbg trigger %u\n", + trigger->id); + break; + } + + IWL_INFO(drv, "Found debug trigger: %u\n", trigger->id); + + pieces->dbg_trigger_tlv[trigger_id] = trigger; + pieces->dbg_trigger_tlv_len[trigger_id] = tlv_len; + break; + } + case IWL_UCODE_TLV_FW_DBG_DUMP_LST: { + if (tlv_len != sizeof(u32)) { + IWL_ERR(drv, + "dbg lst mask size incorrect, skip\n"); + break; + } + + drv->fw.dbg.dump_mask = + le32_to_cpup((const __le32 *)tlv_data); + break; + } + case IWL_UCODE_TLV_SEC_RT_USNIFFER: + *usniffer_images = true; + iwl_store_ucode_sec(pieces, tlv_data, + IWL_UCODE_REGULAR_USNIFFER, + tlv_len); + break; + case IWL_UCODE_TLV_PAGING: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + paging_mem_size = le32_to_cpup((const __le32 *)tlv_data); + + IWL_DEBUG_FW(drv, + "Paging: paging enabled (size = %u bytes)\n", + paging_mem_size); + + if (paging_mem_size > MAX_PAGING_IMAGE_SIZE) { + IWL_ERR(drv, + "Paging: driver supports up to %lu bytes for paging image\n", + MAX_PAGING_IMAGE_SIZE); + return -EINVAL; + } + + if (paging_mem_size & (FW_PAGING_SIZE - 1)) { + IWL_ERR(drv, + "Paging: image isn't multiple %lu\n", + FW_PAGING_SIZE); + return -EINVAL; + } + + drv->fw.img[IWL_UCODE_REGULAR].paging_mem_size = + paging_mem_size; + usniffer_img = IWL_UCODE_REGULAR_USNIFFER; + drv->fw.img[usniffer_img].paging_mem_size = + paging_mem_size; + break; + case IWL_UCODE_TLV_FW_GSCAN_CAPA: + /* ignored */ + break; + case IWL_UCODE_TLV_FW_MEM_SEG: { + const struct iwl_fw_dbg_mem_seg_tlv *dbg_mem = + (const void *)tlv_data; + size_t size; + struct iwl_fw_dbg_mem_seg_tlv *n; + + if (tlv_len != (sizeof(*dbg_mem))) + goto invalid_tlv_len; + + IWL_DEBUG_INFO(drv, "Found debug memory segment: %u\n", + dbg_mem->data_type); + + size = sizeof(*pieces->dbg_mem_tlv) * + (pieces->n_mem_tlv + 1); + n = krealloc(pieces->dbg_mem_tlv, size, GFP_KERNEL); + if (!n) + return -ENOMEM; + pieces->dbg_mem_tlv = n; + pieces->dbg_mem_tlv[pieces->n_mem_tlv] = *dbg_mem; + pieces->n_mem_tlv++; + break; + } + case IWL_UCODE_TLV_IML: { + drv->fw.iml_len = tlv_len; + drv->fw.iml = kmemdup(tlv_data, tlv_len, GFP_KERNEL); + if (!drv->fw.iml) + return -ENOMEM; + break; + } + case IWL_UCODE_TLV_FW_RECOVERY_INFO: { + const struct { + __le32 buf_addr; + __le32 buf_size; + } *recov_info = (const void *)tlv_data; + + if (tlv_len != sizeof(*recov_info)) + goto invalid_tlv_len; + capa->error_log_addr = + le32_to_cpu(recov_info->buf_addr); + capa->error_log_size = + le32_to_cpu(recov_info->buf_size); + } + break; + case IWL_UCODE_TLV_FW_FSEQ_VERSION: { + const struct { + u8 version[32]; + u8 sha1[20]; + } *fseq_ver = (const void *)tlv_data; + + if (tlv_len != sizeof(*fseq_ver)) + goto invalid_tlv_len; + IWL_INFO(drv, "TLV_FW_FSEQ_VERSION: %s\n", + fseq_ver->version); + } + break; + case IWL_UCODE_TLV_FW_NUM_STATIONS: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + if (le32_to_cpup((const __le32 *)tlv_data) > + IWL_MVM_STATION_COUNT_MAX) { + IWL_ERR(drv, + "%d is an invalid number of station\n", + le32_to_cpup((const __le32 *)tlv_data)); + goto tlv_error; + } + capa->num_stations = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_UMAC_DEBUG_ADDRS: { + const struct iwl_umac_debug_addrs *dbg_ptrs = + (const void *)tlv_data; + + if (tlv_len != sizeof(*dbg_ptrs)) + goto invalid_tlv_len; + if (drv->trans->trans_cfg->device_family < + IWL_DEVICE_FAMILY_22000) + break; + drv->trans->dbg.umac_error_event_table = + le32_to_cpu(dbg_ptrs->error_info_addr) & + ~FW_ADDR_CACHE_CONTROL; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_UMAC; + break; + } + case IWL_UCODE_TLV_LMAC_DEBUG_ADDRS: { + const struct iwl_lmac_debug_addrs *dbg_ptrs = + (const void *)tlv_data; + + if (tlv_len != sizeof(*dbg_ptrs)) + goto invalid_tlv_len; + if (drv->trans->trans_cfg->device_family < + IWL_DEVICE_FAMILY_22000) + break; + drv->trans->dbg.lmac_error_event_table[0] = + le32_to_cpu(dbg_ptrs->error_event_table_ptr) & + ~FW_ADDR_CACHE_CONTROL; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_LMAC1; + break; + } + case IWL_UCODE_TLV_TCM_DEBUG_ADDRS: { + const struct iwl_fw_tcm_error_addr *ptr = + (const void *)tlv_data; + + if (tlv_len != sizeof(*ptr)) + goto invalid_tlv_len; + drv->trans->dbg.tcm_error_event_table = + le32_to_cpu(ptr->addr) & ~FW_ADDR_CACHE_CONTROL; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_TCM; + break; + } + case IWL_UCODE_TLV_TYPE_DEBUG_INFO: + case IWL_UCODE_TLV_TYPE_BUFFER_ALLOCATION: + case IWL_UCODE_TLV_TYPE_HCMD: + case IWL_UCODE_TLV_TYPE_REGIONS: + case IWL_UCODE_TLV_TYPE_TRIGGERS: + case IWL_UCODE_TLV_TYPE_CONF_SET: + if (iwlwifi_mod_params.enable_ini) + iwl_dbg_tlv_alloc(drv->trans, tlv, false); + break; + case IWL_UCODE_TLV_CMD_VERSIONS: + if (tlv_len % sizeof(struct iwl_fw_cmd_version)) { + IWL_ERR(drv, + "Invalid length for command versions: %u\n", + tlv_len); + tlv_len /= sizeof(struct iwl_fw_cmd_version); + tlv_len *= sizeof(struct iwl_fw_cmd_version); + } + if (WARN_ON(capa->cmd_versions)) + return -EINVAL; + capa->cmd_versions = kmemdup(tlv_data, tlv_len, + GFP_KERNEL); + if (!capa->cmd_versions) + return -ENOMEM; + capa->n_cmd_versions = + tlv_len / sizeof(struct iwl_fw_cmd_version); + break; + case IWL_UCODE_TLV_PHY_INTEGRATION_VERSION: + if (drv->fw.phy_integration_ver) { + IWL_ERR(drv, + "phy integration str ignored, already exists\n"); + break; + } + + drv->fw.phy_integration_ver = + kmemdup(tlv_data, tlv_len, GFP_KERNEL); + if (!drv->fw.phy_integration_ver) + return -ENOMEM; + drv->fw.phy_integration_ver_len = tlv_len; + break; + case IWL_UCODE_TLV_SEC_TABLE_ADDR: + case IWL_UCODE_TLV_D3_KEK_KCK_ADDR: + iwl_drv_set_dump_exclude(drv, tlv_type, + tlv_data, tlv_len); + break; + default: + IWL_DEBUG_INFO(drv, "unknown TLV: %d\n", tlv_type); + break; + } + } + + if (!fw_has_capa(capa, IWL_UCODE_TLV_CAPA_USNIFFER_UNIFIED) && + usniffer_req && !*usniffer_images) { + IWL_ERR(drv, + "user selected to work with usniffer but usniffer image isn't available in ucode package\n"); + return -EINVAL; + } + + if (len) { + IWL_ERR(drv, "invalid TLV after parsing: %zd\n", len); +#ifdef CONFIG_IWLWIFI_DEBUGFS + iwl_print_hex_dump(drv, IWL_DL_FW, "TLV ", (u8 *)data, len); +#endif + return -EINVAL; + } + + return 0; + + invalid_tlv_len: + IWL_ERR(drv, "TLV %d has invalid size: %u\n", tlv_type, tlv_len); + tlv_error: +#ifdef CONFIG_IWLWIFI_DEBUGFS + iwl_print_hex_dump(drv, IWL_DL_FW, "TLV ", tlv_data, tlv_len); +#endif + + return -EINVAL; +} + +static int iwl_alloc_ucode(struct iwl_drv *drv, + struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type) +{ + int i; + struct fw_desc *sec; + + sec = kcalloc(pieces->img[type].sec_counter, sizeof(*sec), GFP_KERNEL); + if (!sec) + return -ENOMEM; + drv->fw.img[type].sec = sec; + drv->fw.img[type].num_sec = pieces->img[type].sec_counter; + + for (i = 0; i < pieces->img[type].sec_counter; i++) + if (iwl_alloc_fw_desc(drv, &sec[i], get_sec(pieces, type, i))) + return -ENOMEM; + + return 0; +} + +static int validate_sec_sizes(struct iwl_drv *drv, + struct iwl_firmware_pieces *pieces, + const struct iwl_cfg *cfg) +{ + IWL_DEBUG_INFO(drv, "f/w package hdr runtime inst size = %zd\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST)); + IWL_DEBUG_INFO(drv, "f/w package hdr runtime data size = %zd\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA)); + IWL_DEBUG_INFO(drv, "f/w package hdr init inst size = %zd\n", + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST)); + IWL_DEBUG_INFO(drv, "f/w package hdr init data size = %zd\n", + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA)); + + /* Verify that uCode images will fit in card's SRAM. */ + if (get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST) > + cfg->max_inst_size) { + IWL_ERR(drv, "uCode instr len %zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST)); + return -1; + } + + if (get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA) > + cfg->max_data_size) { + IWL_ERR(drv, "uCode data len %zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA)); + return -1; + } + + if (get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST) > + cfg->max_inst_size) { + IWL_ERR(drv, "uCode init instr len %zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST)); + return -1; + } + + if (get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA) > + cfg->max_data_size) { + IWL_ERR(drv, "uCode init data len %zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA)); + return -1; + } + return 0; +} + +static struct iwl_op_mode * +_iwl_op_mode_start(struct iwl_drv *drv, struct iwlwifi_opmode_table *op) +{ + const struct iwl_op_mode_ops *ops = op->ops; + struct dentry *dbgfs_dir = NULL; + struct iwl_op_mode *op_mode = NULL; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + drv->dbgfs_op_mode = debugfs_create_dir(op->name, + drv->dbgfs_drv); + dbgfs_dir = drv->dbgfs_op_mode; +#endif + + op_mode = ops->start(drv->trans, drv->trans->cfg, &drv->fw, dbgfs_dir); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (!op_mode) { + debugfs_remove_recursive(drv->dbgfs_op_mode); + drv->dbgfs_op_mode = NULL; + } +#endif + + return op_mode; +} + +static void _iwl_op_mode_stop(struct iwl_drv *drv) +{ + /* op_mode can be NULL if its start failed */ + if (drv->op_mode) { + iwl_op_mode_stop(drv->op_mode); + drv->op_mode = NULL; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(drv->dbgfs_op_mode); + drv->dbgfs_op_mode = NULL; +#endif + } +} + +/* + * iwl_req_fw_callback - callback when firmware was loaded + * + * If loaded successfully, copies the firmware into buffers + * for the card to fetch (via DMA). + */ +static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context) +{ + struct iwl_drv *drv = context; + struct iwl_fw *fw = &drv->fw; + const struct iwl_ucode_header *ucode; + struct iwlwifi_opmode_table *op; + int err; + struct iwl_firmware_pieces *pieces; + const unsigned int api_max = drv->trans->cfg->ucode_api_max; + const unsigned int api_min = drv->trans->cfg->ucode_api_min; + size_t trigger_tlv_sz[FW_DBG_TRIGGER_MAX]; + u32 api_ver; + int i; + bool load_module = false; + bool usniffer_images = false; + + fw->ucode_capa.max_probe_length = IWL_DEFAULT_MAX_PROBE_LENGTH; + fw->ucode_capa.standard_phy_calibration_size = + IWL_DEFAULT_STANDARD_PHY_CALIBRATE_TBL_SIZE; + fw->ucode_capa.n_scan_channels = IWL_DEFAULT_SCAN_CHANNELS; + fw->ucode_capa.num_stations = IWL_MVM_STATION_COUNT_MAX; + /* dump all fw memory areas by default */ + fw->dbg.dump_mask = 0xffffffff; + + pieces = kzalloc(sizeof(*pieces), GFP_KERNEL); + if (!pieces) + goto out_free_fw; + + if (!ucode_raw) + goto try_again; + + IWL_DEBUG_FW_INFO(drv, "Loaded firmware file '%s' (%zd bytes).\n", + drv->firmware_name, ucode_raw->size); + + /* Make sure that we got at least the API version number */ + if (ucode_raw->size < 4) { + IWL_ERR(drv, "File size way too small!\n"); + goto try_again; + } + + /* Data from ucode file: header followed by uCode images */ + ucode = (const struct iwl_ucode_header *)ucode_raw->data; + + if (ucode->ver) + err = iwl_parse_v1_v2_firmware(drv, ucode_raw, pieces); + else + err = iwl_parse_tlv_firmware(drv, ucode_raw, pieces, + &fw->ucode_capa, &usniffer_images); + + if (err) + goto try_again; + + if (fw_has_api(&drv->fw.ucode_capa, IWL_UCODE_TLV_API_NEW_VERSION)) + api_ver = drv->fw.ucode_ver; + else + api_ver = IWL_UCODE_API(drv->fw.ucode_ver); + + /* + * api_ver should match the api version forming part of the + * firmware filename ... but we don't check for that and only rely + * on the API version read from firmware header from here on forward + */ + if (api_ver < api_min || api_ver > api_max) { + IWL_ERR(drv, + "Driver unable to support your firmware API. " + "Driver supports v%u, firmware is v%u.\n", + api_max, api_ver); + goto try_again; + } + + /* + * In mvm uCode there is no difference between data and instructions + * sections. + */ + if (fw->type == IWL_FW_DVM && validate_sec_sizes(drv, pieces, + drv->trans->cfg)) + goto try_again; + + /* Allocate ucode buffers for card's bus-master loading ... */ + + /* Runtime instructions and 2 copies of data: + * 1) unmodified from disk + * 2) backup cache for save/restore during power-downs + */ + for (i = 0; i < IWL_UCODE_TYPE_MAX; i++) + if (iwl_alloc_ucode(drv, pieces, i)) + goto out_free_fw; + + if (pieces->dbg_dest_tlv_init) { + size_t dbg_dest_size = sizeof(*drv->fw.dbg.dest_tlv) + + sizeof(drv->fw.dbg.dest_tlv->reg_ops[0]) * + drv->fw.dbg.n_dest_reg; + + drv->fw.dbg.dest_tlv = kmalloc(dbg_dest_size, GFP_KERNEL); + + if (!drv->fw.dbg.dest_tlv) + goto out_free_fw; + + if (*pieces->dbg_dest_ver == 0) { + memcpy(drv->fw.dbg.dest_tlv, pieces->dbg_dest_tlv_v1, + dbg_dest_size); + } else { + struct iwl_fw_dbg_dest_tlv_v1 *dest_tlv = + drv->fw.dbg.dest_tlv; + + dest_tlv->version = pieces->dbg_dest_tlv->version; + dest_tlv->monitor_mode = + pieces->dbg_dest_tlv->monitor_mode; + dest_tlv->size_power = + pieces->dbg_dest_tlv->size_power; + dest_tlv->wrap_count = + pieces->dbg_dest_tlv->wrap_count; + dest_tlv->write_ptr_reg = + pieces->dbg_dest_tlv->write_ptr_reg; + dest_tlv->base_shift = + pieces->dbg_dest_tlv->base_shift; + memcpy(dest_tlv->reg_ops, + pieces->dbg_dest_tlv->reg_ops, + sizeof(drv->fw.dbg.dest_tlv->reg_ops[0]) * + drv->fw.dbg.n_dest_reg); + + /* In version 1 of the destination tlv, which is + * relevant for internal buffer exclusively, + * the base address is part of given with the length + * of the buffer, and the size shift is give instead of + * end shift. We now store these values in base_reg, + * and end shift, and when dumping the data we'll + * manipulate it for extracting both the length and + * base address */ + dest_tlv->base_reg = pieces->dbg_dest_tlv->cfg_reg; + dest_tlv->end_shift = + pieces->dbg_dest_tlv->size_shift; + } + } + + for (i = 0; i < ARRAY_SIZE(drv->fw.dbg.conf_tlv); i++) { + if (pieces->dbg_conf_tlv[i]) { + drv->fw.dbg.conf_tlv[i] = + kmemdup(pieces->dbg_conf_tlv[i], + pieces->dbg_conf_tlv_len[i], + GFP_KERNEL); + if (!drv->fw.dbg.conf_tlv[i]) + goto out_free_fw; + } + } + + memset(&trigger_tlv_sz, 0xff, sizeof(trigger_tlv_sz)); + + trigger_tlv_sz[FW_DBG_TRIGGER_MISSED_BEACONS] = + sizeof(struct iwl_fw_dbg_trigger_missed_bcon); + trigger_tlv_sz[FW_DBG_TRIGGER_CHANNEL_SWITCH] = 0; + trigger_tlv_sz[FW_DBG_TRIGGER_FW_NOTIF] = + sizeof(struct iwl_fw_dbg_trigger_cmd); + trigger_tlv_sz[FW_DBG_TRIGGER_MLME] = + sizeof(struct iwl_fw_dbg_trigger_mlme); + trigger_tlv_sz[FW_DBG_TRIGGER_STATS] = + sizeof(struct iwl_fw_dbg_trigger_stats); + trigger_tlv_sz[FW_DBG_TRIGGER_RSSI] = + sizeof(struct iwl_fw_dbg_trigger_low_rssi); + trigger_tlv_sz[FW_DBG_TRIGGER_TXQ_TIMERS] = + sizeof(struct iwl_fw_dbg_trigger_txq_timer); + trigger_tlv_sz[FW_DBG_TRIGGER_TIME_EVENT] = + sizeof(struct iwl_fw_dbg_trigger_time_event); + trigger_tlv_sz[FW_DBG_TRIGGER_BA] = + sizeof(struct iwl_fw_dbg_trigger_ba); + trigger_tlv_sz[FW_DBG_TRIGGER_TDLS] = + sizeof(struct iwl_fw_dbg_trigger_tdls); + + for (i = 0; i < ARRAY_SIZE(drv->fw.dbg.trigger_tlv); i++) { + if (pieces->dbg_trigger_tlv[i]) { + /* + * If the trigger isn't long enough, WARN and exit. + * Someone is trying to debug something and he won't + * be able to catch the bug he is trying to chase. + * We'd better be noisy to be sure he knows what's + * going on. + */ + if (WARN_ON(pieces->dbg_trigger_tlv_len[i] < + (trigger_tlv_sz[i] + + sizeof(struct iwl_fw_dbg_trigger_tlv)))) + goto out_free_fw; + drv->fw.dbg.trigger_tlv_len[i] = + pieces->dbg_trigger_tlv_len[i]; + drv->fw.dbg.trigger_tlv[i] = + kmemdup(pieces->dbg_trigger_tlv[i], + drv->fw.dbg.trigger_tlv_len[i], + GFP_KERNEL); + if (!drv->fw.dbg.trigger_tlv[i]) + goto out_free_fw; + } + } + + /* Now that we can no longer fail, copy information */ + + drv->fw.dbg.mem_tlv = pieces->dbg_mem_tlv; + pieces->dbg_mem_tlv = NULL; + drv->fw.dbg.n_mem_tlv = pieces->n_mem_tlv; + + /* + * The (size - 16) / 12 formula is based on the information recorded + * for each event, which is of mode 1 (including timestamp) for all + * new microcodes that include this information. + */ + fw->init_evtlog_ptr = pieces->init_evtlog_ptr; + if (pieces->init_evtlog_size) + fw->init_evtlog_size = (pieces->init_evtlog_size - 16)/12; + else + fw->init_evtlog_size = + drv->trans->trans_cfg->base_params->max_event_log_size; + fw->init_errlog_ptr = pieces->init_errlog_ptr; + fw->inst_evtlog_ptr = pieces->inst_evtlog_ptr; + if (pieces->inst_evtlog_size) + fw->inst_evtlog_size = (pieces->inst_evtlog_size - 16)/12; + else + fw->inst_evtlog_size = + drv->trans->trans_cfg->base_params->max_event_log_size; + fw->inst_errlog_ptr = pieces->inst_errlog_ptr; + + /* + * figure out the offset of chain noise reset and gain commands + * base on the size of standard phy calibration commands table size + */ + if (fw->ucode_capa.standard_phy_calibration_size > + IWL_MAX_PHY_CALIBRATE_TBL_SIZE) + fw->ucode_capa.standard_phy_calibration_size = + IWL_MAX_STANDARD_PHY_CALIBRATE_TBL_SIZE; + + /* We have our copies now, allow OS release its copies */ + release_firmware(ucode_raw); + + mutex_lock(&iwlwifi_opmode_table_mtx); + switch (fw->type) { + case IWL_FW_DVM: + op = &iwlwifi_opmode_table[DVM_OP_MODE]; + break; + default: + WARN(1, "Invalid fw type %d\n", fw->type); + fallthrough; + case IWL_FW_MVM: + op = &iwlwifi_opmode_table[MVM_OP_MODE]; + break; + } + + IWL_INFO(drv, "loaded firmware version %s op_mode %s\n", + drv->fw.fw_version, op->name); + + iwl_dbg_tlv_load_bin(drv->trans->dev, drv->trans); + + /* add this device to the list of devices using this op_mode */ + list_add_tail(&drv->list, &op->drv); + + if (op->ops) { + drv->op_mode = _iwl_op_mode_start(drv, op); + + if (!drv->op_mode) { + mutex_unlock(&iwlwifi_opmode_table_mtx); + goto out_unbind; + } + } else { + load_module = true; + } + mutex_unlock(&iwlwifi_opmode_table_mtx); + + /* + * Complete the firmware request last so that + * a driver unbind (stop) doesn't run while we + * are doing the start() above. + */ + complete(&drv->request_firmware_complete); + + /* + * Load the module last so we don't block anything + * else from proceeding if the module fails to load + * or hangs loading. + */ + if (load_module) { + request_module("%s", op->name); +#ifdef CONFIG_IWLWIFI_OPMODE_MODULAR + if (err) + IWL_ERR(drv, + "failed to load module %s (error %d), is dynamic loading enabled?\n", + op->name, err); +#endif + } + goto free; + + try_again: + /* try next, if any */ + release_firmware(ucode_raw); + if (iwl_request_firmware(drv, false)) + goto out_unbind; + goto free; + + out_free_fw: + release_firmware(ucode_raw); + out_unbind: + complete(&drv->request_firmware_complete); + device_release_driver(drv->trans->dev); + free: + if (pieces) { + for (i = 0; i < ARRAY_SIZE(pieces->img); i++) + kfree(pieces->img[i].sec); + kfree(pieces->dbg_mem_tlv); + kfree(pieces); + } +} + +struct iwl_drv *iwl_drv_start(struct iwl_trans *trans) +{ + struct iwl_drv *drv; + int ret; + + drv = kzalloc(sizeof(*drv), GFP_KERNEL); + if (!drv) { + ret = -ENOMEM; + goto err; + } + + drv->trans = trans; + drv->dev = trans->dev; + + init_completion(&drv->request_firmware_complete); + INIT_LIST_HEAD(&drv->list); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + /* Create the device debugfs entries. */ + drv->dbgfs_drv = debugfs_create_dir(dev_name(trans->dev), + iwl_dbgfs_root); + + /* Create transport layer debugfs dir */ + drv->trans->dbgfs_dir = debugfs_create_dir("trans", drv->dbgfs_drv); +#endif + + drv->trans->dbg.domains_bitmap = IWL_TRANS_FW_DBG_DOMAIN(drv->trans); + + ret = iwl_request_firmware(drv, true); + if (ret) { + IWL_ERR(trans, "Couldn't request the fw\n"); + goto err_fw; + } + + return drv; + +err_fw: +#ifdef CONFIG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(drv->dbgfs_drv); + iwl_dbg_tlv_free(drv->trans); +#endif + kfree(drv); +err: + return ERR_PTR(ret); +} + +void iwl_drv_stop(struct iwl_drv *drv) +{ + wait_for_completion(&drv->request_firmware_complete); + + _iwl_op_mode_stop(drv); + + iwl_dealloc_ucode(drv); + + mutex_lock(&iwlwifi_opmode_table_mtx); + /* + * List is empty (this item wasn't added) + * when firmware loading failed -- in that + * case we can't remove it from any list. + */ + if (!list_empty(&drv->list)) + list_del(&drv->list); + mutex_unlock(&iwlwifi_opmode_table_mtx); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + drv->trans->ops->debugfs_cleanup(drv->trans); + + debugfs_remove_recursive(drv->dbgfs_drv); +#endif + + iwl_dbg_tlv_free(drv->trans); + + kfree(drv); +} + + +/* shared module parameters */ +struct iwl_mod_params iwlwifi_mod_params = { + .fw_restart = true, + .bt_coex_active = true, + .power_level = IWL_POWER_INDEX_1, + .uapsd_disable = IWL_DISABLE_UAPSD_BSS | IWL_DISABLE_UAPSD_P2P_CLIENT, + .enable_ini = true, +#if defined(__FreeBSD__) + .disable_11n = 1, + .disable_11ac = true, + .disable_11ax = true, +#endif + /* the rest are 0 by default */ +}; +IWL_EXPORT_SYMBOL(iwlwifi_mod_params); + +int iwl_opmode_register(const char *name, const struct iwl_op_mode_ops *ops) +{ + int i; + struct iwl_drv *drv; + struct iwlwifi_opmode_table *op; + + mutex_lock(&iwlwifi_opmode_table_mtx); + for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) { + op = &iwlwifi_opmode_table[i]; + if (strcmp(op->name, name)) + continue; + op->ops = ops; + /* TODO: need to handle exceptional case */ + list_for_each_entry(drv, &op->drv, list) + drv->op_mode = _iwl_op_mode_start(drv, op); + + mutex_unlock(&iwlwifi_opmode_table_mtx); + return 0; + } + mutex_unlock(&iwlwifi_opmode_table_mtx); + return -EIO; +} +IWL_EXPORT_SYMBOL(iwl_opmode_register); + +void iwl_opmode_deregister(const char *name) +{ + int i; + struct iwl_drv *drv; + + mutex_lock(&iwlwifi_opmode_table_mtx); + for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) { + if (strcmp(iwlwifi_opmode_table[i].name, name)) + continue; + iwlwifi_opmode_table[i].ops = NULL; + + /* call the stop routine for all devices */ + list_for_each_entry(drv, &iwlwifi_opmode_table[i].drv, list) + _iwl_op_mode_stop(drv); + + mutex_unlock(&iwlwifi_opmode_table_mtx); + return; + } + mutex_unlock(&iwlwifi_opmode_table_mtx); +} +IWL_EXPORT_SYMBOL(iwl_opmode_deregister); + +static int __init iwl_drv_init(void) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) + INIT_LIST_HEAD(&iwlwifi_opmode_table[i].drv); + + pr_info(DRV_DESCRIPTION "\n"); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + /* Create the root of iwlwifi debugfs subsystem. */ + iwl_dbgfs_root = debugfs_create_dir(DRV_NAME, NULL); +#endif + + err = iwl_pci_register_driver(); + if (err) + goto cleanup_debugfs; + + return 0; + +cleanup_debugfs: +#ifdef CONFIG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(iwl_dbgfs_root); +#endif + return err; +} +module_init(iwl_drv_init); + +static void __exit iwl_drv_exit(void) +{ + iwl_pci_unregister_driver(); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(iwl_dbgfs_root); +#endif +} +module_exit(iwl_drv_exit); + +#ifdef CONFIG_IWLWIFI_DEBUG +module_param_named(debug, iwlwifi_mod_params.debug_level, uint, 0644); +MODULE_PARM_DESC(debug, "debug output mask"); +#endif + +module_param_named(swcrypto, iwlwifi_mod_params.swcrypto, int, 0444); +MODULE_PARM_DESC(swcrypto, "using crypto in software (default 0 [hardware])"); +module_param_named(11n_disable, iwlwifi_mod_params.disable_11n, uint, 0444); +MODULE_PARM_DESC(11n_disable, + "disable 11n functionality, bitmap: 1: full, 2: disable agg TX, 4: disable agg RX, 8 enable agg TX"); +module_param_named(amsdu_size, iwlwifi_mod_params.amsdu_size, int, 0444); +MODULE_PARM_DESC(amsdu_size, + "amsdu size 0: 12K for multi Rx queue devices, 2K for AX210 devices, " + "4K for other devices 1:4K 2:8K 3:12K (16K buffers) 4: 2K (default 0)"); +module_param_named(fw_restart, iwlwifi_mod_params.fw_restart, bool, 0444); +MODULE_PARM_DESC(fw_restart, "restart firmware in case of error (default true)"); + +module_param_named(nvm_file, iwlwifi_mod_params.nvm_file, charp, 0444); +MODULE_PARM_DESC(nvm_file, "NVM file name"); + +module_param_named(uapsd_disable, iwlwifi_mod_params.uapsd_disable, uint, 0644); +MODULE_PARM_DESC(uapsd_disable, + "disable U-APSD functionality bitmap 1: BSS 2: P2P Client (default: 3)"); +module_param_named(enable_ini, iwlwifi_mod_params.enable_ini, + bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_ini, + "Enable debug INI TLV FW debug infrastructure (default: true"); + +/* + * set bt_coex_active to true, uCode will do kill/defer + * every time the priority line is asserted (BT is sending signals on the + * priority line in the PCIx). + * set bt_coex_active to false, uCode will ignore the BT activity and + * perform the normal operation + * + * User might experience transmit issue on some platform due to WiFi/BT + * co-exist problem. The possible behaviors are: + * Able to scan and finding all the available AP + * Not able to associate with any AP + * On those platforms, WiFi communication can be restored by set + * "bt_coex_active" module parameter to "false" + * + * default: bt_coex_active = true (BT_COEX_ENABLE) + */ +module_param_named(bt_coex_active, iwlwifi_mod_params.bt_coex_active, + bool, 0444); +MODULE_PARM_DESC(bt_coex_active, "enable wifi/bt co-exist (default: enable)"); + +module_param_named(led_mode, iwlwifi_mod_params.led_mode, int, 0444); +MODULE_PARM_DESC(led_mode, "0=system default, " + "1=On(RF On)/Off(RF Off), 2=blinking, 3=Off (default: 0)"); + +module_param_named(power_save, iwlwifi_mod_params.power_save, bool, 0444); +MODULE_PARM_DESC(power_save, + "enable WiFi power management (default: disable)"); + +module_param_named(power_level, iwlwifi_mod_params.power_level, int, 0444); +MODULE_PARM_DESC(power_level, + "default power save level (range from 1 - 5, default: 1)"); + +module_param_named(disable_11ac, iwlwifi_mod_params.disable_11ac, bool, 0444); +MODULE_PARM_DESC(disable_11ac, "Disable VHT capabilities (default: false)"); + +module_param_named(remove_when_gone, + iwlwifi_mod_params.remove_when_gone, bool, + 0444); +MODULE_PARM_DESC(remove_when_gone, + "Remove dev from PCIe bus if it is deemed inaccessible (default: false)"); + +module_param_named(disable_11ax, iwlwifi_mod_params.disable_11ax, bool, + S_IRUGO); +MODULE_PARM_DESC(disable_11ax, "Disable HE capabilities (default: false)"); diff --git a/sys/contrib/dev/iwlwifi/iwl-drv.h b/sys/contrib/dev/iwlwifi/iwl-drv.h new file mode 100644 index 00000000000..2e2d60a5869 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-drv.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2020-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + */ +#ifndef __iwl_drv_h__ +#define __iwl_drv_h__ +#include + +/* for all modules */ +#define DRV_NAME "iwlwifi" + +/* radio config bits (actual values from NVM definition) */ +#define NVM_RF_CFG_DASH_MSK(x) (x & 0x3) /* bits 0-1 */ +#define NVM_RF_CFG_STEP_MSK(x) ((x >> 2) & 0x3) /* bits 2-3 */ +#define NVM_RF_CFG_TYPE_MSK(x) ((x >> 4) & 0x3) /* bits 4-5 */ +#define NVM_RF_CFG_PNUM_MSK(x) ((x >> 6) & 0x3) /* bits 6-7 */ +#define NVM_RF_CFG_TX_ANT_MSK(x) ((x >> 8) & 0xF) /* bits 8-11 */ +#define NVM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */ + +#define EXT_NVM_RF_CFG_FLAVOR_MSK(x) ((x) & 0xF) +#define EXT_NVM_RF_CFG_DASH_MSK(x) (((x) >> 4) & 0xF) +#define EXT_NVM_RF_CFG_STEP_MSK(x) (((x) >> 8) & 0xF) +#define EXT_NVM_RF_CFG_TYPE_MSK(x) (((x) >> 12) & 0xFFF) +#define EXT_NVM_RF_CFG_TX_ANT_MSK(x) (((x) >> 24) & 0xF) +#define EXT_NVM_RF_CFG_RX_ANT_MSK(x) (((x) >> 28) & 0xF) + +/** + * DOC: Driver system flows - drv component + * + * This component implements the system flows such as bus enumeration, bus + * removal. Bus dependent parts of system flows (such as iwl_pci_probe) are in + * bus specific files (transport files). This is the code that is common among + * different buses. + * + * This component is also in charge of managing the several implementations of + * the wifi flows: it will allow to have several fw API implementation. These + * different implementations will differ in the way they implement mac80211's + * handlers too. + + * The init flow wrt to the drv component looks like this: + * 1) The bus specific component is called from module_init + * 2) The bus specific component registers the bus driver + * 3) The bus driver calls the probe function + * 4) The bus specific component configures the bus + * 5) The bus specific component calls to the drv bus agnostic part + * (iwl_drv_start) + * 6) iwl_drv_start fetches the fw ASYNC, iwl_req_fw_callback + * 7) iwl_req_fw_callback parses the fw file + * 8) iwl_req_fw_callback starts the wifi implementation to matches the fw + */ + +struct iwl_drv; +struct iwl_trans; +struct iwl_cfg; +/** + * iwl_drv_start - start the drv + * + * @trans_ops: the ops of the transport + * + * starts the driver: fetches the firmware. This should be called by bus + * specific system flows implementations. For example, the bus specific probe + * function should do bus related operations only, and then call to this + * function. It returns the driver object or %NULL if an error occurred. + */ +struct iwl_drv *iwl_drv_start(struct iwl_trans *trans); + +/** + * iwl_drv_stop - stop the drv + * + * @drv: + * + * Stop the driver. This should be called by bus specific system flows + * implementations. For example, the bus specific remove function should first + * call this function and then do the bus related operations only. + */ +void iwl_drv_stop(struct iwl_drv *drv); + +/* + * exported symbol management + * + * The driver can be split into multiple modules, in which case some symbols + * must be exported for the sub-modules. However, if it's not split and + * everything is built-in, then we can avoid that. + */ +#ifdef CONFIG_IWLWIFI_OPMODE_MODULAR +#define IWL_EXPORT_SYMBOL(sym) EXPORT_SYMBOL_GPL(sym) +#else +#define IWL_EXPORT_SYMBOL(sym) +#endif + +#endif /* __iwl_drv_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-eeprom-parse.c b/sys/contrib/dev/iwlwifi/iwl-eeprom-parse.c new file mode 100644 index 00000000000..d7a7835b935 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-eeprom-parse.c @@ -0,0 +1,879 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2020 Intel Corporation + * Copyright (C) 2015 Intel Mobile Communications GmbH + */ +#include +#include +#include +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "iwl-eeprom-parse.h" + +#if IS_ENABLED(CONFIG_IWLDVM) +/* EEPROM offset definitions */ + +/* indirect access definitions */ +#define ADDRESS_MSK 0x0000FFFF +#define INDIRECT_TYPE_MSK 0x000F0000 +#define INDIRECT_HOST 0x00010000 +#define INDIRECT_GENERAL 0x00020000 +#define INDIRECT_REGULATORY 0x00030000 +#define INDIRECT_CALIBRATION 0x00040000 +#define INDIRECT_PROCESS_ADJST 0x00050000 +#define INDIRECT_OTHERS 0x00060000 +#define INDIRECT_TXP_LIMIT 0x00070000 +#define INDIRECT_TXP_LIMIT_SIZE 0x00080000 +#define INDIRECT_ADDRESS 0x00100000 + +/* corresponding link offsets in EEPROM */ +#define EEPROM_LINK_HOST (2*0x64) +#define EEPROM_LINK_GENERAL (2*0x65) +#define EEPROM_LINK_REGULATORY (2*0x66) +#define EEPROM_LINK_CALIBRATION (2*0x67) +#define EEPROM_LINK_PROCESS_ADJST (2*0x68) +#define EEPROM_LINK_OTHERS (2*0x69) +#define EEPROM_LINK_TXP_LIMIT (2*0x6a) +#define EEPROM_LINK_TXP_LIMIT_SIZE (2*0x6b) + +/* General */ +#define EEPROM_DEVICE_ID (2*0x08) /* 2 bytes */ +#define EEPROM_SUBSYSTEM_ID (2*0x0A) /* 2 bytes */ +#define EEPROM_MAC_ADDRESS (2*0x15) /* 6 bytes */ +#define EEPROM_BOARD_REVISION (2*0x35) /* 2 bytes */ +#define EEPROM_BOARD_PBA_NUMBER (2*0x3B+1) /* 9 bytes */ +#define EEPROM_VERSION (2*0x44) /* 2 bytes */ +#define EEPROM_SKU_CAP (2*0x45) /* 2 bytes */ +#define EEPROM_OEM_MODE (2*0x46) /* 2 bytes */ +#define EEPROM_RADIO_CONFIG (2*0x48) /* 2 bytes */ +#define EEPROM_NUM_MAC_ADDRESS (2*0x4C) /* 2 bytes */ + +/* calibration */ +struct iwl_eeprom_calib_hdr { + u8 version; + u8 pa_type; + __le16 voltage; +} __packed; + +#define EEPROM_CALIB_ALL (INDIRECT_ADDRESS | INDIRECT_CALIBRATION) +#define EEPROM_XTAL ((2*0x128) | EEPROM_CALIB_ALL) + +/* temperature */ +#define EEPROM_KELVIN_TEMPERATURE ((2*0x12A) | EEPROM_CALIB_ALL) +#define EEPROM_RAW_TEMPERATURE ((2*0x12B) | EEPROM_CALIB_ALL) + +/* SKU Capabilities (actual values from EEPROM definition) */ +enum eeprom_sku_bits { + EEPROM_SKU_CAP_BAND_24GHZ = BIT(4), + EEPROM_SKU_CAP_BAND_52GHZ = BIT(5), + EEPROM_SKU_CAP_11N_ENABLE = BIT(6), + EEPROM_SKU_CAP_AMT_ENABLE = BIT(7), + EEPROM_SKU_CAP_IPAN_ENABLE = BIT(8) +}; + +/* radio config bits (actual values from EEPROM definition) */ +#define EEPROM_RF_CFG_TYPE_MSK(x) (x & 0x3) /* bits 0-1 */ +#define EEPROM_RF_CFG_STEP_MSK(x) ((x >> 2) & 0x3) /* bits 2-3 */ +#define EEPROM_RF_CFG_DASH_MSK(x) ((x >> 4) & 0x3) /* bits 4-5 */ +#define EEPROM_RF_CFG_PNUM_MSK(x) ((x >> 6) & 0x3) /* bits 6-7 */ +#define EEPROM_RF_CFG_TX_ANT_MSK(x) ((x >> 8) & 0xF) /* bits 8-11 */ +#define EEPROM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */ + + +/* + * EEPROM bands + * These are the channel numbers from each band in the order + * that they are stored in the EEPROM band information. Note + * that EEPROM bands aren't the same as mac80211 bands, and + * there are even special "ht40 bands" in the EEPROM. + */ +static const u8 iwl_eeprom_band_1[14] = { /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 +}; + +static const u8 iwl_eeprom_band_2[] = { /* 4915-5080MHz */ + 183, 184, 185, 187, 188, 189, 192, 196, 7, 8, 11, 12, 16 +}; + +static const u8 iwl_eeprom_band_3[] = { /* 5170-5320MHz */ + 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64 +}; + +static const u8 iwl_eeprom_band_4[] = { /* 5500-5700MHz */ + 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 +}; + +static const u8 iwl_eeprom_band_5[] = { /* 5725-5825MHz */ + 145, 149, 153, 157, 161, 165 +}; + +static const u8 iwl_eeprom_band_6[] = { /* 2.4 ht40 channel */ + 1, 2, 3, 4, 5, 6, 7 +}; + +static const u8 iwl_eeprom_band_7[] = { /* 5.2 ht40 channel */ + 36, 44, 52, 60, 100, 108, 116, 124, 132, 149, 157 +}; + +#define IWL_NUM_CHANNELS (ARRAY_SIZE(iwl_eeprom_band_1) + \ + ARRAY_SIZE(iwl_eeprom_band_2) + \ + ARRAY_SIZE(iwl_eeprom_band_3) + \ + ARRAY_SIZE(iwl_eeprom_band_4) + \ + ARRAY_SIZE(iwl_eeprom_band_5)) + +/* rate data (static) */ +static struct ieee80211_rate iwl_cfg80211_rates[] = { + { .bitrate = 1 * 10, .hw_value = 0, .hw_value_short = 0, }, + { .bitrate = 2 * 10, .hw_value = 1, .hw_value_short = 1, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 5.5 * 10, .hw_value = 2, .hw_value_short = 2, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 11 * 10, .hw_value = 3, .hw_value_short = 3, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 6 * 10, .hw_value = 4, .hw_value_short = 4, }, + { .bitrate = 9 * 10, .hw_value = 5, .hw_value_short = 5, }, + { .bitrate = 12 * 10, .hw_value = 6, .hw_value_short = 6, }, + { .bitrate = 18 * 10, .hw_value = 7, .hw_value_short = 7, }, + { .bitrate = 24 * 10, .hw_value = 8, .hw_value_short = 8, }, + { .bitrate = 36 * 10, .hw_value = 9, .hw_value_short = 9, }, + { .bitrate = 48 * 10, .hw_value = 10, .hw_value_short = 10, }, + { .bitrate = 54 * 10, .hw_value = 11, .hw_value_short = 11, }, +}; +#define RATES_24_OFFS 0 +#define N_RATES_24 ARRAY_SIZE(iwl_cfg80211_rates) +#define RATES_52_OFFS 4 +#define N_RATES_52 (N_RATES_24 - RATES_52_OFFS) + +/* EEPROM reading functions */ + +static u16 iwl_eeprom_query16(const u8 *eeprom, size_t eeprom_size, int offset) +{ + if (WARN_ON(offset + sizeof(u16) > eeprom_size)) + return 0; + return le16_to_cpup((__le16 *)(eeprom + offset)); +} + +static u32 eeprom_indirect_address(const u8 *eeprom, size_t eeprom_size, + u32 address) +{ + u16 offset = 0; + + if ((address & INDIRECT_ADDRESS) == 0) + return address; + + switch (address & INDIRECT_TYPE_MSK) { + case INDIRECT_HOST: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_HOST); + break; + case INDIRECT_GENERAL: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_GENERAL); + break; + case INDIRECT_REGULATORY: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_REGULATORY); + break; + case INDIRECT_TXP_LIMIT: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_TXP_LIMIT); + break; + case INDIRECT_TXP_LIMIT_SIZE: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_TXP_LIMIT_SIZE); + break; + case INDIRECT_CALIBRATION: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_CALIBRATION); + break; + case INDIRECT_PROCESS_ADJST: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_PROCESS_ADJST); + break; + case INDIRECT_OTHERS: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_OTHERS); + break; + default: + WARN_ON(1); + break; + } + + /* translate the offset from words to byte */ + return (address & ADDRESS_MSK) + (offset << 1); +} + +static const u8 *iwl_eeprom_query_addr(const u8 *eeprom, size_t eeprom_size, + u32 offset) +{ + u32 address = eeprom_indirect_address(eeprom, eeprom_size, offset); + + if (WARN_ON(address >= eeprom_size)) + return NULL; + + return &eeprom[address]; +} + +static int iwl_eeprom_read_calib(const u8 *eeprom, size_t eeprom_size, + struct iwl_nvm_data *data) +{ + struct iwl_eeprom_calib_hdr *hdr; + + hdr = (void *)iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_CALIB_ALL); + if (!hdr) + return -ENODATA; + data->calib_version = hdr->version; + data->calib_voltage = hdr->voltage; + + return 0; +} + +/** + * enum iwl_eeprom_channel_flags - channel flags in EEPROM + * @EEPROM_CHANNEL_VALID: channel is usable for this SKU/geo + * @EEPROM_CHANNEL_IBSS: usable as an IBSS channel + * @EEPROM_CHANNEL_ACTIVE: active scanning allowed + * @EEPROM_CHANNEL_RADAR: radar detection required + * @EEPROM_CHANNEL_WIDE: 20 MHz channel okay (?) + * @EEPROM_CHANNEL_DFS: dynamic freq selection candidate + */ +enum iwl_eeprom_channel_flags { + EEPROM_CHANNEL_VALID = BIT(0), + EEPROM_CHANNEL_IBSS = BIT(1), + EEPROM_CHANNEL_ACTIVE = BIT(3), + EEPROM_CHANNEL_RADAR = BIT(4), + EEPROM_CHANNEL_WIDE = BIT(5), + EEPROM_CHANNEL_DFS = BIT(7), +}; + +/** + * struct iwl_eeprom_channel - EEPROM channel data + * @flags: %EEPROM_CHANNEL_* flags + * @max_power_avg: max power (in dBm) on this channel, at most 31 dBm + */ +struct iwl_eeprom_channel { + u8 flags; + s8 max_power_avg; +} __packed; + + +enum iwl_eeprom_enhanced_txpwr_flags { + IWL_EEPROM_ENH_TXP_FL_VALID = BIT(0), + IWL_EEPROM_ENH_TXP_FL_BAND_52G = BIT(1), + IWL_EEPROM_ENH_TXP_FL_OFDM = BIT(2), + IWL_EEPROM_ENH_TXP_FL_40MHZ = BIT(3), + IWL_EEPROM_ENH_TXP_FL_HT_AP = BIT(4), + IWL_EEPROM_ENH_TXP_FL_RES1 = BIT(5), + IWL_EEPROM_ENH_TXP_FL_RES2 = BIT(6), + IWL_EEPROM_ENH_TXP_FL_COMMON_TYPE = BIT(7), +}; + +/** + * struct iwl_eeprom_enhanced_txpwr + * @flags: entry flags + * @channel: channel number + * @chain_a_max: chain a max power in 1/2 dBm + * @chain_b_max: chain b max power in 1/2 dBm + * @chain_c_max: chain c max power in 1/2 dBm + * @delta_20_in_40: 20-in-40 deltas (hi/lo) + * @mimo2_max: mimo2 max power in 1/2 dBm + * @mimo3_max: mimo3 max power in 1/2 dBm + * + * This structure presents the enhanced regulatory tx power limit layout + * in an EEPROM image. + */ +struct iwl_eeprom_enhanced_txpwr { + u8 flags; + u8 channel; + s8 chain_a_max; + s8 chain_b_max; + s8 chain_c_max; + u8 delta_20_in_40; + s8 mimo2_max; + s8 mimo3_max; +} __packed; + +static s8 iwl_get_max_txpwr_half_dbm(const struct iwl_nvm_data *data, + struct iwl_eeprom_enhanced_txpwr *txp) +{ + s8 result = 0; /* (.5 dBm) */ + + /* Take the highest tx power from any valid chains */ + if (data->valid_tx_ant & ANT_A && txp->chain_a_max > result) + result = txp->chain_a_max; + + if (data->valid_tx_ant & ANT_B && txp->chain_b_max > result) + result = txp->chain_b_max; + + if (data->valid_tx_ant & ANT_C && txp->chain_c_max > result) + result = txp->chain_c_max; + + if ((data->valid_tx_ant == ANT_AB || + data->valid_tx_ant == ANT_BC || + data->valid_tx_ant == ANT_AC) && txp->mimo2_max > result) + result = txp->mimo2_max; + + if (data->valid_tx_ant == ANT_ABC && txp->mimo3_max > result) + result = txp->mimo3_max; + + return result; +} + +#define EEPROM_TXP_OFFS (0x00 | INDIRECT_ADDRESS | INDIRECT_TXP_LIMIT) +#define EEPROM_TXP_ENTRY_LEN sizeof(struct iwl_eeprom_enhanced_txpwr) +#define EEPROM_TXP_SZ_OFFS (0x00 | INDIRECT_ADDRESS | INDIRECT_TXP_LIMIT_SIZE) + +#define TXP_CHECK_AND_PRINT(x) \ + ((txp->flags & IWL_EEPROM_ENH_TXP_FL_##x) ? # x " " : "") + +static void +iwl_eeprom_enh_txp_read_element(struct iwl_nvm_data *data, + struct iwl_eeprom_enhanced_txpwr *txp, + int n_channels, s8 max_txpower_avg) +{ + int ch_idx; + enum nl80211_band band; + + band = txp->flags & IWL_EEPROM_ENH_TXP_FL_BAND_52G ? + NL80211_BAND_5GHZ : NL80211_BAND_2GHZ; + + for (ch_idx = 0; ch_idx < n_channels; ch_idx++) { + struct ieee80211_channel *chan = &data->channels[ch_idx]; + + /* update matching channel or from common data only */ + if (txp->channel != 0 && chan->hw_value != txp->channel) + continue; + + /* update matching band only */ + if (band != chan->band) + continue; + + if (chan->max_power < max_txpower_avg && + !(txp->flags & IWL_EEPROM_ENH_TXP_FL_40MHZ)) + chan->max_power = max_txpower_avg; + } +} + +static void iwl_eeprom_enhanced_txpower(struct device *dev, + struct iwl_nvm_data *data, + const u8 *eeprom, size_t eeprom_size, + int n_channels) +{ + struct iwl_eeprom_enhanced_txpwr *txp_array, *txp; + int idx, entries; + __le16 *txp_len; + s8 max_txp_avg_halfdbm; + + BUILD_BUG_ON(sizeof(struct iwl_eeprom_enhanced_txpwr) != 8); + + /* the length is in 16-bit words, but we want entries */ + txp_len = (__le16 *)iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_TXP_SZ_OFFS); + entries = le16_to_cpup(txp_len) * 2 / EEPROM_TXP_ENTRY_LEN; + + txp_array = (void *)iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_TXP_OFFS); + + for (idx = 0; idx < entries; idx++) { + txp = &txp_array[idx]; + /* skip invalid entries */ + if (!(txp->flags & IWL_EEPROM_ENH_TXP_FL_VALID)) + continue; + + IWL_DEBUG_EEPROM(dev, "%s %d:\t %s%s%s%s%s%s%s%s (0x%02x)\n", + (txp->channel && (txp->flags & + IWL_EEPROM_ENH_TXP_FL_COMMON_TYPE)) ? + "Common " : (txp->channel) ? + "Channel" : "Common", + (txp->channel), + TXP_CHECK_AND_PRINT(VALID), + TXP_CHECK_AND_PRINT(BAND_52G), + TXP_CHECK_AND_PRINT(OFDM), + TXP_CHECK_AND_PRINT(40MHZ), + TXP_CHECK_AND_PRINT(HT_AP), + TXP_CHECK_AND_PRINT(RES1), + TXP_CHECK_AND_PRINT(RES2), + TXP_CHECK_AND_PRINT(COMMON_TYPE), + txp->flags); + IWL_DEBUG_EEPROM(dev, + "\t\t chain_A: %d chain_B: %d chain_C: %d\n", + txp->chain_a_max, txp->chain_b_max, + txp->chain_c_max); + IWL_DEBUG_EEPROM(dev, + "\t\t MIMO2: %d MIMO3: %d High 20_on_40: 0x%02x Low 20_on_40: 0x%02x\n", + txp->mimo2_max, txp->mimo3_max, + ((txp->delta_20_in_40 & 0xf0) >> 4), + (txp->delta_20_in_40 & 0x0f)); + + max_txp_avg_halfdbm = iwl_get_max_txpwr_half_dbm(data, txp); + + iwl_eeprom_enh_txp_read_element(data, txp, n_channels, + DIV_ROUND_UP(max_txp_avg_halfdbm, 2)); + + if (max_txp_avg_halfdbm > data->max_tx_pwr_half_dbm) + data->max_tx_pwr_half_dbm = max_txp_avg_halfdbm; + } +} + +static void iwl_init_band_reference(const struct iwl_cfg *cfg, + const u8 *eeprom, size_t eeprom_size, + int eeprom_band, int *eeprom_ch_count, + const struct iwl_eeprom_channel **ch_info, + const u8 **eeprom_ch_array) +{ + u32 offset = cfg->eeprom_params->regulatory_bands[eeprom_band - 1]; + + offset |= INDIRECT_ADDRESS | INDIRECT_REGULATORY; + + *ch_info = (void *)iwl_eeprom_query_addr(eeprom, eeprom_size, offset); + + switch (eeprom_band) { + case 1: /* 2.4GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_1); + *eeprom_ch_array = iwl_eeprom_band_1; + break; + case 2: /* 4.9GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_2); + *eeprom_ch_array = iwl_eeprom_band_2; + break; + case 3: /* 5.2GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_3); + *eeprom_ch_array = iwl_eeprom_band_3; + break; + case 4: /* 5.5GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_4); + *eeprom_ch_array = iwl_eeprom_band_4; + break; + case 5: /* 5.7GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_5); + *eeprom_ch_array = iwl_eeprom_band_5; + break; + case 6: /* 2.4GHz ht40 channels */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_6); + *eeprom_ch_array = iwl_eeprom_band_6; + break; + case 7: /* 5 GHz ht40 channels */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_7); + *eeprom_ch_array = iwl_eeprom_band_7; + break; + default: + *eeprom_ch_count = 0; + *eeprom_ch_array = NULL; + WARN_ON(1); + } +} + +#define CHECK_AND_PRINT(x) \ + ((eeprom_ch->flags & EEPROM_CHANNEL_##x) ? # x " " : "") + +static void iwl_mod_ht40_chan_info(struct device *dev, + struct iwl_nvm_data *data, int n_channels, + enum nl80211_band band, u16 channel, + const struct iwl_eeprom_channel *eeprom_ch, + u8 clear_ht40_extension_channel) +{ + struct ieee80211_channel *chan = NULL; + int i; + + for (i = 0; i < n_channels; i++) { + if (data->channels[i].band != band) + continue; + if (data->channels[i].hw_value != channel) + continue; + chan = &data->channels[i]; + break; + } + + if (!chan) + return; + + IWL_DEBUG_EEPROM(dev, + "HT40 Ch. %d [%sGHz] %s%s%s%s%s(0x%02x %ddBm): Ad-Hoc %ssupported\n", + channel, + band == NL80211_BAND_5GHZ ? "5.2" : "2.4", + CHECK_AND_PRINT(IBSS), + CHECK_AND_PRINT(ACTIVE), + CHECK_AND_PRINT(RADAR), + CHECK_AND_PRINT(WIDE), + CHECK_AND_PRINT(DFS), + eeprom_ch->flags, + eeprom_ch->max_power_avg, + ((eeprom_ch->flags & EEPROM_CHANNEL_IBSS) && + !(eeprom_ch->flags & EEPROM_CHANNEL_RADAR)) ? "" + : "not "); + + if (eeprom_ch->flags & EEPROM_CHANNEL_VALID) + chan->flags &= ~clear_ht40_extension_channel; +} + +#define CHECK_AND_PRINT_I(x) \ + ((eeprom_ch_info[ch_idx].flags & EEPROM_CHANNEL_##x) ? # x " " : "") + +static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const u8 *eeprom, size_t eeprom_size) +{ + int band, ch_idx; + const struct iwl_eeprom_channel *eeprom_ch_info; + const u8 *eeprom_ch_array; + int eeprom_ch_count; + int n_channels = 0; + + /* + * Loop through the 5 EEPROM bands and add them to the parse list + */ + for (band = 1; band <= 5; band++) { + struct ieee80211_channel *channel; + + iwl_init_band_reference(cfg, eeprom, eeprom_size, band, + &eeprom_ch_count, &eeprom_ch_info, + &eeprom_ch_array); + + /* Loop through each band adding each of the channels */ + for (ch_idx = 0; ch_idx < eeprom_ch_count; ch_idx++) { + const struct iwl_eeprom_channel *eeprom_ch; + + eeprom_ch = &eeprom_ch_info[ch_idx]; + + if (!(eeprom_ch->flags & EEPROM_CHANNEL_VALID)) { + IWL_DEBUG_EEPROM(dev, + "Ch. %d Flags %x [%sGHz] - No traffic\n", + eeprom_ch_array[ch_idx], + eeprom_ch_info[ch_idx].flags, + (band != 1) ? "5.2" : "2.4"); + continue; + } + + channel = &data->channels[n_channels]; + n_channels++; + + channel->hw_value = eeprom_ch_array[ch_idx]; + channel->band = (band == 1) ? NL80211_BAND_2GHZ + : NL80211_BAND_5GHZ; + channel->center_freq = + ieee80211_channel_to_frequency( + channel->hw_value, channel->band); + + /* set no-HT40, will enable as appropriate later */ + channel->flags = IEEE80211_CHAN_NO_HT40; + + if (!(eeprom_ch->flags & EEPROM_CHANNEL_IBSS)) + channel->flags |= IEEE80211_CHAN_NO_IR; + + if (!(eeprom_ch->flags & EEPROM_CHANNEL_ACTIVE)) + channel->flags |= IEEE80211_CHAN_NO_IR; + + if (eeprom_ch->flags & EEPROM_CHANNEL_RADAR) + channel->flags |= IEEE80211_CHAN_RADAR; + + /* Initialize regulatory-based run-time data */ + channel->max_power = + eeprom_ch_info[ch_idx].max_power_avg; + IWL_DEBUG_EEPROM(dev, + "Ch. %d [%sGHz] %s%s%s%s%s%s(0x%02x %ddBm): Ad-Hoc %ssupported\n", + channel->hw_value, + (band != 1) ? "5.2" : "2.4", + CHECK_AND_PRINT_I(VALID), + CHECK_AND_PRINT_I(IBSS), + CHECK_AND_PRINT_I(ACTIVE), + CHECK_AND_PRINT_I(RADAR), + CHECK_AND_PRINT_I(WIDE), + CHECK_AND_PRINT_I(DFS), + eeprom_ch_info[ch_idx].flags, + eeprom_ch_info[ch_idx].max_power_avg, + ((eeprom_ch_info[ch_idx].flags & + EEPROM_CHANNEL_IBSS) && + !(eeprom_ch_info[ch_idx].flags & + EEPROM_CHANNEL_RADAR)) + ? "" : "not "); + } + } + + if (cfg->eeprom_params->enhanced_txpower) { + /* + * for newer device (6000 series and up) + * EEPROM contain enhanced tx power information + * driver need to process addition information + * to determine the max channel tx power limits + */ + iwl_eeprom_enhanced_txpower(dev, data, eeprom, eeprom_size, + n_channels); + } else { + /* All others use data from channel map */ + int i; + + data->max_tx_pwr_half_dbm = -128; + + for (i = 0; i < n_channels; i++) + data->max_tx_pwr_half_dbm = + max_t(s8, data->max_tx_pwr_half_dbm, + data->channels[i].max_power * 2); + } + + /* Check if we do have HT40 channels */ + if (cfg->eeprom_params->regulatory_bands[5] == + EEPROM_REGULATORY_BAND_NO_HT40 && + cfg->eeprom_params->regulatory_bands[6] == + EEPROM_REGULATORY_BAND_NO_HT40) + return n_channels; + + /* Two additional EEPROM bands for 2.4 and 5 GHz HT40 channels */ + for (band = 6; band <= 7; band++) { + enum nl80211_band ieeeband; + + iwl_init_band_reference(cfg, eeprom, eeprom_size, band, + &eeprom_ch_count, &eeprom_ch_info, + &eeprom_ch_array); + + /* EEPROM band 6 is 2.4, band 7 is 5 GHz */ + ieeeband = (band == 6) ? NL80211_BAND_2GHZ + : NL80211_BAND_5GHZ; + + /* Loop through each band adding each of the channels */ + for (ch_idx = 0; ch_idx < eeprom_ch_count; ch_idx++) { + /* Set up driver's info for lower half */ + iwl_mod_ht40_chan_info(dev, data, n_channels, ieeeband, + eeprom_ch_array[ch_idx], + &eeprom_ch_info[ch_idx], + IEEE80211_CHAN_NO_HT40PLUS); + + /* Set up driver's info for upper half */ + iwl_mod_ht40_chan_info(dev, data, n_channels, ieeeband, + eeprom_ch_array[ch_idx] + 4, + &eeprom_ch_info[ch_idx], + IEEE80211_CHAN_NO_HT40MINUS); + } + } + + return n_channels; +} +#endif + +int iwl_init_sband_channels(struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + int n_channels, enum nl80211_band band) +{ + struct ieee80211_channel *chan = &data->channels[0]; + int n = 0, idx = 0; + + while (idx < n_channels && chan->band != band) + chan = &data->channels[++idx]; + + sband->channels = &data->channels[idx]; + + while (idx < n_channels && chan->band == band) { + chan = &data->channels[++idx]; + n++; + } + + sband->n_channels = n; + + return n; +} + +#define MAX_BIT_RATE_40_MHZ 150 /* Mbps */ +#define MAX_BIT_RATE_20_MHZ 72 /* Mbps */ + +void iwl_init_ht_hw_capab(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_sta_ht_cap *ht_info, + enum nl80211_band band, + u8 tx_chains, u8 rx_chains) +{ + const struct iwl_cfg *cfg = trans->cfg; + int max_bit_rate = 0; + + tx_chains = hweight8(tx_chains); + if (cfg->rx_with_siso_diversity) + rx_chains = 1; + else + rx_chains = hweight8(rx_chains); + + if (!(data->sku_cap_11n_enable) || + (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL) || + !cfg->ht_params) { + ht_info->ht_supported = false; + return; + } + + if (data->sku_cap_mimo_disabled) + rx_chains = 1; + + ht_info->ht_supported = true; + ht_info->cap = IEEE80211_HT_CAP_DSSSCCK40; + + if (cfg->ht_params->stbc) { + ht_info->cap |= (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT); + + if (tx_chains > 1) + ht_info->cap |= IEEE80211_HT_CAP_TX_STBC; + } + + if (cfg->ht_params->ldpc) + ht_info->cap |= IEEE80211_HT_CAP_LDPC_CODING; + + if (trans->trans_cfg->mq_rx_supported || + iwlwifi_mod_params.amsdu_size >= IWL_AMSDU_8K) + ht_info->cap |= IEEE80211_HT_CAP_MAX_AMSDU; + + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; + + ht_info->mcs.rx_mask[0] = 0xFF; + if (rx_chains >= 2) + ht_info->mcs.rx_mask[1] = 0xFF; + if (rx_chains >= 3) + ht_info->mcs.rx_mask[2] = 0xFF; + + if (cfg->ht_params->ht_greenfield_support) + ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD; + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + + max_bit_rate = MAX_BIT_RATE_20_MHZ; + + if (cfg->ht_params->ht40_bands & BIT(band)) { + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + max_bit_rate = MAX_BIT_RATE_40_MHZ; + } + + /* Highest supported Rx data rate */ + max_bit_rate *= rx_chains; + WARN_ON(max_bit_rate & ~IEEE80211_HT_MCS_RX_HIGHEST_MASK); + ht_info->mcs.rx_highest = cpu_to_le16(max_bit_rate); + + /* Tx MCS capabilities */ + ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + if (tx_chains != rx_chains) { + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_RX_DIFF; + ht_info->mcs.tx_params |= ((tx_chains - 1) << + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT); + } +} + +#if IS_ENABLED(CONFIG_IWLDVM) +static void iwl_init_sbands(struct iwl_trans *trans, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const u8 *eeprom, size_t eeprom_size) +{ + struct device *dev = trans->dev; + int n_channels = iwl_init_channel_map(dev, cfg, data, + eeprom, eeprom_size); + int n_used = 0; + struct ieee80211_supported_band *sband; + + sband = &data->bands[NL80211_BAND_2GHZ]; + sband->band = NL80211_BAND_2GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_24_OFFS]; + sband->n_bitrates = N_RATES_24; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_2GHZ); + iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_2GHZ, + data->valid_tx_ant, data->valid_rx_ant); + + sband = &data->bands[NL80211_BAND_5GHZ]; + sband->band = NL80211_BAND_5GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_52_OFFS]; + sband->n_bitrates = N_RATES_52; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_5GHZ); + iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_5GHZ, + data->valid_tx_ant, data->valid_rx_ant); + + if (n_channels != n_used) + IWL_ERR_DEV(dev, "EEPROM: used only %d of %d channels\n", + n_used, n_channels); +} + +/* EEPROM data functions */ + +struct iwl_nvm_data * +iwl_parse_eeprom_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const u8 *eeprom, size_t eeprom_size) +{ + struct iwl_nvm_data *data; + struct device *dev = trans->dev; + const void *tmp; + u16 radio_cfg, sku; + + if (WARN_ON(!cfg || !cfg->eeprom_params)) + return NULL; + + data = kzalloc(struct_size(data, channels, IWL_NUM_CHANNELS), + GFP_KERNEL); + if (!data) + return NULL; + + /* get MAC address(es) */ + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, EEPROM_MAC_ADDRESS); + if (!tmp) + goto err_free; + memcpy(data->hw_addr, tmp, ETH_ALEN); + data->n_hw_addrs = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_NUM_MAC_ADDRESS); + + if (iwl_eeprom_read_calib(eeprom, eeprom_size, data)) + goto err_free; + + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, EEPROM_XTAL); + if (!tmp) + goto err_free; + memcpy(data->xtal_calib, tmp, sizeof(data->xtal_calib)); + + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_RAW_TEMPERATURE); + if (!tmp) + goto err_free; + data->raw_temperature = *(__le16 *)tmp; + + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_KELVIN_TEMPERATURE); + if (!tmp) + goto err_free; + data->kelvin_temperature = *(__le16 *)tmp; + data->kelvin_voltage = *((__le16 *)tmp + 1); + + radio_cfg = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_RADIO_CONFIG); + data->radio_cfg_dash = EEPROM_RF_CFG_DASH_MSK(radio_cfg); + data->radio_cfg_pnum = EEPROM_RF_CFG_PNUM_MSK(radio_cfg); + data->radio_cfg_step = EEPROM_RF_CFG_STEP_MSK(radio_cfg); + data->radio_cfg_type = EEPROM_RF_CFG_TYPE_MSK(radio_cfg); + data->valid_rx_ant = EEPROM_RF_CFG_RX_ANT_MSK(radio_cfg); + data->valid_tx_ant = EEPROM_RF_CFG_TX_ANT_MSK(radio_cfg); + + sku = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_SKU_CAP); + data->sku_cap_11n_enable = sku & EEPROM_SKU_CAP_11N_ENABLE; + data->sku_cap_amt_enable = sku & EEPROM_SKU_CAP_AMT_ENABLE; + data->sku_cap_band_24ghz_enable = sku & EEPROM_SKU_CAP_BAND_24GHZ; + data->sku_cap_band_52ghz_enable = sku & EEPROM_SKU_CAP_BAND_52GHZ; + data->sku_cap_ipan_enable = sku & EEPROM_SKU_CAP_IPAN_ENABLE; + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL) + data->sku_cap_11n_enable = false; + + data->nvm_version = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_VERSION); + + /* check overrides (some devices have wrong EEPROM) */ + if (cfg->valid_tx_ant) + data->valid_tx_ant = cfg->valid_tx_ant; + if (cfg->valid_rx_ant) + data->valid_rx_ant = cfg->valid_rx_ant; + + if (!data->valid_tx_ant || !data->valid_rx_ant) { + IWL_ERR_DEV(dev, "invalid antennas (0x%x, 0x%x)\n", + data->valid_tx_ant, data->valid_rx_ant); + goto err_free; + } + + iwl_init_sbands(trans, cfg, data, eeprom, eeprom_size); + + return data; + err_free: + kfree(data); + return NULL; +} +IWL_EXPORT_SYMBOL(iwl_parse_eeprom_data); +#endif diff --git a/sys/contrib/dev/iwlwifi/iwl-eeprom-parse.h b/sys/contrib/dev/iwlwifi/iwl-eeprom-parse.h new file mode 100644 index 00000000000..0efffb6eeb1 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-eeprom-parse.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018 Intel Corporation + * Copyright (C) 2015 Intel Mobile Communications GmbH + */ +#ifndef __iwl_eeprom_parse_h__ +#define __iwl_eeprom_parse_h__ + +#include +#include +#include +#include "iwl-trans.h" + +struct iwl_nvm_data { + int n_hw_addrs; + u8 hw_addr[ETH_ALEN]; + + u8 calib_version; + __le16 calib_voltage; + + __le16 raw_temperature; + __le16 kelvin_temperature; + __le16 kelvin_voltage; + __le16 xtal_calib[2]; + + bool sku_cap_band_24ghz_enable; + bool sku_cap_band_52ghz_enable; + bool sku_cap_11n_enable; + bool sku_cap_11ac_enable; + bool sku_cap_11ax_enable; + bool sku_cap_amt_enable; + bool sku_cap_ipan_enable; + bool sku_cap_mimo_disabled; + + u16 radio_cfg_type; + u8 radio_cfg_step; + u8 radio_cfg_dash; + u8 radio_cfg_pnum; + u8 valid_tx_ant, valid_rx_ant; + + u32 nvm_version; + s8 max_tx_pwr_half_dbm; + + bool lar_enabled; + bool vht160_supported; + struct ieee80211_supported_band bands[NUM_NL80211_BANDS]; + + /* + * iftype data for low (2.4 GHz) and high (5 and 6 GHz) bands, + * we can use the same for 5 and 6 GHz bands because they have + * the same data + */ + struct { + struct ieee80211_sband_iftype_data low[2]; + struct ieee80211_sband_iftype_data high[2]; + } iftd; + + struct ieee80211_channel channels[]; +}; + +/** + * iwl_parse_eeprom_data - parse EEPROM data and return values + * + * @dev: device pointer we're parsing for, for debug only + * @cfg: device configuration for parsing and overrides + * @eeprom: the EEPROM data + * @eeprom_size: length of the EEPROM data + * + * This function parses all EEPROM values we need and then + * returns a (newly allocated) struct containing all the + * relevant values for driver use. The struct must be freed + * later with iwl_free_nvm_data(). + */ +struct iwl_nvm_data * +iwl_parse_eeprom_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const u8 *eeprom, size_t eeprom_size); + +int iwl_init_sband_channels(struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + int n_channels, enum nl80211_band band); + +void iwl_init_ht_hw_capab(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_sta_ht_cap *ht_info, + enum nl80211_band band, + u8 tx_chains, u8 rx_chains); + +#endif /* __iwl_eeprom_parse_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-eeprom-read.c b/sys/contrib/dev/iwlwifi/iwl-eeprom-read.c new file mode 100644 index 00000000000..1017b487868 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-eeprom-read.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2019, 2021 Intel Corporation + */ +#include +#include +#include +#if defined(__FreeBSD__) +#include +#endif + +#include "iwl-drv.h" +#include "iwl-debug.h" +#include "iwl-eeprom-read.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" + +/* + * EEPROM access time values: + * + * Driver initiates EEPROM read by writing byte address << 1 to CSR_EEPROM_REG. + * Driver then polls CSR_EEPROM_REG for CSR_EEPROM_REG_READ_VALID_MSK (0x1). + * When polling, wait 10 uSec between polling loops, up to a maximum 5000 uSec. + * Driver reads 16-bit value from bits 31-16 of CSR_EEPROM_REG. + */ +#define IWL_EEPROM_ACCESS_TIMEOUT 5000 /* uSec */ + +#define IWL_EEPROM_SEM_TIMEOUT 10 /* microseconds */ +#define IWL_EEPROM_SEM_RETRY_LIMIT 1000 /* number of attempts (not time) */ + + +/* + * The device's EEPROM semaphore prevents conflicts between driver and uCode + * when accessing the EEPROM; each access is a series of pulses to/from the + * EEPROM chip, not a single event, so even reads could conflict if they + * weren't arbitrated by the semaphore. + */ + +#define EEPROM_SEM_TIMEOUT 10 /* milliseconds */ +#define EEPROM_SEM_RETRY_LIMIT 1000 /* number of attempts (not time) */ + +static int iwl_eeprom_acquire_semaphore(struct iwl_trans *trans) +{ + u16 count; + int ret; + + for (count = 0; count < EEPROM_SEM_RETRY_LIMIT; count++) { + /* Request semaphore */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM); + + /* See if we got it */ + ret = iwl_poll_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM, + EEPROM_SEM_TIMEOUT); + if (ret >= 0) { + IWL_DEBUG_EEPROM(trans->dev, + "Acquired semaphore after %d tries.\n", + count+1); + return ret; + } + } + + return ret; +} + +static void iwl_eeprom_release_semaphore(struct iwl_trans *trans) +{ + iwl_clear_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM); +} + +static int iwl_eeprom_verify_signature(struct iwl_trans *trans, bool nvm_is_otp) +{ + u32 gp = iwl_read32(trans, CSR_EEPROM_GP) & CSR_EEPROM_GP_VALID_MSK; + + IWL_DEBUG_EEPROM(trans->dev, "EEPROM signature=0x%08x\n", gp); + + switch (gp) { + case CSR_EEPROM_GP_BAD_SIG_EEP_GOOD_SIG_OTP: + if (!nvm_is_otp) { + IWL_ERR(trans, "EEPROM with bad signature: 0x%08x\n", + gp); + return -ENOENT; + } + return 0; + case CSR_EEPROM_GP_GOOD_SIG_EEP_LESS_THAN_4K: + case CSR_EEPROM_GP_GOOD_SIG_EEP_MORE_THAN_4K: + if (nvm_is_otp) { + IWL_ERR(trans, "OTP with bad signature: 0x%08x\n", gp); + return -ENOENT; + } + return 0; + case CSR_EEPROM_GP_BAD_SIGNATURE_BOTH_EEP_AND_OTP: + default: + IWL_ERR(trans, + "bad EEPROM/OTP signature, type=%s, EEPROM_GP=0x%08x\n", + nvm_is_otp ? "OTP" : "EEPROM", gp); + return -ENOENT; + } +} + +/****************************************************************************** + * + * OTP related functions + * +******************************************************************************/ + +static void iwl_set_otp_access_absolute(struct iwl_trans *trans) +{ + iwl_read32(trans, CSR_OTP_GP_REG); + + iwl_clear_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_OTP_ACCESS_MODE); +} + +static int iwl_nvm_is_otp(struct iwl_trans *trans) +{ + u32 otpgp; + + /* OTP only valid for CP/PP and after */ + switch (trans->hw_rev & CSR_HW_REV_TYPE_MSK) { + case CSR_HW_REV_TYPE_NONE: + IWL_ERR(trans, "Unknown hardware type\n"); + return -EIO; + case CSR_HW_REV_TYPE_5300: + case CSR_HW_REV_TYPE_5350: + case CSR_HW_REV_TYPE_5100: + case CSR_HW_REV_TYPE_5150: + return 0; + default: + otpgp = iwl_read32(trans, CSR_OTP_GP_REG); + if (otpgp & CSR_OTP_GP_REG_DEVICE_SELECT) + return 1; + return 0; + } +} + +static int iwl_init_otp_access(struct iwl_trans *trans) +{ + int ret; + + ret = iwl_finish_nic_init(trans); + if (ret) + return ret; + + iwl_set_bits_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_RESET_REQ); + udelay(5); + iwl_clear_bits_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_RESET_REQ); + + /* + * CSR auto clock gate disable bit - + * this is only applicable for HW with OTP shadow RAM + */ + if (trans->trans_cfg->base_params->shadow_ram_support) + iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + + return 0; +} + +static int iwl_read_otp_word(struct iwl_trans *trans, u16 addr, + __le16 *eeprom_data) +{ + int ret = 0; + u32 r; + u32 otpgp; + + iwl_write32(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_MSK_ADDR & (addr << 1)); + ret = iwl_poll_bit(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_READ_VALID_MSK, + CSR_EEPROM_REG_READ_VALID_MSK, + IWL_EEPROM_ACCESS_TIMEOUT); + if (ret < 0) { + IWL_ERR(trans, "Time out reading OTP[%d]\n", addr); + return ret; + } + r = iwl_read32(trans, CSR_EEPROM_REG); + /* check for ECC errors: */ + otpgp = iwl_read32(trans, CSR_OTP_GP_REG); + if (otpgp & CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK) { + /* stop in this case */ + /* set the uncorrectable OTP ECC bit for acknowledgment */ + iwl_set_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK); + IWL_ERR(trans, "Uncorrectable OTP ECC error, abort OTP read\n"); + return -EINVAL; + } + if (otpgp & CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK) { + /* continue in this case */ + /* set the correctable OTP ECC bit for acknowledgment */ + iwl_set_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK); + IWL_ERR(trans, "Correctable OTP ECC error, continue read\n"); + } + *eeprom_data = cpu_to_le16(r >> 16); + return 0; +} + +/* + * iwl_is_otp_empty: check for empty OTP + */ +static bool iwl_is_otp_empty(struct iwl_trans *trans) +{ + u16 next_link_addr = 0; + __le16 link_value; + bool is_empty = false; + + /* locate the beginning of OTP link list */ + if (!iwl_read_otp_word(trans, next_link_addr, &link_value)) { + if (!link_value) { + IWL_ERR(trans, "OTP is empty\n"); + is_empty = true; + } + } else { + IWL_ERR(trans, "Unable to read first block of OTP list.\n"); + is_empty = true; + } + + return is_empty; +} + + +/* + * iwl_find_otp_image: find EEPROM image in OTP + * finding the OTP block that contains the EEPROM image. + * the last valid block on the link list (the block _before_ the last block) + * is the block we should read and used to configure the device. + * If all the available OTP blocks are full, the last block will be the block + * we should read and used to configure the device. + * only perform this operation if shadow RAM is disabled + */ +static int iwl_find_otp_image(struct iwl_trans *trans, + u16 *validblockaddr) +{ + u16 next_link_addr = 0, valid_addr; + __le16 link_value = 0; + int usedblocks = 0; + + /* set addressing mode to absolute to traverse the link list */ + iwl_set_otp_access_absolute(trans); + + /* checking for empty OTP or error */ + if (iwl_is_otp_empty(trans)) + return -EINVAL; + + /* + * start traverse link list + * until reach the max number of OTP blocks + * different devices have different number of OTP blocks + */ + do { + /* save current valid block address + * check for more block on the link list + */ + valid_addr = next_link_addr; + next_link_addr = le16_to_cpu(link_value) * sizeof(u16); + IWL_DEBUG_EEPROM(trans->dev, "OTP blocks %d addr 0x%x\n", + usedblocks, next_link_addr); + if (iwl_read_otp_word(trans, next_link_addr, &link_value)) + return -EINVAL; + if (!link_value) { + /* + * reach the end of link list, return success and + * set address point to the starting address + * of the image + */ + *validblockaddr = valid_addr; + /* skip first 2 bytes (link list pointer) */ + *validblockaddr += 2; + return 0; + } + /* more in the link list, continue */ + usedblocks++; + } while (usedblocks <= trans->trans_cfg->base_params->max_ll_items); + + /* OTP has no valid blocks */ + IWL_DEBUG_EEPROM(trans->dev, "OTP has no valid blocks\n"); + return -EINVAL; +} + +/* + * iwl_read_eeprom - read EEPROM contents + * + * Load the EEPROM contents from adapter and return it + * and its size. + * + * NOTE: This routine uses the non-debug IO access functions. + */ +int iwl_read_eeprom(struct iwl_trans *trans, u8 **eeprom, size_t *eeprom_size) +{ + __le16 *e; + u32 gp = iwl_read32(trans, CSR_EEPROM_GP); + int sz; + int ret; + u16 addr; + u16 validblockaddr = 0; + u16 cache_addr = 0; + int nvm_is_otp; + + if (!eeprom || !eeprom_size) + return -EINVAL; + + nvm_is_otp = iwl_nvm_is_otp(trans); + if (nvm_is_otp < 0) + return nvm_is_otp; + + sz = trans->trans_cfg->base_params->eeprom_size; + IWL_DEBUG_EEPROM(trans->dev, "NVM size = %d\n", sz); + + e = kmalloc(sz, GFP_KERNEL); + if (!e) + return -ENOMEM; + + ret = iwl_eeprom_verify_signature(trans, nvm_is_otp); + if (ret < 0) { + IWL_ERR(trans, "EEPROM not found, EEPROM_GP=0x%08x\n", gp); + goto err_free; + } + + /* Make sure driver (instead of uCode) is allowed to read EEPROM */ + ret = iwl_eeprom_acquire_semaphore(trans); + if (ret < 0) { + IWL_ERR(trans, "Failed to acquire EEPROM semaphore.\n"); + goto err_free; + } + + if (nvm_is_otp) { + ret = iwl_init_otp_access(trans); + if (ret) { + IWL_ERR(trans, "Failed to initialize OTP access.\n"); + goto err_unlock; + } + + iwl_write32(trans, CSR_EEPROM_GP, + iwl_read32(trans, CSR_EEPROM_GP) & + ~CSR_EEPROM_GP_IF_OWNER_MSK); + + iwl_set_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK | + CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK); + /* traversing the linked list if no shadow ram supported */ + if (!trans->trans_cfg->base_params->shadow_ram_support) { + ret = iwl_find_otp_image(trans, &validblockaddr); + if (ret) + goto err_unlock; + } + for (addr = validblockaddr; addr < validblockaddr + sz; + addr += sizeof(u16)) { + __le16 eeprom_data; + + ret = iwl_read_otp_word(trans, addr, &eeprom_data); + if (ret) + goto err_unlock; + e[cache_addr / 2] = eeprom_data; + cache_addr += sizeof(u16); + } + } else { + /* eeprom is an array of 16bit values */ + for (addr = 0; addr < sz; addr += sizeof(u16)) { + u32 r; + + iwl_write32(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_MSK_ADDR & (addr << 1)); + + ret = iwl_poll_bit(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_READ_VALID_MSK, + CSR_EEPROM_REG_READ_VALID_MSK, + IWL_EEPROM_ACCESS_TIMEOUT); + if (ret < 0) { + IWL_ERR(trans, + "Time out reading EEPROM[%d]\n", addr); + goto err_unlock; + } + r = iwl_read32(trans, CSR_EEPROM_REG); + e[addr / 2] = cpu_to_le16(r >> 16); + } + } + + IWL_DEBUG_EEPROM(trans->dev, "NVM Type: %s\n", + nvm_is_otp ? "OTP" : "EEPROM"); + + iwl_eeprom_release_semaphore(trans); + + *eeprom_size = sz; + *eeprom = (u8 *)e; + return 0; + + err_unlock: + iwl_eeprom_release_semaphore(trans); + err_free: + kfree(e); + + return ret; +} +IWL_EXPORT_SYMBOL(iwl_read_eeprom); diff --git a/sys/contrib/dev/iwlwifi/iwl-eeprom-read.h b/sys/contrib/dev/iwlwifi/iwl-eeprom-read.h new file mode 100644 index 00000000000..63b8e6c6659 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-eeprom-read.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + */ +#ifndef __iwl_eeprom_h__ +#define __iwl_eeprom_h__ + +#include "iwl-trans.h" + +int iwl_read_eeprom(struct iwl_trans *trans, u8 **eeprom, size_t *eeprom_size); + +#endif /* __iwl_eeprom_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-fh.h b/sys/contrib/dev/iwlwifi/iwl-fh.h new file mode 100644 index 00000000000..38623a1a269 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-fh.h @@ -0,0 +1,720 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2020 Intel Corporation + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fh_h__ +#define __iwl_fh_h__ + +#include +#include + +#include "iwl-trans.h" + +/****************************/ +/* Flow Handler Definitions */ +/****************************/ + +/** + * This I/O area is directly read/writable by driver (e.g. Linux uses writel()) + * Addresses are offsets from device's PCI hardware base address. + */ +#define FH_MEM_LOWER_BOUND (0x1000) +#define FH_MEM_UPPER_BOUND (0x2000) +#define FH_MEM_LOWER_BOUND_GEN2 (0xa06000) +#define FH_MEM_UPPER_BOUND_GEN2 (0xa08000) + +/** + * Keep-Warm (KW) buffer base address. + * + * Driver must allocate a 4KByte buffer that is for keeping the + * host DRAM powered on (via dummy accesses to DRAM) to maintain low-latency + * DRAM access when doing Txing or Rxing. The dummy accesses prevent host + * from going into a power-savings mode that would cause higher DRAM latency, + * and possible data over/under-runs, before all Tx/Rx is complete. + * + * Driver loads FH_KW_MEM_ADDR_REG with the physical address (bits 35:4) + * of the buffer, which must be 4K aligned. Once this is set up, the device + * automatically invokes keep-warm accesses when normal accesses might not + * be sufficient to maintain fast DRAM response. + * + * Bit fields: + * 31-0: Keep-warm buffer physical base address [35:4], must be 4K aligned + */ +#define FH_KW_MEM_ADDR_REG (FH_MEM_LOWER_BOUND + 0x97C) + + +/** + * TFD Circular Buffers Base (CBBC) addresses + * + * Device has 16 base pointer registers, one for each of 16 host-DRAM-resident + * circular buffers (CBs/queues) containing Transmit Frame Descriptors (TFDs) + * (see struct iwl_tfd_frame). These 16 pointer registers are offset by 0x04 + * bytes from one another. Each TFD circular buffer in DRAM must be 256-byte + * aligned (address bits 0-7 must be 0). + * Later devices have 20 (5000 series) or 30 (higher) queues, but the registers + * for them are in different places. + * + * Bit fields in each pointer register: + * 27-0: TFD CB physical base address [35:8], must be 256-byte aligned + */ +#define FH_MEM_CBBC_0_15_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0x9D0) +#define FH_MEM_CBBC_0_15_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xA10) +#define FH_MEM_CBBC_16_19_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xBF0) +#define FH_MEM_CBBC_16_19_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xC00) +#define FH_MEM_CBBC_20_31_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xB20) +#define FH_MEM_CBBC_20_31_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xB80) +/* 22000 TFD table address, 64 bit */ +#define TFH_TFDQ_CBB_TABLE (0x1C00) + +/* Find TFD CB base pointer for given queue */ +static inline unsigned int FH_MEM_CBBC_QUEUE(struct iwl_trans *trans, + unsigned int chnl) +{ + if (trans->trans_cfg->use_tfh) { + WARN_ON_ONCE(chnl >= 64); + return TFH_TFDQ_CBB_TABLE + 8 * chnl; + } + if (chnl < 16) + return FH_MEM_CBBC_0_15_LOWER_BOUND + 4 * chnl; + if (chnl < 20) + return FH_MEM_CBBC_16_19_LOWER_BOUND + 4 * (chnl - 16); + WARN_ON_ONCE(chnl >= 32); + return FH_MEM_CBBC_20_31_LOWER_BOUND + 4 * (chnl - 20); +} + +/* 22000 configuration registers */ + +/* + * TFH Configuration register. + * + * BIT fields: + * + * Bits 3:0: + * Define the maximum number of pending read requests. + * Maximum configuration value allowed is 0xC + * Bits 9:8: + * Define the maximum transfer size. (64 / 128 / 256) + * Bit 10: + * When bit is set and transfer size is set to 128B, the TFH will enable + * reading chunks of more than 64B only if the read address is aligned to 128B. + * In case of DRAM read address which is not aligned to 128B, the TFH will + * enable transfer size which doesn't cross 64B DRAM address boundary. +*/ +#define TFH_TRANSFER_MODE (0x1F40) +#define TFH_TRANSFER_MAX_PENDING_REQ 0xc +#define TFH_CHUNK_SIZE_128 BIT(8) +#define TFH_CHUNK_SPLIT_MODE BIT(10) +/* + * Defines the offset address in dwords referring from the beginning of the + * Tx CMD which will be updated in DRAM. + * Note that the TFH offset address for Tx CMD update is always referring to + * the start of the TFD first TB. + * In case of a DRAM Tx CMD update the TFH will update PN and Key ID + */ +#define TFH_TXCMD_UPDATE_CFG (0x1F48) +/* + * Controls TX DMA operation + * + * BIT fields: + * + * Bits 31:30: Enable the SRAM DMA channel. + * Turning on bit 31 will kick the SRAM2DRAM DMA. + * Note that the sram2dram may be enabled only after configuring the DRAM and + * SRAM addresses registers and the byte count register. + * Bits 25:24: Defines the interrupt target upon dram2sram transfer done. When + * set to 1 - interrupt is sent to the driver + * Bit 0: Indicates the snoop configuration +*/ +#define TFH_SRV_DMA_CHNL0_CTRL (0x1F60) +#define TFH_SRV_DMA_SNOOP BIT(0) +#define TFH_SRV_DMA_TO_DRIVER BIT(24) +#define TFH_SRV_DMA_START BIT(31) + +/* Defines the DMA SRAM write start address to transfer a data block */ +#define TFH_SRV_DMA_CHNL0_SRAM_ADDR (0x1F64) + +/* Defines the 64bits DRAM start address to read the DMA data block from */ +#define TFH_SRV_DMA_CHNL0_DRAM_ADDR (0x1F68) + +/* + * Defines the number of bytes to transfer from DRAM to SRAM. + * Note that this register may be configured with non-dword aligned size. + */ +#define TFH_SRV_DMA_CHNL0_BC (0x1F70) + +/** + * Rx SRAM Control and Status Registers (RSCSR) + * + * These registers provide handshake between driver and device for the Rx queue + * (this queue handles *all* command responses, notifications, Rx data, etc. + * sent from uCode to host driver). Unlike Tx, there is only one Rx + * queue, and only one Rx DMA/FIFO channel. Also unlike Tx, which can + * concatenate up to 20 DRAM buffers to form a Tx frame, each Receive Buffer + * Descriptor (RBD) points to only one Rx Buffer (RB); there is a 1:1 + * mapping between RBDs and RBs. + * + * Driver must allocate host DRAM memory for the following, and set the + * physical address of each into device registers: + * + * 1) Receive Buffer Descriptor (RBD) circular buffer (CB), typically with 256 + * entries (although any power of 2, up to 4096, is selectable by driver). + * Each entry (1 dword) points to a receive buffer (RB) of consistent size + * (typically 4K, although 8K or 16K are also selectable by driver). + * Driver sets up RB size and number of RBDs in the CB via Rx config + * register FH_MEM_RCSR_CHNL0_CONFIG_REG. + * + * Bit fields within one RBD: + * 27-0: Receive Buffer physical address bits [35:8], 256-byte aligned + * + * Driver sets physical address [35:8] of base of RBD circular buffer + * into FH_RSCSR_CHNL0_RBDCB_BASE_REG [27:0]. + * + * 2) Rx status buffer, 8 bytes, in which uCode indicates which Rx Buffers + * (RBs) have been filled, via a "write pointer", actually the index of + * the RB's corresponding RBD within the circular buffer. Driver sets + * physical address [35:4] into FH_RSCSR_CHNL0_STTS_WPTR_REG [31:0]. + * + * Bit fields in lower dword of Rx status buffer (upper dword not used + * by driver: + * 31-12: Not used by driver + * 11- 0: Index of last filled Rx buffer descriptor + * (device writes, driver reads this value) + * + * As the driver prepares Receive Buffers (RBs) for device to fill, driver must + * enter pointers to these RBs into contiguous RBD circular buffer entries, + * and update the device's "write" index register, + * FH_RSCSR_CHNL0_RBDCB_WPTR_REG. + * + * This "write" index corresponds to the *next* RBD that the driver will make + * available, i.e. one RBD past the tail of the ready-to-fill RBDs within + * the circular buffer. This value should initially be 0 (before preparing any + * RBs), should be 8 after preparing the first 8 RBs (for example), and must + * wrap back to 0 at the end of the circular buffer (but don't wrap before + * "read" index has advanced past 1! See below). + * NOTE: DEVICE EXPECTS THE WRITE INDEX TO BE INCREMENTED IN MULTIPLES OF 8. + * + * As the device fills RBs (referenced from contiguous RBDs within the circular + * buffer), it updates the Rx status buffer in host DRAM, 2) described above, + * to tell the driver the index of the latest filled RBD. The driver must + * read this "read" index from DRAM after receiving an Rx interrupt from device + * + * The driver must also internally keep track of a third index, which is the + * next RBD to process. When receiving an Rx interrupt, driver should process + * all filled but unprocessed RBs up to, but not including, the RB + * corresponding to the "read" index. For example, if "read" index becomes "1", + * driver may process the RB pointed to by RBD 0. Depending on volume of + * traffic, there may be many RBs to process. + * + * If read index == write index, device thinks there is no room to put new data. + * Due to this, the maximum number of filled RBs is 255, instead of 256. To + * be safe, make sure that there is a gap of at least 2 RBDs between "write" + * and "read" indexes; that is, make sure that there are no more than 254 + * buffers waiting to be filled. + */ +#define FH_MEM_RSCSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xBC0) +#define FH_MEM_RSCSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xC00) +#define FH_MEM_RSCSR_CHNL0 (FH_MEM_RSCSR_LOWER_BOUND) + +/** + * Physical base address of 8-byte Rx Status buffer. + * Bit fields: + * 31-0: Rx status buffer physical base address [35:4], must 16-byte aligned. + */ +#define FH_RSCSR_CHNL0_STTS_WPTR_REG (FH_MEM_RSCSR_CHNL0) + +/** + * Physical base address of Rx Buffer Descriptor Circular Buffer. + * Bit fields: + * 27-0: RBD CD physical base address [35:8], must be 256-byte aligned. + */ +#define FH_RSCSR_CHNL0_RBDCB_BASE_REG (FH_MEM_RSCSR_CHNL0 + 0x004) + +/** + * Rx write pointer (index, really!). + * Bit fields: + * 11-0: Index of driver's most recent prepared-to-be-filled RBD, + 1. + * NOTE: For 256-entry circular buffer, use only bits [7:0]. + */ +#define FH_RSCSR_CHNL0_RBDCB_WPTR_REG (FH_MEM_RSCSR_CHNL0 + 0x008) +#define FH_RSCSR_CHNL0_WPTR (FH_RSCSR_CHNL0_RBDCB_WPTR_REG) + +#define FW_RSCSR_CHNL0_RXDCB_RDPTR_REG (FH_MEM_RSCSR_CHNL0 + 0x00c) +#define FH_RSCSR_CHNL0_RDPTR FW_RSCSR_CHNL0_RXDCB_RDPTR_REG + +/** + * Rx Config/Status Registers (RCSR) + * Rx Config Reg for channel 0 (only channel used) + * + * Driver must initialize FH_MEM_RCSR_CHNL0_CONFIG_REG as follows for + * normal operation (see bit fields). + * + * Clearing FH_MEM_RCSR_CHNL0_CONFIG_REG to 0 turns off Rx DMA. + * Driver should poll FH_MEM_RSSR_RX_STATUS_REG for + * FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE (bit 24) before continuing. + * + * Bit fields: + * 31-30: Rx DMA channel enable: '00' off/pause, '01' pause at end of frame, + * '10' operate normally + * 29-24: reserved + * 23-20: # RBDs in circular buffer = 2^value; use "8" for 256 RBDs (normal), + * min "5" for 32 RBDs, max "12" for 4096 RBDs. + * 19-18: reserved + * 17-16: size of each receive buffer; '00' 4K (normal), '01' 8K, + * '10' 12K, '11' 16K. + * 15-14: reserved + * 13-12: IRQ destination; '00' none, '01' host driver (normal operation) + * 11- 4: timeout for closing Rx buffer and interrupting host (units 32 usec) + * typical value 0x10 (about 1/2 msec) + * 3- 0: reserved + */ +#define FH_MEM_RCSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xC00) +#define FH_MEM_RCSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xCC0) +#define FH_MEM_RCSR_CHNL0 (FH_MEM_RCSR_LOWER_BOUND) + +#define FH_MEM_RCSR_CHNL0_CONFIG_REG (FH_MEM_RCSR_CHNL0) +#define FH_MEM_RCSR_CHNL0_RBDCB_WPTR (FH_MEM_RCSR_CHNL0 + 0x8) +#define FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ (FH_MEM_RCSR_CHNL0 + 0x10) + +#define FH_RCSR_CHNL0_RX_CONFIG_RB_TIMEOUT_MSK (0x00000FF0) /* bits 4-11 */ +#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_MSK (0x00001000) /* bits 12 */ +#define FH_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK (0x00008000) /* bit 15 */ +#define FH_RCSR_CHNL0_RX_CONFIG_RB_SIZE_MSK (0x00030000) /* bits 16-17 */ +#define FH_RCSR_CHNL0_RX_CONFIG_RBDBC_SIZE_MSK (0x00F00000) /* bits 20-23 */ +#define FH_RCSR_CHNL0_RX_CONFIG_DMA_CHNL_EN_MSK (0xC0000000) /* bits 30-31*/ + +#define FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS (20) +#define FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS (4) +#define RX_RB_TIMEOUT (0x11) + +#define FH_RCSR_RX_CONFIG_CHNL_EN_PAUSE_VAL (0x00000000) +#define FH_RCSR_RX_CONFIG_CHNL_EN_PAUSE_EOF_VAL (0x40000000) +#define FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL (0x80000000) + +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K (0x00000000) +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_8K (0x00010000) +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_12K (0x00020000) +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_16K (0x00030000) + +#define FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY (0x00000004) +#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_NO_INT_VAL (0x00000000) +#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL (0x00001000) + +/** + * Rx Shared Status Registers (RSSR) + * + * After stopping Rx DMA channel (writing 0 to + * FH_MEM_RCSR_CHNL0_CONFIG_REG), driver must poll + * FH_MEM_RSSR_RX_STATUS_REG until Rx channel is idle. + * + * Bit fields: + * 24: 1 = Channel 0 is idle + * + * FH_MEM_RSSR_SHARED_CTRL_REG and FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV + * contain default values that should not be altered by the driver. + */ +#define FH_MEM_RSSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xC40) +#define FH_MEM_RSSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xD00) + +#define FH_MEM_RSSR_SHARED_CTRL_REG (FH_MEM_RSSR_LOWER_BOUND) +#define FH_MEM_RSSR_RX_STATUS_REG (FH_MEM_RSSR_LOWER_BOUND + 0x004) +#define FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV\ + (FH_MEM_RSSR_LOWER_BOUND + 0x008) + +#define FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE (0x01000000) + +#define FH_MEM_TFDIB_REG1_ADDR_BITSHIFT 28 +#define FH_MEM_TB_MAX_LENGTH (0x00020000) + +/* 9000 rx series registers */ + +#define RFH_Q0_FRBDCB_BA_LSB 0xA08000 /* 64 bit address */ +#define RFH_Q_FRBDCB_BA_LSB(q) (RFH_Q0_FRBDCB_BA_LSB + (q) * 8) +/* Write index table */ +#define RFH_Q0_FRBDCB_WIDX 0xA08080 +#define RFH_Q_FRBDCB_WIDX(q) (RFH_Q0_FRBDCB_WIDX + (q) * 4) +/* Write index table - shadow registers */ +#define RFH_Q0_FRBDCB_WIDX_TRG 0x1C80 +#define RFH_Q_FRBDCB_WIDX_TRG(q) (RFH_Q0_FRBDCB_WIDX_TRG + (q) * 4) +/* Read index table */ +#define RFH_Q0_FRBDCB_RIDX 0xA080C0 +#define RFH_Q_FRBDCB_RIDX(q) (RFH_Q0_FRBDCB_RIDX + (q) * 4) +/* Used list table */ +#define RFH_Q0_URBDCB_BA_LSB 0xA08100 /* 64 bit address */ +#define RFH_Q_URBDCB_BA_LSB(q) (RFH_Q0_URBDCB_BA_LSB + (q) * 8) +/* Write index table */ +#define RFH_Q0_URBDCB_WIDX 0xA08180 +#define RFH_Q_URBDCB_WIDX(q) (RFH_Q0_URBDCB_WIDX + (q) * 4) +#define RFH_Q0_URBDCB_VAID 0xA081C0 +#define RFH_Q_URBDCB_VAID(q) (RFH_Q0_URBDCB_VAID + (q) * 4) +/* stts */ +#define RFH_Q0_URBD_STTS_WPTR_LSB 0xA08200 /*64 bits address */ +#define RFH_Q_URBD_STTS_WPTR_LSB(q) (RFH_Q0_URBD_STTS_WPTR_LSB + (q) * 8) + +#define RFH_Q0_ORB_WPTR_LSB 0xA08280 +#define RFH_Q_ORB_WPTR_LSB(q) (RFH_Q0_ORB_WPTR_LSB + (q) * 8) +#define RFH_RBDBUF_RBD0_LSB 0xA08300 +#define RFH_RBDBUF_RBD_LSB(q) (RFH_RBDBUF_RBD0_LSB + (q) * 8) + +/** + * RFH Status Register + * + * Bit fields: + * + * Bit 29: RBD_FETCH_IDLE + * This status flag is set by the RFH when there is no active RBD fetch from + * DRAM. + * Once the RFH RBD controller starts fetching (or when there is a pending + * RBD read response from DRAM), this flag is immediately turned off. + * + * Bit 30: SRAM_DMA_IDLE + * This status flag is set by the RFH when there is no active transaction from + * SRAM to DRAM. + * Once the SRAM to DRAM DMA is active, this flag is immediately turned off. + * + * Bit 31: RXF_DMA_IDLE + * This status flag is set by the RFH when there is no active transaction from + * RXF to DRAM. + * Once the RXF-to-DRAM DMA is active, this flag is immediately turned off. + */ +#define RFH_GEN_STATUS 0xA09808 +#define RFH_GEN_STATUS_GEN3 0xA07824 +#define RBD_FETCH_IDLE BIT(29) +#define SRAM_DMA_IDLE BIT(30) +#define RXF_DMA_IDLE BIT(31) + +/* DMA configuration */ +#define RFH_RXF_DMA_CFG 0xA09820 +#define RFH_RXF_DMA_CFG_GEN3 0xA07880 +/* RB size */ +#define RFH_RXF_DMA_RB_SIZE_MASK (0x000F0000) /* bits 16-19 */ +#define RFH_RXF_DMA_RB_SIZE_POS 16 +#define RFH_RXF_DMA_RB_SIZE_1K (0x1 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_2K (0x2 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_4K (0x4 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_8K (0x8 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_12K (0x9 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_16K (0xA << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_20K (0xB << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_24K (0xC << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_28K (0xD << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_32K (0xE << RFH_RXF_DMA_RB_SIZE_POS) +/* RB Circular Buffer size:defines the table sizes in RBD units */ +#define RFH_RXF_DMA_RBDCB_SIZE_MASK (0x00F00000) /* bits 20-23 */ +#define RFH_RXF_DMA_RBDCB_SIZE_POS 20 +#define RFH_RXF_DMA_RBDCB_SIZE_8 (0x3 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_16 (0x4 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_32 (0x5 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_64 (0x7 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_128 (0x7 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_256 (0x8 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_512 (0x9 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_1024 (0xA << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_2048 (0xB << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_MIN_RB_SIZE_MASK (0x03000000) /* bit 24-25 */ +#define RFH_RXF_DMA_MIN_RB_SIZE_POS 24 +#define RFH_RXF_DMA_MIN_RB_4_8 (3 << RFH_RXF_DMA_MIN_RB_SIZE_POS) +#define RFH_RXF_DMA_DROP_TOO_LARGE_MASK (0x04000000) /* bit 26 */ +#define RFH_RXF_DMA_SINGLE_FRAME_MASK (0x20000000) /* bit 29 */ +#define RFH_DMA_EN_MASK (0xC0000000) /* bits 30-31*/ +#define RFH_DMA_EN_ENABLE_VAL BIT(31) + +#define RFH_RXF_RXQ_ACTIVE 0xA0980C + +#define RFH_GEN_CFG 0xA09800 +#define RFH_GEN_CFG_SERVICE_DMA_SNOOP BIT(0) +#define RFH_GEN_CFG_RFH_DMA_SNOOP BIT(1) +#define RFH_GEN_CFG_RB_CHUNK_SIZE BIT(4) +#define RFH_GEN_CFG_RB_CHUNK_SIZE_128 1 +#define RFH_GEN_CFG_RB_CHUNK_SIZE_64 0 +/* the driver assumes everywhere that the default RXQ is 0 */ +#define RFH_GEN_CFG_DEFAULT_RXQ_NUM 0xF00 +#define RFH_GEN_CFG_VAL(_n, _v) FIELD_PREP(RFH_GEN_CFG_ ## _n, _v) + +/* end of 9000 rx series registers */ + +/* TFDB Area - TFDs buffer table */ +#define FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK (0xFFFFFFFF) +#define FH_TFDIB_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0x900) +#define FH_TFDIB_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0x958) +#define FH_TFDIB_CTRL0_REG(_chnl) (FH_TFDIB_LOWER_BOUND + 0x8 * (_chnl)) +#define FH_TFDIB_CTRL1_REG(_chnl) (FH_TFDIB_LOWER_BOUND + 0x8 * (_chnl) + 0x4) + +/** + * Transmit DMA Channel Control/Status Registers (TCSR) + * + * Device has one configuration register for each of 8 Tx DMA/FIFO channels + * supported in hardware (don't confuse these with the 16 Tx queues in DRAM, + * which feed the DMA/FIFO channels); config regs are separated by 0x20 bytes. + * + * To use a Tx DMA channel, driver must initialize its + * FH_TCSR_CHNL_TX_CONFIG_REG(chnl) with: + * + * FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + * FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE_VAL + * + * All other bits should be 0. + * + * Bit fields: + * 31-30: Tx DMA channel enable: '00' off/pause, '01' pause at end of frame, + * '10' operate normally + * 29- 4: Reserved, set to "0" + * 3: Enable internal DMA requests (1, normal operation), disable (0) + * 2- 0: Reserved, set to "0" + */ +#define FH_TCSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xD00) +#define FH_TCSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xE60) + +/* Find Control/Status reg for given Tx DMA/FIFO channel */ +#define FH_TCSR_CHNL_NUM (8) + +/* TCSR: tx_config register values */ +#define FH_TCSR_CHNL_TX_CONFIG_REG(_chnl) \ + (FH_TCSR_LOWER_BOUND + 0x20 * (_chnl)) +#define FH_TCSR_CHNL_TX_CREDIT_REG(_chnl) \ + (FH_TCSR_LOWER_BOUND + 0x20 * (_chnl) + 0x4) +#define FH_TCSR_CHNL_TX_BUF_STS_REG(_chnl) \ + (FH_TCSR_LOWER_BOUND + 0x20 * (_chnl) + 0x8) + +#define FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_TXF (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_DRV (0x00000001) + +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE (0x00000008) + +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_NOINT (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD (0x00100000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_IFTFD (0x00200000) + +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_NOINT (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_ENDTFD (0x00400000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_IFTFD (0x00800000) + +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE_EOF (0x40000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE (0x80000000) + +#define FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_EMPTY (0x00000000) +#define FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_WAIT (0x00002000) +#define FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID (0x00000003) + +#define FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM (20) +#define FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX (12) + +/** + * Tx Shared Status Registers (TSSR) + * + * After stopping Tx DMA channel (writing 0 to + * FH_TCSR_CHNL_TX_CONFIG_REG(chnl)), driver must poll + * FH_TSSR_TX_STATUS_REG until selected Tx channel is idle + * (channel's buffers empty | no pending requests). + * + * Bit fields: + * 31-24: 1 = Channel buffers empty (channel 7:0) + * 23-16: 1 = No pending requests (channel 7:0) + */ +#define FH_TSSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xEA0) +#define FH_TSSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xEC0) + +#define FH_TSSR_TX_STATUS_REG (FH_TSSR_LOWER_BOUND + 0x010) + +/** + * Bit fields for TSSR(Tx Shared Status & Control) error status register: + * 31: Indicates an address error when accessed to internal memory + * uCode/driver must write "1" in order to clear this flag + * 30: Indicates that Host did not send the expected number of dwords to FH + * uCode/driver must write "1" in order to clear this flag + * 16-9:Each status bit is for one channel. Indicates that an (Error) ActDMA + * command was received from the scheduler while the TRB was already full + * with previous command + * uCode/driver must write "1" in order to clear this flag + * 7-0: Each status bit indicates a channel's TxCredit error. When an error + * bit is set, it indicates that the FH has received a full indication + * from the RTC TxFIFO and the current value of the TxCredit counter was + * not equal to zero. This mean that the credit mechanism was not + * synchronized to the TxFIFO status + * uCode/driver must write "1" in order to clear this flag + */ +#define FH_TSSR_TX_ERROR_REG (FH_TSSR_LOWER_BOUND + 0x018) +#define FH_TSSR_TX_MSG_CONFIG_REG (FH_TSSR_LOWER_BOUND + 0x008) + +#define FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(_chnl) ((1 << (_chnl)) << 16) + +/* Tx service channels */ +#define FH_SRVC_CHNL (9) +#define FH_SRVC_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0x9C8) +#define FH_SRVC_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0x9D0) +#define FH_SRVC_CHNL_SRAM_ADDR_REG(_chnl) \ + (FH_SRVC_LOWER_BOUND + ((_chnl) - 9) * 0x4) + +#define FH_TX_CHICKEN_BITS_REG (FH_MEM_LOWER_BOUND + 0xE98) +#define FH_TX_TRB_REG(_chan) (FH_MEM_LOWER_BOUND + 0x958 + (_chan) * 4) + +/* Instruct FH to increment the retry count of a packet when + * it is brought from the memory to TX-FIFO + */ +#define FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN (0x00000002) + +#define RX_POOL_SIZE(rbds) ((rbds) - 1 + \ + IWL_MAX_RX_HW_QUEUES * \ + (RX_CLAIM_REQ_ALLOC - RX_POST_REQ_ALLOC)) +/* cb size is the exponent */ +#define RX_QUEUE_CB_SIZE(x) ilog2(x) + +#define RX_QUEUE_SIZE 256 +#define RX_QUEUE_MASK 255 +#define RX_QUEUE_SIZE_LOG 8 + +/** + * struct iwl_rb_status - reserve buffer status + * host memory mapped FH registers + * @closed_rb_num [0:11] - Indicates the index of the RB which was closed + * @closed_fr_num [0:11] - Indicates the index of the RX Frame which was closed + * @finished_rb_num [0:11] - Indicates the index of the current RB + * in which the last frame was written to + * @finished_fr_num [0:11] - Indicates the index of the RX Frame + * which was transferred + */ +struct iwl_rb_status { + __le16 closed_rb_num; + __le16 closed_fr_num; + __le16 finished_rb_num; + __le16 finished_fr_nam; + __le32 spare; +} __packed; + + +#define TFD_QUEUE_SIZE_MAX (256) +#define TFD_QUEUE_SIZE_MAX_GEN3 (65536) +/* cb size is the exponent - 3 */ +#define TFD_QUEUE_CB_SIZE(x) (ilog2(x) - 3) +#define TFD_QUEUE_SIZE_BC_DUP (64) +#define TFD_QUEUE_BC_SIZE (TFD_QUEUE_SIZE_MAX + TFD_QUEUE_SIZE_BC_DUP) +#define TFD_QUEUE_BC_SIZE_GEN3 1024 +#define IWL_TX_DMA_MASK DMA_BIT_MASK(36) +#define IWL_NUM_OF_TBS 20 +#define IWL_TFH_NUM_TBS 25 + +static inline u8 iwl_get_dma_hi_addr(dma_addr_t addr) +{ + return (sizeof(addr) > sizeof(u32) ? upper_32_bits(addr) : 0) & 0xF; +} + +/** + * enum iwl_tfd_tb_hi_n_len - TB hi_n_len bits + * @TB_HI_N_LEN_ADDR_HI_MSK: high 4 bits (to make it 36) of DMA address + * @TB_HI_N_LEN_LEN_MSK: length of the TB + */ +enum iwl_tfd_tb_hi_n_len { + TB_HI_N_LEN_ADDR_HI_MSK = 0xf, + TB_HI_N_LEN_LEN_MSK = 0xfff0, +}; + +/** + * struct iwl_tfd_tb transmit buffer descriptor within transmit frame descriptor + * + * This structure contains dma address and length of transmission address + * + * @lo: low [31:0] portion of the dma address of TX buffer + * every even is unaligned on 16 bit boundary + * @hi_n_len: &enum iwl_tfd_tb_hi_n_len + */ +struct iwl_tfd_tb { + __le32 lo; + __le16 hi_n_len; +} __packed; + +/** + * struct iwl_tfh_tb transmit buffer descriptor within transmit frame descriptor + * + * This structure contains dma address and length of transmission address + * + * @tb_len length of the tx buffer + * @addr 64 bits dma address + */ +struct iwl_tfh_tb { + __le16 tb_len; + __le64 addr; +} __packed; + +/** + * Each Tx queue uses a circular buffer of 256 TFDs stored in host DRAM. + * Both driver and device share these circular buffers, each of which must be + * contiguous 256 TFDs. + * For pre 22000 HW it is 256 x 128 bytes-per-TFD = 32 KBytes + * For 22000 HW and on it is 256 x 256 bytes-per-TFD = 65 KBytes + * + * Driver must indicate the physical address of the base of each + * circular buffer via the FH_MEM_CBBC_QUEUE registers. + * + * Each TFD contains pointer/size information for up to 20 / 25 data buffers + * in host DRAM. These buffers collectively contain the (one) frame described + * by the TFD. Each buffer must be a single contiguous block of memory within + * itself, but buffers may be scattered in host DRAM. Each buffer has max size + * of (4K - 4). The concatenates all of a TFD's buffers into a single + * Tx frame, up to 8 KBytes in size. + * + * A maximum of 255 (not 256!) TFDs may be on a queue waiting for Tx. + */ + +/** + * struct iwl_tfd - Transmit Frame Descriptor (TFD) + * @ __reserved1[3] reserved + * @ num_tbs 0-4 number of active tbs + * 5 reserved + * 6-7 padding (not used) + * @ tbs[20] transmit frame buffer descriptors + * @ __pad padding + */ +struct iwl_tfd { + u8 __reserved1[3]; + u8 num_tbs; + struct iwl_tfd_tb tbs[IWL_NUM_OF_TBS]; + __le32 __pad; +} __packed; + +/** + * struct iwl_tfh_tfd - Transmit Frame Descriptor (TFD) + * @ num_tbs 0-4 number of active tbs + * 5 -15 reserved + * @ tbs[25] transmit frame buffer descriptors + * @ __pad padding + */ +struct iwl_tfh_tfd { + __le16 num_tbs; + struct iwl_tfh_tb tbs[IWL_TFH_NUM_TBS]; + __le32 __pad; +} __packed; + +/* Keep Warm Size */ +#define IWL_KW_SIZE 0x1000 /* 4k */ + +/* Fixed (non-configurable) rx data from phy */ + +/** + * struct iwlagn_schedq_bc_tbl scheduler byte count table + * base physical address provided by SCD_DRAM_BASE_ADDR + * For devices up to 22000: + * @tfd_offset 0-12 - tx command byte count + * 12-16 - station index + * For 22000: + * @tfd_offset 0-12 - tx command byte count + * 12-13 - number of 64 byte chunks + * 14-16 - reserved + */ +struct iwlagn_scd_bc_tbl { + __le16 tfd_offset[TFD_QUEUE_BC_SIZE]; +} __packed; + +/** + * struct iwl_gen3_bc_tbl scheduler byte count table gen3 + * For AX210 and on: + * @tfd_offset: 0-12 - tx command byte count + * 12-13 - number of 64 byte chunks + * 14-16 - reserved + */ +struct iwl_gen3_bc_tbl { + __le16 tfd_offset[TFD_QUEUE_BC_SIZE_GEN3]; +} __packed; + +#endif /* !__iwl_fh_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-io.c b/sys/contrib/dev/iwlwifi/iwl-io.c new file mode 100644 index 00000000000..46917b4216b --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-io.c @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2003-2014, 2018-2021 Intel Corporation + * Copyright (C) 2015-2016 Intel Deutschland GmbH + */ +#include +#include +#include + +#include "iwl-drv.h" +#include "iwl-io.h" +#include "iwl-csr.h" +#include "iwl-debug.h" +#include "iwl-prph.h" +#include "iwl-fh.h" + +void iwl_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + trace_iwlwifi_dev_iowrite8(trans->dev, ofs, val); + iwl_trans_write8(trans, ofs, val); +} +IWL_EXPORT_SYMBOL(iwl_write8); + +void iwl_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + trace_iwlwifi_dev_iowrite32(trans->dev, ofs, val); + iwl_trans_write32(trans, ofs, val); +} +IWL_EXPORT_SYMBOL(iwl_write32); + +void iwl_write64(struct iwl_trans *trans, u64 ofs, u64 val) +{ + trace_iwlwifi_dev_iowrite64(trans->dev, ofs, val); + iwl_trans_write32(trans, ofs, lower_32_bits(val)); + iwl_trans_write32(trans, ofs + 4, upper_32_bits(val)); +} +IWL_EXPORT_SYMBOL(iwl_write64); + +u32 iwl_read32(struct iwl_trans *trans, u32 ofs) +{ + u32 val = iwl_trans_read32(trans, ofs); + + trace_iwlwifi_dev_ioread32(trans->dev, ofs, val); + return val; +} +IWL_EXPORT_SYMBOL(iwl_read32); + +#define IWL_POLL_INTERVAL 10 /* microseconds */ + +int iwl_poll_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout) +{ + int t = 0; + + do { + if ((iwl_read32(trans, addr) & mask) == (bits & mask)) + return t; + udelay(IWL_POLL_INTERVAL); + t += IWL_POLL_INTERVAL; + } while (t < timeout); + + return -ETIMEDOUT; +} +IWL_EXPORT_SYMBOL(iwl_poll_bit); + +u32 iwl_read_direct32(struct iwl_trans *trans, u32 reg) +{ + u32 value = 0x5a5a5a5a; + + if (iwl_trans_grab_nic_access(trans)) { + value = iwl_read32(trans, reg); + iwl_trans_release_nic_access(trans); + } + + return value; +} +IWL_EXPORT_SYMBOL(iwl_read_direct32); + +void iwl_write_direct32(struct iwl_trans *trans, u32 reg, u32 value) +{ + if (iwl_trans_grab_nic_access(trans)) { + iwl_write32(trans, reg, value); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_write_direct32); + +void iwl_write_direct64(struct iwl_trans *trans, u64 reg, u64 value) +{ + if (iwl_trans_grab_nic_access(trans)) { + iwl_write64(trans, reg, value); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_write_direct64); + +int iwl_poll_direct_bit(struct iwl_trans *trans, u32 addr, u32 mask, + int timeout) +{ + int t = 0; + + do { + if ((iwl_read_direct32(trans, addr) & mask) == mask) + return t; + udelay(IWL_POLL_INTERVAL); + t += IWL_POLL_INTERVAL; + } while (t < timeout); + + return -ETIMEDOUT; +} +IWL_EXPORT_SYMBOL(iwl_poll_direct_bit); + +u32 iwl_read_prph_no_grab(struct iwl_trans *trans, u32 ofs) +{ + u32 val = iwl_trans_read_prph(trans, ofs); + trace_iwlwifi_dev_ioread_prph32(trans->dev, ofs, val); + return val; +} +IWL_EXPORT_SYMBOL(iwl_read_prph_no_grab); + +void iwl_write_prph_no_grab(struct iwl_trans *trans, u32 ofs, u32 val) +{ + trace_iwlwifi_dev_iowrite_prph32(trans->dev, ofs, val); + iwl_trans_write_prph(trans, ofs, val); +} +IWL_EXPORT_SYMBOL(iwl_write_prph_no_grab); + +void iwl_write_prph64_no_grab(struct iwl_trans *trans, u64 ofs, u64 val) +{ + trace_iwlwifi_dev_iowrite_prph64(trans->dev, ofs, val); + iwl_write_prph_no_grab(trans, ofs, val & 0xffffffff); + iwl_write_prph_no_grab(trans, ofs + 4, val >> 32); +} +IWL_EXPORT_SYMBOL(iwl_write_prph64_no_grab); + +u32 iwl_read_prph(struct iwl_trans *trans, u32 ofs) +{ + u32 val = 0x5a5a5a5a; + + if (iwl_trans_grab_nic_access(trans)) { + val = iwl_read_prph_no_grab(trans, ofs); + iwl_trans_release_nic_access(trans); + } + return val; +} +IWL_EXPORT_SYMBOL(iwl_read_prph); + +void iwl_write_prph_delay(struct iwl_trans *trans, u32 ofs, u32 val, u32 delay_ms) +{ + if (iwl_trans_grab_nic_access(trans)) { + mdelay(delay_ms); + iwl_write_prph_no_grab(trans, ofs, val); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_write_prph_delay); + +int iwl_poll_prph_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout) +{ + int t = 0; + + do { + if ((iwl_read_prph(trans, addr) & mask) == (bits & mask)) + return t; + udelay(IWL_POLL_INTERVAL); + t += IWL_POLL_INTERVAL; + } while (t < timeout); + + return -ETIMEDOUT; +} + +void iwl_set_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask) +{ + if (iwl_trans_grab_nic_access(trans)) { + iwl_write_prph_no_grab(trans, ofs, + iwl_read_prph_no_grab(trans, ofs) | + mask); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_set_bits_prph); + +void iwl_set_bits_mask_prph(struct iwl_trans *trans, u32 ofs, + u32 bits, u32 mask) +{ + if (iwl_trans_grab_nic_access(trans)) { + iwl_write_prph_no_grab(trans, ofs, + (iwl_read_prph_no_grab(trans, ofs) & + mask) | bits); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_set_bits_mask_prph); + +void iwl_clear_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask) +{ + u32 val; + + if (iwl_trans_grab_nic_access(trans)) { + val = iwl_read_prph_no_grab(trans, ofs); + iwl_write_prph_no_grab(trans, ofs, (val & ~mask)); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_clear_bits_prph); + +void iwl_force_nmi(struct iwl_trans *trans) +{ + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_9000) + iwl_write_prph_delay(trans, DEVICE_SET_NMI_REG, + DEVICE_SET_NMI_VAL_DRV, 1); + else if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + iwl_write_umac_prph(trans, UREG_NIC_SET_NMI_DRIVER, + UREG_NIC_SET_NMI_DRIVER_NMI_FROM_DRIVER); + else if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_BZ) + iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6, + UREG_DOORBELL_TO_ISR6_NMI_BIT); + else + iwl_write32(trans, CSR_DOORBELL_VECTOR, + CSR_DOORBELL_VECTOR_NMI); +} +IWL_EXPORT_SYMBOL(iwl_force_nmi); + +static const char *get_rfh_string(int cmd) +{ +#define IWL_CMD(x) case x: return #x +#define IWL_CMD_MQ(arg, reg, q) { if (arg == reg(q)) return #reg; } + + int i; + + for (i = 0; i < IWL_MAX_RX_HW_QUEUES; i++) { + IWL_CMD_MQ(cmd, RFH_Q_FRBDCB_BA_LSB, i); + IWL_CMD_MQ(cmd, RFH_Q_FRBDCB_WIDX, i); + IWL_CMD_MQ(cmd, RFH_Q_FRBDCB_RIDX, i); + IWL_CMD_MQ(cmd, RFH_Q_URBD_STTS_WPTR_LSB, i); + } + + switch (cmd) { + IWL_CMD(RFH_RXF_DMA_CFG); + IWL_CMD(RFH_GEN_CFG); + IWL_CMD(RFH_GEN_STATUS); + IWL_CMD(FH_TSSR_TX_STATUS_REG); + IWL_CMD(FH_TSSR_TX_ERROR_REG); + default: + return "UNKNOWN"; + } +#undef IWL_CMD_MQ +} + +struct reg { + u32 addr; + bool is64; +}; + +static int iwl_dump_rfh(struct iwl_trans *trans, char **buf) +{ + int i, q; + int num_q = trans->num_rx_queues; + static const u32 rfh_tbl[] = { + RFH_RXF_DMA_CFG, + RFH_GEN_CFG, + RFH_GEN_STATUS, + FH_TSSR_TX_STATUS_REG, + FH_TSSR_TX_ERROR_REG, + }; + static const struct reg rfh_mq_tbl[] = { + { RFH_Q0_FRBDCB_BA_LSB, true }, + { RFH_Q0_FRBDCB_WIDX, false }, + { RFH_Q0_FRBDCB_RIDX, false }, + { RFH_Q0_URBD_STTS_WPTR_LSB, true }, + }; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (buf) { + int pos = 0; + /* + * Register (up to 34 for name + 8 blank/q for MQ): 40 chars + * Colon + space: 2 characters + * 0X%08x: 10 characters + * New line: 1 character + * Total of 53 characters + */ + size_t bufsz = ARRAY_SIZE(rfh_tbl) * 53 + + ARRAY_SIZE(rfh_mq_tbl) * 53 * num_q + 40; + + *buf = kmalloc(bufsz, GFP_KERNEL); + if (!*buf) + return -ENOMEM; + + pos += scnprintf(*buf + pos, bufsz - pos, + "RFH register values:\n"); + + for (i = 0; i < ARRAY_SIZE(rfh_tbl); i++) + pos += scnprintf(*buf + pos, bufsz - pos, + "%40s: 0X%08x\n", + get_rfh_string(rfh_tbl[i]), + iwl_read_prph(trans, rfh_tbl[i])); + + for (i = 0; i < ARRAY_SIZE(rfh_mq_tbl); i++) + for (q = 0; q < num_q; q++) { + u32 addr = rfh_mq_tbl[i].addr; + + addr += q * (rfh_mq_tbl[i].is64 ? 8 : 4); + pos += scnprintf(*buf + pos, bufsz - pos, + "%34s(q %2d): 0X%08x\n", + get_rfh_string(addr), q, + iwl_read_prph(trans, addr)); + } + + return pos; + } +#endif + + IWL_ERR(trans, "RFH register values:\n"); + for (i = 0; i < ARRAY_SIZE(rfh_tbl); i++) + IWL_ERR(trans, " %34s: 0X%08x\n", + get_rfh_string(rfh_tbl[i]), + iwl_read_prph(trans, rfh_tbl[i])); + + for (i = 0; i < ARRAY_SIZE(rfh_mq_tbl); i++) + for (q = 0; q < num_q; q++) { + u32 addr = rfh_mq_tbl[i].addr; + + addr += q * (rfh_mq_tbl[i].is64 ? 8 : 4); + IWL_ERR(trans, " %34s(q %d): 0X%08x\n", + get_rfh_string(addr), q, + iwl_read_prph(trans, addr)); + } + + return 0; +} + +static const char *get_fh_string(int cmd) +{ + switch (cmd) { + IWL_CMD(FH_RSCSR_CHNL0_STTS_WPTR_REG); + IWL_CMD(FH_RSCSR_CHNL0_RBDCB_BASE_REG); + IWL_CMD(FH_RSCSR_CHNL0_WPTR); + IWL_CMD(FH_MEM_RCSR_CHNL0_CONFIG_REG); + IWL_CMD(FH_MEM_RSSR_SHARED_CTRL_REG); + IWL_CMD(FH_MEM_RSSR_RX_STATUS_REG); + IWL_CMD(FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV); + IWL_CMD(FH_TSSR_TX_STATUS_REG); + IWL_CMD(FH_TSSR_TX_ERROR_REG); + default: + return "UNKNOWN"; + } +#undef IWL_CMD +} + +int iwl_dump_fh(struct iwl_trans *trans, char **buf) +{ + int i; + static const u32 fh_tbl[] = { + FH_RSCSR_CHNL0_STTS_WPTR_REG, + FH_RSCSR_CHNL0_RBDCB_BASE_REG, + FH_RSCSR_CHNL0_WPTR, + FH_MEM_RCSR_CHNL0_CONFIG_REG, + FH_MEM_RSSR_SHARED_CTRL_REG, + FH_MEM_RSSR_RX_STATUS_REG, + FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV, + FH_TSSR_TX_STATUS_REG, + FH_TSSR_TX_ERROR_REG + }; + + if (trans->trans_cfg->mq_rx_supported) + return iwl_dump_rfh(trans, buf); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (buf) { + int pos = 0; + size_t bufsz = ARRAY_SIZE(fh_tbl) * 48 + 40; + + *buf = kmalloc(bufsz, GFP_KERNEL); + if (!*buf) + return -ENOMEM; + + pos += scnprintf(*buf + pos, bufsz - pos, + "FH register values:\n"); + + for (i = 0; i < ARRAY_SIZE(fh_tbl); i++) + pos += scnprintf(*buf + pos, bufsz - pos, + " %34s: 0X%08x\n", + get_fh_string(fh_tbl[i]), + iwl_read_direct32(trans, fh_tbl[i])); + + return pos; + } +#endif + + IWL_ERR(trans, "FH register values:\n"); + for (i = 0; i < ARRAY_SIZE(fh_tbl); i++) + IWL_ERR(trans, " %34s: 0X%08x\n", + get_fh_string(fh_tbl[i]), + iwl_read_direct32(trans, fh_tbl[i])); + + return 0; +} + +#define IWL_HOST_MON_BLOCK_PEMON 0x00 +#define IWL_HOST_MON_BLOCK_HIPM 0x22 + +#define IWL_HOST_MON_BLOCK_PEMON_VEC0 0x00 +#define IWL_HOST_MON_BLOCK_PEMON_VEC1 0x01 +#define IWL_HOST_MON_BLOCK_PEMON_WFPM 0x06 + +static void iwl_dump_host_monitor_block(struct iwl_trans *trans, + u32 block, u32 vec, u32 iter) +{ + int i; + + IWL_ERR(trans, "Host monitor block 0x%x vector 0x%x\n", block, vec); + iwl_write32(trans, CSR_MONITOR_CFG_REG, (block << 8) | vec); + for (i = 0; i < iter; i++) + IWL_ERR(trans, " value [iter %d]: 0x%08x\n", + i, iwl_read32(trans, CSR_MONITOR_STATUS_REG)); +} + +static void iwl_dump_host_monitor(struct iwl_trans *trans) +{ + switch (trans->trans_cfg->device_family) { + case IWL_DEVICE_FAMILY_22000: + case IWL_DEVICE_FAMILY_AX210: + IWL_ERR(trans, "CSR_RESET = 0x%x\n", + iwl_read32(trans, CSR_RESET)); + iwl_dump_host_monitor_block(trans, IWL_HOST_MON_BLOCK_PEMON, + IWL_HOST_MON_BLOCK_PEMON_VEC0, 15); + iwl_dump_host_monitor_block(trans, IWL_HOST_MON_BLOCK_PEMON, + IWL_HOST_MON_BLOCK_PEMON_VEC1, 15); + iwl_dump_host_monitor_block(trans, IWL_HOST_MON_BLOCK_PEMON, + IWL_HOST_MON_BLOCK_PEMON_WFPM, 15); + iwl_dump_host_monitor_block(trans, IWL_HOST_MON_BLOCK_HIPM, + IWL_HOST_MON_BLOCK_PEMON_VEC0, 1); + break; + default: + /* not supported yet */ + return; + } +} + +int iwl_finish_nic_init(struct iwl_trans *trans) +{ + const struct iwl_cfg_trans_params *cfg_trans = trans->trans_cfg; + u32 poll_ready; + int err; + + if (cfg_trans->bisr_workaround) { + /* ensure the TOP FSM isn't still in previous reset */ + mdelay(2); + } + + /* + * Set "initialization complete" bit to move adapter from + * D0U* --> D0A* (powered-up active) state. + */ + if (cfg_trans->device_family >= IWL_DEVICE_FAMILY_BZ) { + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY | + CSR_GP_CNTRL_REG_FLAG_MAC_INIT); + poll_ready = CSR_GP_CNTRL_REG_FLAG_MAC_STATUS; + } else { + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + poll_ready = CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY; + } + + if (cfg_trans->device_family == IWL_DEVICE_FAMILY_8000) + udelay(2); + + /* + * Wait for clock stabilization; once stabilized, access to + * device-internal resources is supported, e.g. iwl_write_prph() + * and accesses to uCode SRAM. + */ + err = iwl_poll_bit(trans, CSR_GP_CNTRL, poll_ready, poll_ready, 25000); + if (err < 0) { + IWL_DEBUG_INFO(trans, "Failed to wake NIC\n"); + + iwl_dump_host_monitor(trans); + } + + if (cfg_trans->bisr_workaround) { + /* ensure BISR shift has finished */ + udelay(200); + } + + return err < 0 ? err : 0; +} +IWL_EXPORT_SYMBOL(iwl_finish_nic_init); + +void iwl_trans_sync_nmi_with_addr(struct iwl_trans *trans, u32 inta_addr, + u32 sw_err_bit) +{ + unsigned long timeout = jiffies + IWL_TRANS_NMI_TIMEOUT; + bool interrupts_enabled = test_bit(STATUS_INT_ENABLED, &trans->status); + + /* if the interrupts were already disabled, there is no point in + * calling iwl_disable_interrupts + */ + if (interrupts_enabled) + iwl_trans_interrupts(trans, false); + + iwl_force_nmi(trans); + while (time_after(timeout, jiffies)) { + u32 inta_hw = iwl_read32(trans, inta_addr); + + /* Error detected by uCode */ + if (inta_hw & sw_err_bit) { + /* Clear causes register */ + iwl_write32(trans, inta_addr, inta_hw & sw_err_bit); + break; + } + + mdelay(1); + } + + /* enable interrupts only if there were already enabled before this + * function to avoid a case were the driver enable interrupts before + * proper configurations were made + */ + if (interrupts_enabled) + iwl_trans_interrupts(trans, true); + + iwl_trans_fw_error(trans, false); +} diff --git a/sys/contrib/dev/iwlwifi/iwl-io.h b/sys/contrib/dev/iwlwifi/iwl-io.h new file mode 100644 index 00000000000..37b3bd62897 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-io.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2018-2021 Intel Corporation + */ +#ifndef __iwl_io_h__ +#define __iwl_io_h__ + +#include "iwl-devtrace.h" +#include "iwl-trans.h" + +void iwl_write8(struct iwl_trans *trans, u32 ofs, u8 val); +void iwl_write32(struct iwl_trans *trans, u32 ofs, u32 val); +void iwl_write64(struct iwl_trans *trans, u64 ofs, u64 val); +u32 iwl_read32(struct iwl_trans *trans, u32 ofs); + +static inline void iwl_set_bit(struct iwl_trans *trans, u32 reg, u32 mask) +{ + iwl_trans_set_bits_mask(trans, reg, mask, mask); +} + +static inline void iwl_clear_bit(struct iwl_trans *trans, u32 reg, u32 mask) +{ + iwl_trans_set_bits_mask(trans, reg, mask, 0); +} + +int iwl_poll_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout); +int iwl_poll_direct_bit(struct iwl_trans *trans, u32 addr, u32 mask, + int timeout); + +u32 iwl_read_direct32(struct iwl_trans *trans, u32 reg); +void iwl_write_direct32(struct iwl_trans *trans, u32 reg, u32 value); +void iwl_write_direct64(struct iwl_trans *trans, u64 reg, u64 value); + + +u32 iwl_read_prph_no_grab(struct iwl_trans *trans, u32 ofs); +u32 iwl_read_prph(struct iwl_trans *trans, u32 ofs); +void iwl_write_prph_no_grab(struct iwl_trans *trans, u32 ofs, u32 val); +void iwl_write_prph64_no_grab(struct iwl_trans *trans, u64 ofs, u64 val); +void iwl_write_prph_delay(struct iwl_trans *trans, u32 ofs, + u32 val, u32 delay_ms); +static inline void iwl_write_prph(struct iwl_trans *trans, u32 ofs, u32 val) +{ + iwl_write_prph_delay(trans, ofs, val, 0); +} + +int iwl_poll_prph_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout); +void iwl_set_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask); +void iwl_set_bits_mask_prph(struct iwl_trans *trans, u32 ofs, + u32 bits, u32 mask); +void iwl_clear_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask); +void iwl_force_nmi(struct iwl_trans *trans); + +int iwl_finish_nic_init(struct iwl_trans *trans); + +/* Error handling */ +int iwl_dump_fh(struct iwl_trans *trans, char **buf); + +/* + * UMAC periphery address space changed from 0xA00000 to 0xD00000 starting from + * device family AX200. So peripheries used in families above and below AX200 + * should go through iwl_..._umac_..._prph. + */ +static inline u32 iwl_umac_prph(struct iwl_trans *trans, u32 ofs) +{ + return ofs + trans->trans_cfg->umac_prph_offset; +} + +static inline u32 iwl_read_umac_prph_no_grab(struct iwl_trans *trans, u32 ofs) +{ + return iwl_read_prph_no_grab(trans, ofs + + trans->trans_cfg->umac_prph_offset); +} + +static inline u32 iwl_read_umac_prph(struct iwl_trans *trans, u32 ofs) +{ + return iwl_read_prph(trans, ofs + trans->trans_cfg->umac_prph_offset); +} + +static inline void iwl_write_umac_prph_no_grab(struct iwl_trans *trans, u32 ofs, + u32 val) +{ + iwl_write_prph_no_grab(trans, ofs + trans->trans_cfg->umac_prph_offset, + val); +} + +static inline void iwl_write_umac_prph(struct iwl_trans *trans, u32 ofs, + u32 val) +{ + iwl_write_prph(trans, ofs + trans->trans_cfg->umac_prph_offset, val); +} + +static inline int iwl_poll_umac_prph_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout) +{ + return iwl_poll_prph_bit(trans, addr + + trans->trans_cfg->umac_prph_offset, + bits, mask, timeout); +} + +#endif diff --git a/sys/contrib/dev/iwlwifi/iwl-modparams.h b/sys/contrib/dev/iwlwifi/iwl-modparams.h new file mode 100644 index 00000000000..32fabd80656 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-modparams.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2020 Intel Corporation + */ +#ifndef __iwl_modparams_h__ +#define __iwl_modparams_h__ + +#include +#include +#include +#ifdef CONFIG_IWLWIFI_DEBUG +#include "iwl-debug.h" +#endif + +extern struct iwl_mod_params iwlwifi_mod_params; + +enum iwl_power_level { + IWL_POWER_INDEX_1, + IWL_POWER_INDEX_2, + IWL_POWER_INDEX_3, + IWL_POWER_INDEX_4, + IWL_POWER_INDEX_5, + IWL_POWER_NUM +}; + +enum iwl_disable_11n { + IWL_DISABLE_HT_ALL = BIT(0), + IWL_DISABLE_HT_TXAGG = BIT(1), + IWL_DISABLE_HT_RXAGG = BIT(2), + IWL_ENABLE_HT_TXAGG = BIT(3), +}; + +enum iwl_amsdu_size { + IWL_AMSDU_DEF = 0, + IWL_AMSDU_4K = 1, + IWL_AMSDU_8K = 2, + IWL_AMSDU_12K = 3, + /* Add 2K at the end to avoid breaking current API */ + IWL_AMSDU_2K = 4, +}; + +enum iwl_uapsd_disable { + IWL_DISABLE_UAPSD_BSS = BIT(0), + IWL_DISABLE_UAPSD_P2P_CLIENT = BIT(1), +}; + +/** + * struct iwl_mod_params + * + * Holds the module parameters + * + * @swcrypto: using hardware encryption, default = 0 + * @disable_11n: disable 11n capabilities, default = 0, + * use IWL_[DIS,EN]ABLE_HT_* constants + * @amsdu_size: See &enum iwl_amsdu_size. + * @fw_restart: restart firmware, default = 1 + * @bt_coex_active: enable bt coex, default = true + * @led_mode: system default, default = 0 + * @power_save: enable power save, default = false + * @power_level: power level, default = 1 + * @debug_level: levels are IWL_DL_* + * @nvm_file: specifies a external NVM file + * @uapsd_disable: disable U-APSD, see &enum iwl_uapsd_disable, default = + * IWL_DISABLE_UAPSD_BSS | IWL_DISABLE_UAPSD_P2P_CLIENT + * @disable_11ac: disable VHT capabilities, default = false. + * @remove_when_gone: remove an inaccessible device from the PCIe bus. + * @enable_ini: enable new FW debug infratructure (INI TLVs) + */ +struct iwl_mod_params { + int swcrypto; + unsigned int disable_11n; + int amsdu_size; + bool fw_restart; + bool bt_coex_active; + int led_mode; + bool power_save; + int power_level; +#ifdef CONFIG_IWLWIFI_DEBUG + enum iwl_dl debug_level; +#endif + char *nvm_file; + u32 uapsd_disable; + bool disable_11ac; + /** + * @disable_11ax: disable HE capabilities, default = false + */ + bool disable_11ax; + bool remove_when_gone; + bool enable_ini; +}; + +static inline bool iwl_enable_rx_ampdu(void) +{ + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_RXAGG) + return false; + return true; +} + +static inline bool iwl_enable_tx_ampdu(void) +{ + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_TXAGG) + return false; + if (iwlwifi_mod_params.disable_11n & IWL_ENABLE_HT_TXAGG) + return true; + + /* enabled by default */ + return true; +} + +#endif /* #__iwl_modparams_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-nvm-parse.c b/sys/contrib/dev/iwlwifi/iwl-nvm-parse.c new file mode 100644 index 00000000000..f992546a0e9 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-nvm-parse.c @@ -0,0 +1,1756 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include +#include +#include +#include +#include + +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "iwl-nvm-parse.h" +#include "iwl-prph.h" +#include "iwl-io.h" +#include "iwl-csr.h" +#include "fw/acpi.h" +#include "fw/api/nvm-reg.h" +#include "fw/api/commands.h" +#include "fw/api/cmdhdr.h" +#include "fw/img.h" + +/* NVM offsets (in words) definitions */ +enum nvm_offsets { + /* NVM HW-Section offset (in words) definitions */ + SUBSYSTEM_ID = 0x0A, + HW_ADDR = 0x15, + + /* NVM SW-Section offset (in words) definitions */ + NVM_SW_SECTION = 0x1C0, + NVM_VERSION = 0, + RADIO_CFG = 1, + SKU = 2, + N_HW_ADDRS = 3, + NVM_CHANNELS = 0x1E0 - NVM_SW_SECTION, + + /* NVM calibration section offset (in words) definitions */ + NVM_CALIB_SECTION = 0x2B8, + XTAL_CALIB = 0x316 - NVM_CALIB_SECTION, + + /* NVM REGULATORY -Section offset (in words) definitions */ + NVM_CHANNELS_SDP = 0, +}; + +enum ext_nvm_offsets { + /* NVM HW-Section offset (in words) definitions */ + MAC_ADDRESS_OVERRIDE_EXT_NVM = 1, + + /* NVM SW-Section offset (in words) definitions */ + NVM_VERSION_EXT_NVM = 0, + N_HW_ADDRS_FAMILY_8000 = 3, + + /* NVM PHY_SKU-Section offset (in words) definitions */ + RADIO_CFG_FAMILY_EXT_NVM = 0, + SKU_FAMILY_8000 = 2, + + /* NVM REGULATORY -Section offset (in words) definitions */ + NVM_CHANNELS_EXTENDED = 0, + NVM_LAR_OFFSET_OLD = 0x4C7, + NVM_LAR_OFFSET = 0x507, + NVM_LAR_ENABLED = 0x7, +}; + +/* SKU Capabilities (actual values from NVM definition) */ +enum nvm_sku_bits { + NVM_SKU_CAP_BAND_24GHZ = BIT(0), + NVM_SKU_CAP_BAND_52GHZ = BIT(1), + NVM_SKU_CAP_11N_ENABLE = BIT(2), + NVM_SKU_CAP_11AC_ENABLE = BIT(3), + NVM_SKU_CAP_MIMO_DISABLE = BIT(5), +}; + +/* + * These are the channel numbers in the order that they are stored in the NVM + */ +static const u16 iwl_nvm_channels[] = { + /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 5 GHz */ + 36, 40, 44 , 48, 52, 56, 60, 64, + 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, + 149, 153, 157, 161, 165 +}; + +static const u16 iwl_ext_nvm_channels[] = { + /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 5 GHz */ + 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, + 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, + 149, 153, 157, 161, 165, 169, 173, 177, 181 +}; + +static const u16 iwl_uhb_nvm_channels[] = { + /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 5 GHz */ + 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, + 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, + 149, 153, 157, 161, 165, 169, 173, 177, 181, + /* 6-7 GHz */ + 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, + 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, + 133, 137, 141, 145, 149, 153, 157, 161, 165, 169, 173, 177, 181, 185, + 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 233 +}; + +#define IWL_NVM_NUM_CHANNELS ARRAY_SIZE(iwl_nvm_channels) +#define IWL_NVM_NUM_CHANNELS_EXT ARRAY_SIZE(iwl_ext_nvm_channels) +#define IWL_NVM_NUM_CHANNELS_UHB ARRAY_SIZE(iwl_uhb_nvm_channels) +#define NUM_2GHZ_CHANNELS 14 +#define NUM_5GHZ_CHANNELS 37 +#define FIRST_2GHZ_HT_MINUS 5 +#define LAST_2GHZ_HT_PLUS 9 +#define N_HW_ADDR_MASK 0xF + +/* rate data (static) */ +static struct ieee80211_rate iwl_cfg80211_rates[] = { + { .bitrate = 1 * 10, .hw_value = 0, .hw_value_short = 0, }, + { .bitrate = 2 * 10, .hw_value = 1, .hw_value_short = 1, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 5.5 * 10, .hw_value = 2, .hw_value_short = 2, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 11 * 10, .hw_value = 3, .hw_value_short = 3, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 6 * 10, .hw_value = 4, .hw_value_short = 4, }, + { .bitrate = 9 * 10, .hw_value = 5, .hw_value_short = 5, }, + { .bitrate = 12 * 10, .hw_value = 6, .hw_value_short = 6, }, + { .bitrate = 18 * 10, .hw_value = 7, .hw_value_short = 7, }, + { .bitrate = 24 * 10, .hw_value = 8, .hw_value_short = 8, }, + { .bitrate = 36 * 10, .hw_value = 9, .hw_value_short = 9, }, + { .bitrate = 48 * 10, .hw_value = 10, .hw_value_short = 10, }, + { .bitrate = 54 * 10, .hw_value = 11, .hw_value_short = 11, }, +}; +#define RATES_24_OFFS 0 +#define N_RATES_24 ARRAY_SIZE(iwl_cfg80211_rates) +#define RATES_52_OFFS 4 +#define N_RATES_52 (N_RATES_24 - RATES_52_OFFS) + +/** + * enum iwl_nvm_channel_flags - channel flags in NVM + * @NVM_CHANNEL_VALID: channel is usable for this SKU/geo + * @NVM_CHANNEL_IBSS: usable as an IBSS channel + * @NVM_CHANNEL_ACTIVE: active scanning allowed + * @NVM_CHANNEL_RADAR: radar detection required + * @NVM_CHANNEL_INDOOR_ONLY: only indoor use is allowed + * @NVM_CHANNEL_GO_CONCURRENT: GO operation is allowed when connected to BSS + * on same channel on 2.4 or same UNII band on 5.2 + * @NVM_CHANNEL_UNIFORM: uniform spreading required + * @NVM_CHANNEL_20MHZ: 20 MHz channel okay + * @NVM_CHANNEL_40MHZ: 40 MHz channel okay + * @NVM_CHANNEL_80MHZ: 80 MHz channel okay + * @NVM_CHANNEL_160MHZ: 160 MHz channel okay + * @NVM_CHANNEL_DC_HIGH: DC HIGH required/allowed (?) + */ +enum iwl_nvm_channel_flags { + NVM_CHANNEL_VALID = BIT(0), + NVM_CHANNEL_IBSS = BIT(1), + NVM_CHANNEL_ACTIVE = BIT(3), + NVM_CHANNEL_RADAR = BIT(4), + NVM_CHANNEL_INDOOR_ONLY = BIT(5), + NVM_CHANNEL_GO_CONCURRENT = BIT(6), + NVM_CHANNEL_UNIFORM = BIT(7), + NVM_CHANNEL_20MHZ = BIT(8), + NVM_CHANNEL_40MHZ = BIT(9), + NVM_CHANNEL_80MHZ = BIT(10), + NVM_CHANNEL_160MHZ = BIT(11), + NVM_CHANNEL_DC_HIGH = BIT(12), +}; + +/** + * enum iwl_reg_capa_flags - global flags applied for the whole regulatory + * domain. + * @REG_CAPA_BF_CCD_LOW_BAND: Beam-forming or Cyclic Delay Diversity in the + * 2.4Ghz band is allowed. + * @REG_CAPA_BF_CCD_HIGH_BAND: Beam-forming or Cyclic Delay Diversity in the + * 5Ghz band is allowed. + * @REG_CAPA_160MHZ_ALLOWED: 11ac channel with a width of 160Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_80MHZ_ALLOWED: 11ac channel with a width of 80Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_MCS_8_ALLOWED: 11ac with MCS 8 is allowed. + * @REG_CAPA_MCS_9_ALLOWED: 11ac with MCS 9 is allowed. + * @REG_CAPA_40MHZ_FORBIDDEN: 11n channel with a width of 40Mhz is forbidden + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_DC_HIGH_ENABLED: DC HIGH allowed. + * @REG_CAPA_11AX_DISABLED: 11ax is forbidden for this regulatory domain. + */ +enum iwl_reg_capa_flags { + REG_CAPA_BF_CCD_LOW_BAND = BIT(0), + REG_CAPA_BF_CCD_HIGH_BAND = BIT(1), + REG_CAPA_160MHZ_ALLOWED = BIT(2), + REG_CAPA_80MHZ_ALLOWED = BIT(3), + REG_CAPA_MCS_8_ALLOWED = BIT(4), + REG_CAPA_MCS_9_ALLOWED = BIT(5), + REG_CAPA_40MHZ_FORBIDDEN = BIT(7), + REG_CAPA_DC_HIGH_ENABLED = BIT(9), + REG_CAPA_11AX_DISABLED = BIT(10), +}; + +/** + * enum iwl_reg_capa_flags_v2 - global flags applied for the whole regulatory + * domain (version 2). + * @REG_CAPA_V2_STRADDLE_DISABLED: Straddle channels (144, 142, 138) are + * disabled. + * @REG_CAPA_V2_BF_CCD_LOW_BAND: Beam-forming or Cyclic Delay Diversity in the + * 2.4Ghz band is allowed. + * @REG_CAPA_V2_BF_CCD_HIGH_BAND: Beam-forming or Cyclic Delay Diversity in the + * 5Ghz band is allowed. + * @REG_CAPA_V2_160MHZ_ALLOWED: 11ac channel with a width of 160Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_V2_80MHZ_ALLOWED: 11ac channel with a width of 80Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_V2_MCS_8_ALLOWED: 11ac with MCS 8 is allowed. + * @REG_CAPA_V2_MCS_9_ALLOWED: 11ac with MCS 9 is allowed. + * @REG_CAPA_V2_WEATHER_DISABLED: Weather radar channels (120, 124, 128, 118, + * 126, 122) are disabled. + * @REG_CAPA_V2_40MHZ_ALLOWED: 11n channel with a width of 40Mhz is allowed + * for this regulatory domain (uvalid only in 5Ghz). + * @REG_CAPA_V2_11AX_DISABLED: 11ax is forbidden for this regulatory domain. + */ +enum iwl_reg_capa_flags_v2 { + REG_CAPA_V2_STRADDLE_DISABLED = BIT(0), + REG_CAPA_V2_BF_CCD_LOW_BAND = BIT(1), + REG_CAPA_V2_BF_CCD_HIGH_BAND = BIT(2), + REG_CAPA_V2_160MHZ_ALLOWED = BIT(3), + REG_CAPA_V2_80MHZ_ALLOWED = BIT(4), + REG_CAPA_V2_MCS_8_ALLOWED = BIT(5), + REG_CAPA_V2_MCS_9_ALLOWED = BIT(6), + REG_CAPA_V2_WEATHER_DISABLED = BIT(7), + REG_CAPA_V2_40MHZ_ALLOWED = BIT(8), + REG_CAPA_V2_11AX_DISABLED = BIT(10), +}; + +/* +* API v2 for reg_capa_flags is relevant from version 6 and onwards of the +* MCC update command response. +*/ +#define REG_CAPA_V2_RESP_VER 6 + +/** + * struct iwl_reg_capa - struct for global regulatory capabilities, Used for + * handling the different APIs of reg_capa_flags. + * + * @allow_40mhz: 11n channel with a width of 40Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @allow_80mhz: 11ac channel with a width of 80Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @allow_160mhz: 11ac channel with a width of 160Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @disable_11ax: 11ax is forbidden for this regulatory domain. + */ +struct iwl_reg_capa { + u16 allow_40mhz; + u16 allow_80mhz; + u16 allow_160mhz; + u16 disable_11ax; +}; + +static inline void iwl_nvm_print_channel_flags(struct device *dev, u32 level, + int chan, u32 flags) +{ +#define CHECK_AND_PRINT_I(x) \ + ((flags & NVM_CHANNEL_##x) ? " " #x : "") + + if (!(flags & NVM_CHANNEL_VALID)) { + IWL_DEBUG_DEV(dev, level, "Ch. %d: 0x%x: No traffic\n", + chan, flags); + return; + } + + /* Note: already can print up to 101 characters, 110 is the limit! */ + IWL_DEBUG_DEV(dev, level, + "Ch. %d: 0x%x:%s%s%s%s%s%s%s%s%s%s%s%s\n", + chan, flags, + CHECK_AND_PRINT_I(VALID), + CHECK_AND_PRINT_I(IBSS), + CHECK_AND_PRINT_I(ACTIVE), + CHECK_AND_PRINT_I(RADAR), + CHECK_AND_PRINT_I(INDOOR_ONLY), + CHECK_AND_PRINT_I(GO_CONCURRENT), + CHECK_AND_PRINT_I(UNIFORM), + CHECK_AND_PRINT_I(20MHZ), + CHECK_AND_PRINT_I(40MHZ), + CHECK_AND_PRINT_I(80MHZ), + CHECK_AND_PRINT_I(160MHZ), + CHECK_AND_PRINT_I(DC_HIGH)); +#undef CHECK_AND_PRINT_I +} + +static u32 iwl_get_channel_flags(u8 ch_num, int ch_idx, enum nl80211_band band, + u32 nvm_flags, const struct iwl_cfg *cfg) +{ + u32 flags = IEEE80211_CHAN_NO_HT40; + + if (band == NL80211_BAND_2GHZ && (nvm_flags & NVM_CHANNEL_40MHZ)) { + if (ch_num <= LAST_2GHZ_HT_PLUS) + flags &= ~IEEE80211_CHAN_NO_HT40PLUS; + if (ch_num >= FIRST_2GHZ_HT_MINUS) + flags &= ~IEEE80211_CHAN_NO_HT40MINUS; + } else if (nvm_flags & NVM_CHANNEL_40MHZ) { + if ((ch_idx - NUM_2GHZ_CHANNELS) % 2 == 0) + flags &= ~IEEE80211_CHAN_NO_HT40PLUS; + else + flags &= ~IEEE80211_CHAN_NO_HT40MINUS; + } + if (!(nvm_flags & NVM_CHANNEL_80MHZ)) + flags |= IEEE80211_CHAN_NO_80MHZ; + if (!(nvm_flags & NVM_CHANNEL_160MHZ)) + flags |= IEEE80211_CHAN_NO_160MHZ; + + if (!(nvm_flags & NVM_CHANNEL_IBSS)) + flags |= IEEE80211_CHAN_NO_IR; + + if (!(nvm_flags & NVM_CHANNEL_ACTIVE)) + flags |= IEEE80211_CHAN_NO_IR; + + if (nvm_flags & NVM_CHANNEL_RADAR) + flags |= IEEE80211_CHAN_RADAR; + + if (nvm_flags & NVM_CHANNEL_INDOOR_ONLY) + flags |= IEEE80211_CHAN_INDOOR_ONLY; + + /* Set the GO concurrent flag only in case that NO_IR is set. + * Otherwise it is meaningless + */ + if ((nvm_flags & NVM_CHANNEL_GO_CONCURRENT) && + (flags & IEEE80211_CHAN_NO_IR)) + flags |= IEEE80211_CHAN_IR_CONCURRENT; + + return flags; +} + +static enum nl80211_band iwl_nl80211_band_from_channel_idx(int ch_idx) +{ + if (ch_idx >= NUM_2GHZ_CHANNELS + NUM_5GHZ_CHANNELS) { + return NL80211_BAND_6GHZ; + } + + if (ch_idx >= NUM_2GHZ_CHANNELS) + return NL80211_BAND_5GHZ; + return NL80211_BAND_2GHZ; +} + +static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const void * const nvm_ch_flags, + u32 sbands_flags, bool v4) +{ + int ch_idx; + int n_channels = 0; + struct ieee80211_channel *channel; + u32 ch_flags; + int num_of_ch; + const u16 *nvm_chan; + + if (cfg->uhb_supported) { + num_of_ch = IWL_NVM_NUM_CHANNELS_UHB; + nvm_chan = iwl_uhb_nvm_channels; + } else if (cfg->nvm_type == IWL_NVM_EXT) { + num_of_ch = IWL_NVM_NUM_CHANNELS_EXT; + nvm_chan = iwl_ext_nvm_channels; + } else { + num_of_ch = IWL_NVM_NUM_CHANNELS; + nvm_chan = iwl_nvm_channels; + } + + for (ch_idx = 0; ch_idx < num_of_ch; ch_idx++) { + enum nl80211_band band = + iwl_nl80211_band_from_channel_idx(ch_idx); + + if (v4) + ch_flags = + __le32_to_cpup((const __le32 *)nvm_ch_flags + ch_idx); + else + ch_flags = + __le16_to_cpup((const __le16 *)nvm_ch_flags + ch_idx); + + if (band == NL80211_BAND_5GHZ && + !data->sku_cap_band_52ghz_enable) + continue; + + /* workaround to disable wide channels in 5GHz */ + if ((sbands_flags & IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ) && + band == NL80211_BAND_5GHZ) { + ch_flags &= ~(NVM_CHANNEL_40MHZ | + NVM_CHANNEL_80MHZ | + NVM_CHANNEL_160MHZ); + } + + if (ch_flags & NVM_CHANNEL_160MHZ) + data->vht160_supported = true; + + if (!(sbands_flags & IWL_NVM_SBANDS_FLAGS_LAR) && + !(ch_flags & NVM_CHANNEL_VALID)) { + /* + * Channels might become valid later if lar is + * supported, hence we still want to add them to + * the list of supported channels to cfg80211. + */ + iwl_nvm_print_channel_flags(dev, IWL_DL_EEPROM, + nvm_chan[ch_idx], ch_flags); + continue; + } + + channel = &data->channels[n_channels]; + n_channels++; + + channel->hw_value = nvm_chan[ch_idx]; + channel->band = band; + channel->center_freq = + ieee80211_channel_to_frequency( + channel->hw_value, channel->band); + + /* Initialize regulatory-based run-time data */ + + /* + * Default value - highest tx power value. max_power + * is not used in mvm, and is used for backwards compatibility + */ + channel->max_power = IWL_DEFAULT_MAX_TX_POWER; + + /* don't put limitations in case we're using LAR */ + if (!(sbands_flags & IWL_NVM_SBANDS_FLAGS_LAR)) + channel->flags = iwl_get_channel_flags(nvm_chan[ch_idx], + ch_idx, band, + ch_flags, cfg); + else + channel->flags = 0; + + /* TODO: Don't put limitations on UHB devices as we still don't + * have NVM for them + */ + if (cfg->uhb_supported) + channel->flags = 0; + iwl_nvm_print_channel_flags(dev, IWL_DL_EEPROM, + channel->hw_value, ch_flags); + IWL_DEBUG_EEPROM(dev, "Ch. %d: %ddBm\n", + channel->hw_value, channel->max_power); + } + + return n_channels; +} + +static void iwl_init_vht_hw_capab(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_sta_vht_cap *vht_cap, + u8 tx_chains, u8 rx_chains) +{ + const struct iwl_cfg *cfg = trans->cfg; + int num_rx_ants = num_of_ant(rx_chains); + int num_tx_ants = num_of_ant(tx_chains); + + vht_cap->vht_supported = true; + + vht_cap->cap = IEEE80211_VHT_CAP_SHORT_GI_80 | + IEEE80211_VHT_CAP_RXSTBC_1 | + IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | + 3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT | + IEEE80211_VHT_MAX_AMPDU_1024K << + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; + + if (data->vht160_supported) + vht_cap->cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ | + IEEE80211_VHT_CAP_SHORT_GI_160; + + if (cfg->vht_mu_mimo_supported) + vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; + + if (cfg->ht_params->ldpc) + vht_cap->cap |= IEEE80211_VHT_CAP_RXLDPC; + + if (data->sku_cap_mimo_disabled) { + num_rx_ants = 1; + num_tx_ants = 1; + } + + if (num_tx_ants > 1) + vht_cap->cap |= IEEE80211_VHT_CAP_TXSTBC; + else + vht_cap->cap |= IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; + + switch (iwlwifi_mod_params.amsdu_size) { + case IWL_AMSDU_DEF: + if (trans->trans_cfg->mq_rx_supported) + vht_cap->cap |= + IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; + else + vht_cap->cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895; + break; + case IWL_AMSDU_2K: + if (trans->trans_cfg->mq_rx_supported) + vht_cap->cap |= + IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; + else + WARN(1, "RB size of 2K is not supported by this device\n"); + break; + case IWL_AMSDU_4K: + vht_cap->cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895; + break; + case IWL_AMSDU_8K: + vht_cap->cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991; + break; + case IWL_AMSDU_12K: + vht_cap->cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; + break; + default: + break; + } + + vht_cap->vht_mcs.rx_mcs_map = + cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 4 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 6 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 8 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 14); + + if (num_rx_ants == 1 || cfg->rx_with_siso_diversity) { + vht_cap->cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN; + /* this works because NOT_SUPPORTED == 3 */ + vht_cap->vht_mcs.rx_mcs_map |= + cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << 2); + } + + vht_cap->vht_mcs.tx_mcs_map = vht_cap->vht_mcs.rx_mcs_map; + + vht_cap->vht_mcs.tx_highest |= + cpu_to_le16(IEEE80211_VHT_EXT_NSS_BW_CAPABLE); +} + +static const u8 iwl_vendor_caps[] = { + 0xdd, /* vendor element */ + 0x06, /* length */ + 0x00, 0x17, 0x35, /* Intel OUI */ + 0x08, /* type (Intel Capabilities) */ + /* followed by 16 bits of capabilities */ +#define IWL_VENDOR_CAP_IMPROVED_BF_FDBK_HE BIT(0) + IWL_VENDOR_CAP_IMPROVED_BF_FDBK_HE, + 0x00 +}; + +static const struct ieee80211_sband_iftype_data iwl_he_capa[] = { + { + .types_mask = BIT(NL80211_IFTYPE_STATION), + .he_cap = { + .has_he = true, + .he_cap_elem = { + .mac_cap_info[0] = + IEEE80211_HE_MAC_CAP0_HTC_HE | + IEEE80211_HE_MAC_CAP0_TWT_REQ, + .mac_cap_info[1] = + IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US | + IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8, + .mac_cap_info[2] = + IEEE80211_HE_MAC_CAP2_32BIT_BA_BITMAP, + .mac_cap_info[3] = + IEEE80211_HE_MAC_CAP3_OMI_CONTROL | + IEEE80211_HE_MAC_CAP3_RX_CTRL_FRAME_TO_MULTIBSS, + .mac_cap_info[4] = + IEEE80211_HE_MAC_CAP4_AMSDU_IN_AMPDU | + IEEE80211_HE_MAC_CAP4_MULTI_TID_AGG_TX_QOS_B39, + .mac_cap_info[5] = + IEEE80211_HE_MAC_CAP5_MULTI_TID_AGG_TX_QOS_B40 | + IEEE80211_HE_MAC_CAP5_MULTI_TID_AGG_TX_QOS_B41 | + IEEE80211_HE_MAC_CAP5_UL_2x996_TONE_RU | + IEEE80211_HE_MAC_CAP5_HE_DYNAMIC_SM_PS | + IEEE80211_HE_MAC_CAP5_HT_VHT_TRIG_FRAME_RX, + .phy_cap_info[0] = + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G | + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G | + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G, + .phy_cap_info[1] = + IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK | + IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A | + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD, + .phy_cap_info[2] = + IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ, + .phy_cap_info[3] = + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_NO_DCM | + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_NO_DCM | + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1, + .phy_cap_info[4] = + IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_8 | + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8, + .phy_cap_info[6] = + IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMING_FB | + IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMING_PARTIAL_BW_FB | + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT, + .phy_cap_info[7] = + IEEE80211_HE_PHY_CAP7_POWER_BOOST_FACTOR_SUPP | + IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI, + .phy_cap_info[8] = + IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI | + IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G | + IEEE80211_HE_PHY_CAP8_20MHZ_IN_160MHZ_HE_PPDU | + IEEE80211_HE_PHY_CAP8_80MHZ_IN_160MHZ_HE_PPDU | + IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_242, + .phy_cap_info[9] = + IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB | + IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB | + IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_RESERVED, + .phy_cap_info[10] = + IEEE80211_HE_PHY_CAP10_HE_MU_M1RU_MAX_LTF, + }, + /* + * Set default Tx/Rx HE MCS NSS Support field. + * Indicate support for up to 2 spatial streams and all + * MCS, without any special cases + */ + .he_mcs_nss_supp = { + .rx_mcs_80 = cpu_to_le16(0xfffa), + .tx_mcs_80 = cpu_to_le16(0xfffa), + .rx_mcs_160 = cpu_to_le16(0xfffa), + .tx_mcs_160 = cpu_to_le16(0xfffa), + .rx_mcs_80p80 = cpu_to_le16(0xffff), + .tx_mcs_80p80 = cpu_to_le16(0xffff), + }, + /* + * Set default PPE thresholds, with PPET16 set to 0, + * PPET8 set to 7 + */ + .ppe_thres = {0x61, 0x1c, 0xc7, 0x71}, + }, + }, + { + .types_mask = BIT(NL80211_IFTYPE_AP), + .he_cap = { + .has_he = true, + .he_cap_elem = { + .mac_cap_info[0] = + IEEE80211_HE_MAC_CAP0_HTC_HE, + .mac_cap_info[1] = + IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US | + IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8, + .mac_cap_info[3] = + IEEE80211_HE_MAC_CAP3_OMI_CONTROL, + .phy_cap_info[0] = + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G | + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G, + .phy_cap_info[1] = + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD, + .phy_cap_info[2] = + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ | + IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US, + .phy_cap_info[3] = + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_NO_DCM | + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_NO_DCM | + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1, + .phy_cap_info[6] = + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT, + .phy_cap_info[7] = + IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI, + .phy_cap_info[8] = + IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI | + IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_242, + .phy_cap_info[9] = + IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_RESERVED, + }, + /* + * Set default Tx/Rx HE MCS NSS Support field. + * Indicate support for up to 2 spatial streams and all + * MCS, without any special cases + */ + .he_mcs_nss_supp = { + .rx_mcs_80 = cpu_to_le16(0xfffa), + .tx_mcs_80 = cpu_to_le16(0xfffa), + .rx_mcs_160 = cpu_to_le16(0xfffa), + .tx_mcs_160 = cpu_to_le16(0xfffa), + .rx_mcs_80p80 = cpu_to_le16(0xffff), + .tx_mcs_80p80 = cpu_to_le16(0xffff), + }, + /* + * Set default PPE thresholds, with PPET16 set to 0, + * PPET8 set to 7 + */ + .ppe_thres = {0x61, 0x1c, 0xc7, 0x71}, + }, + }, +}; + +static void iwl_init_he_6ghz_capa(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + u8 tx_chains, u8 rx_chains) +{ + struct ieee80211_sta_ht_cap ht_cap; + struct ieee80211_sta_vht_cap vht_cap = {}; + struct ieee80211_sband_iftype_data *iftype_data; + u16 he_6ghz_capa = 0; + u32 exp; + int i; + + if (sband->band != NL80211_BAND_6GHZ) + return; + + /* grab HT/VHT capabilities and calculate HE 6 GHz capabilities */ + iwl_init_ht_hw_capab(trans, data, &ht_cap, NL80211_BAND_5GHZ, + tx_chains, rx_chains); + WARN_ON(!ht_cap.ht_supported); + iwl_init_vht_hw_capab(trans, data, &vht_cap, tx_chains, rx_chains); + WARN_ON(!vht_cap.vht_supported); + + he_6ghz_capa |= + u16_encode_bits(ht_cap.ampdu_density, + IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START); + exp = u32_get_bits(vht_cap.cap, + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK); + he_6ghz_capa |= + u16_encode_bits(exp, IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP); + exp = u32_get_bits(vht_cap.cap, IEEE80211_VHT_CAP_MAX_MPDU_MASK); + he_6ghz_capa |= + u16_encode_bits(exp, IEEE80211_HE_6GHZ_CAP_MAX_MPDU_LEN); + /* we don't support extended_ht_cap_info anywhere, so no RD_RESPONDER */ + if (vht_cap.cap & IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN) + he_6ghz_capa |= IEEE80211_HE_6GHZ_CAP_TX_ANTPAT_CONS; + if (vht_cap.cap & IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN) + he_6ghz_capa |= IEEE80211_HE_6GHZ_CAP_RX_ANTPAT_CONS; + + IWL_DEBUG_EEPROM(trans->dev, "he_6ghz_capa=0x%x\n", he_6ghz_capa); + + /* we know it's writable - we set it before ourselves */ + iftype_data = (void *)sband->iftype_data; + for (i = 0; i < sband->n_iftype_data; i++) + iftype_data[i].he_6ghz_capa.capa = cpu_to_le16(he_6ghz_capa); +} + +static void +iwl_nvm_fixup_sband_iftd(struct iwl_trans *trans, + struct ieee80211_supported_band *sband, + struct ieee80211_sband_iftype_data *iftype_data, + u8 tx_chains, u8 rx_chains, + const struct iwl_fw *fw) +{ + bool is_ap = iftype_data->types_mask & BIT(NL80211_IFTYPE_AP); + + /* Advertise an A-MPDU exponent extension based on + * operating band + */ + if (sband->band != NL80211_BAND_2GHZ) + iftype_data->he_cap.he_cap_elem.mac_cap_info[3] |= + IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_1; + else + iftype_data->he_cap.he_cap_elem.mac_cap_info[3] |= + IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_3; + + if (is_ap && iwlwifi_mod_params.nvm_file) + iftype_data->he_cap.he_cap_elem.phy_cap_info[0] |= + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G; + + if ((tx_chains & rx_chains) == ANT_AB) { + iftype_data->he_cap.he_cap_elem.phy_cap_info[2] |= + IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ; + iftype_data->he_cap.he_cap_elem.phy_cap_info[5] |= + IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2 | + IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_2; + if (!is_ap) + iftype_data->he_cap.he_cap_elem.phy_cap_info[7] |= + IEEE80211_HE_PHY_CAP7_MAX_NC_2; + } else if (!is_ap) { + /* If not 2x2, we need to indicate 1x1 in the + * Midamble RX Max NSTS - but not for AP mode + */ + iftype_data->he_cap.he_cap_elem.phy_cap_info[1] &= + ~IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS; + iftype_data->he_cap.he_cap_elem.phy_cap_info[2] &= + ~IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS; + iftype_data->he_cap.he_cap_elem.phy_cap_info[7] |= + IEEE80211_HE_PHY_CAP7_MAX_NC_1; + } + + switch (CSR_HW_RFID_TYPE(trans->hw_rf_id)) { + case IWL_CFG_RF_TYPE_GF: + case IWL_CFG_RF_TYPE_MR: + iftype_data->he_cap.he_cap_elem.phy_cap_info[9] |= + IEEE80211_HE_PHY_CAP9_TX_1024_QAM_LESS_THAN_242_TONE_RU; + if (!is_ap) + iftype_data->he_cap.he_cap_elem.phy_cap_info[9] |= + IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU; + break; + } + + if (fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_BROADCAST_TWT)) + iftype_data->he_cap.he_cap_elem.mac_cap_info[2] |= + IEEE80211_HE_MAC_CAP2_BCAST_TWT; + + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000 && + !is_ap) { + iftype_data->vendor_elems.data = iwl_vendor_caps; + iftype_data->vendor_elems.len = ARRAY_SIZE(iwl_vendor_caps); + } +} + +static void iwl_init_he_hw_capab(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + u8 tx_chains, u8 rx_chains, + const struct iwl_fw *fw) +{ + struct ieee80211_sband_iftype_data *iftype_data; + int i; + + /* should only initialize once */ + if (WARN_ON(sband->iftype_data)) + return; + + BUILD_BUG_ON(sizeof(data->iftd.low) != sizeof(iwl_he_capa)); + BUILD_BUG_ON(sizeof(data->iftd.high) != sizeof(iwl_he_capa)); + + switch (sband->band) { + case NL80211_BAND_2GHZ: + iftype_data = data->iftd.low; + break; + case NL80211_BAND_5GHZ: + case NL80211_BAND_6GHZ: + iftype_data = data->iftd.high; + break; + default: + WARN_ON(1); + return; + } + + memcpy(iftype_data, iwl_he_capa, sizeof(iwl_he_capa)); + + sband->iftype_data = iftype_data; + sband->n_iftype_data = ARRAY_SIZE(iwl_he_capa); + + for (i = 0; i < sband->n_iftype_data; i++) + iwl_nvm_fixup_sband_iftd(trans, sband, &iftype_data[i], + tx_chains, rx_chains, fw); + + iwl_init_he_6ghz_capa(trans, data, sband, tx_chains, rx_chains); +} + +static void iwl_init_sbands(struct iwl_trans *trans, + struct iwl_nvm_data *data, + const void *nvm_ch_flags, u8 tx_chains, + u8 rx_chains, u32 sbands_flags, bool v4, + const struct iwl_fw *fw) +{ + struct device *dev = trans->dev; + const struct iwl_cfg *cfg = trans->cfg; + int n_channels; + int n_used = 0; + struct ieee80211_supported_band *sband; + + n_channels = iwl_init_channel_map(dev, cfg, data, nvm_ch_flags, + sbands_flags, v4); + sband = &data->bands[NL80211_BAND_2GHZ]; + sband->band = NL80211_BAND_2GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_24_OFFS]; + sband->n_bitrates = N_RATES_24; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_2GHZ); + iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_2GHZ, + tx_chains, rx_chains); + + if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax) + iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains, + fw); + + sband = &data->bands[NL80211_BAND_5GHZ]; + sband->band = NL80211_BAND_5GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_52_OFFS]; + sband->n_bitrates = N_RATES_52; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_5GHZ); + iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_5GHZ, + tx_chains, rx_chains); + if (data->sku_cap_11ac_enable && !iwlwifi_mod_params.disable_11ac) + iwl_init_vht_hw_capab(trans, data, &sband->vht_cap, + tx_chains, rx_chains); + + if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax) + iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains, + fw); + + /* 6GHz band. */ + sband = &data->bands[NL80211_BAND_6GHZ]; + sband->band = NL80211_BAND_6GHZ; + /* use the same rates as 5GHz band */ + sband->bitrates = &iwl_cfg80211_rates[RATES_52_OFFS]; + sband->n_bitrates = N_RATES_52; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_6GHZ); + + if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax) + iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains, + fw); + else + sband->n_channels = 0; + if (n_channels != n_used) + IWL_ERR_DEV(dev, "NVM: used only %d of %d channels\n", + n_used, n_channels); +} + +static int iwl_get_sku(const struct iwl_cfg *cfg, const __le16 *nvm_sw, + const __le16 *phy_sku) +{ + if (cfg->nvm_type != IWL_NVM_EXT) + return le16_to_cpup(nvm_sw + SKU); + + return le32_to_cpup((const __le32 *)(phy_sku + SKU_FAMILY_8000)); +} + +static int iwl_get_nvm_version(const struct iwl_cfg *cfg, const __le16 *nvm_sw) +{ + if (cfg->nvm_type != IWL_NVM_EXT) + return le16_to_cpup(nvm_sw + NVM_VERSION); + else + return le32_to_cpup((const __le32 *)(nvm_sw + + NVM_VERSION_EXT_NVM)); +} + +static int iwl_get_radio_cfg(const struct iwl_cfg *cfg, const __le16 *nvm_sw, + const __le16 *phy_sku) +{ + if (cfg->nvm_type != IWL_NVM_EXT) + return le16_to_cpup(nvm_sw + RADIO_CFG); + + return le32_to_cpup((const __le32 *)(phy_sku + RADIO_CFG_FAMILY_EXT_NVM)); + +} + +static int iwl_get_n_hw_addrs(const struct iwl_cfg *cfg, const __le16 *nvm_sw) +{ + int n_hw_addr; + + if (cfg->nvm_type != IWL_NVM_EXT) + return le16_to_cpup(nvm_sw + N_HW_ADDRS); + + n_hw_addr = le32_to_cpup((const __le32 *)(nvm_sw + N_HW_ADDRS_FAMILY_8000)); + + return n_hw_addr & N_HW_ADDR_MASK; +} + +static void iwl_set_radio_cfg(const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + u32 radio_cfg) +{ + if (cfg->nvm_type != IWL_NVM_EXT) { + data->radio_cfg_type = NVM_RF_CFG_TYPE_MSK(radio_cfg); + data->radio_cfg_step = NVM_RF_CFG_STEP_MSK(radio_cfg); + data->radio_cfg_dash = NVM_RF_CFG_DASH_MSK(radio_cfg); + data->radio_cfg_pnum = NVM_RF_CFG_PNUM_MSK(radio_cfg); + return; + } + + /* set the radio configuration for family 8000 */ + data->radio_cfg_type = EXT_NVM_RF_CFG_TYPE_MSK(radio_cfg); + data->radio_cfg_step = EXT_NVM_RF_CFG_STEP_MSK(radio_cfg); + data->radio_cfg_dash = EXT_NVM_RF_CFG_DASH_MSK(radio_cfg); + data->radio_cfg_pnum = EXT_NVM_RF_CFG_FLAVOR_MSK(radio_cfg); + data->valid_tx_ant = EXT_NVM_RF_CFG_TX_ANT_MSK(radio_cfg); + data->valid_rx_ant = EXT_NVM_RF_CFG_RX_ANT_MSK(radio_cfg); +} + +static void iwl_flip_hw_address(__le32 mac_addr0, __le32 mac_addr1, u8 *dest) +{ + const u8 *hw_addr; + + hw_addr = (const u8 *)&mac_addr0; + dest[0] = hw_addr[3]; + dest[1] = hw_addr[2]; + dest[2] = hw_addr[1]; + dest[3] = hw_addr[0]; + + hw_addr = (const u8 *)&mac_addr1; + dest[4] = hw_addr[1]; + dest[5] = hw_addr[0]; +} + +static void iwl_set_hw_address_from_csr(struct iwl_trans *trans, + struct iwl_nvm_data *data) +{ + __le32 mac_addr0 = cpu_to_le32(iwl_read32(trans, + CSR_MAC_ADDR0_STRAP(trans))); + __le32 mac_addr1 = cpu_to_le32(iwl_read32(trans, + CSR_MAC_ADDR1_STRAP(trans))); + + iwl_flip_hw_address(mac_addr0, mac_addr1, data->hw_addr); + /* + * If the OEM fused a valid address, use it instead of the one in the + * OTP + */ + if (is_valid_ether_addr(data->hw_addr)) + return; + + mac_addr0 = cpu_to_le32(iwl_read32(trans, CSR_MAC_ADDR0_OTP(trans))); + mac_addr1 = cpu_to_le32(iwl_read32(trans, CSR_MAC_ADDR1_OTP(trans))); + + iwl_flip_hw_address(mac_addr0, mac_addr1, data->hw_addr); +} + +static void iwl_set_hw_address_family_8000(struct iwl_trans *trans, + const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const __le16 *mac_override, + const __be16 *nvm_hw) +{ + const u8 *hw_addr; + + if (mac_override) { + static const u8 reserved_mac[] = { + 0x02, 0xcc, 0xaa, 0xff, 0xee, 0x00 + }; + + hw_addr = (const u8 *)(mac_override + + MAC_ADDRESS_OVERRIDE_EXT_NVM); + + /* + * Store the MAC address from MAO section. + * No byte swapping is required in MAO section + */ + memcpy(data->hw_addr, hw_addr, ETH_ALEN); + + /* + * Force the use of the OTP MAC address in case of reserved MAC + * address in the NVM, or if address is given but invalid. + */ + if (is_valid_ether_addr(data->hw_addr) && + memcmp(reserved_mac, hw_addr, ETH_ALEN) != 0) + return; + + IWL_ERR(trans, + "mac address from nvm override section is not valid\n"); + } + + if (nvm_hw) { + /* read the mac address from WFMP registers */ + __le32 mac_addr0 = cpu_to_le32(iwl_trans_read_prph(trans, + WFMP_MAC_ADDR_0)); + __le32 mac_addr1 = cpu_to_le32(iwl_trans_read_prph(trans, + WFMP_MAC_ADDR_1)); + + iwl_flip_hw_address(mac_addr0, mac_addr1, data->hw_addr); + + return; + } + + IWL_ERR(trans, "mac address is not found\n"); +} + +static int iwl_set_hw_address(struct iwl_trans *trans, + const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, const __be16 *nvm_hw, + const __le16 *mac_override) +{ + if (cfg->mac_addr_from_csr) { + iwl_set_hw_address_from_csr(trans, data); + } else if (cfg->nvm_type != IWL_NVM_EXT) { + const u8 *hw_addr = (const u8 *)(nvm_hw + HW_ADDR); + + /* The byte order is little endian 16 bit, meaning 214365 */ + data->hw_addr[0] = hw_addr[1]; + data->hw_addr[1] = hw_addr[0]; + data->hw_addr[2] = hw_addr[3]; + data->hw_addr[3] = hw_addr[2]; + data->hw_addr[4] = hw_addr[5]; + data->hw_addr[5] = hw_addr[4]; + } else { + iwl_set_hw_address_family_8000(trans, cfg, data, + mac_override, nvm_hw); + } + + if (!is_valid_ether_addr(data->hw_addr)) { + IWL_ERR(trans, "no valid mac address was found\n"); + return -EINVAL; + } + +#if defined(__linux__) + IWL_INFO(trans, "base HW address: %pM\n", data->hw_addr); +#elif defined(__FreeBSD__) + IWL_INFO(trans, "base HW address: %6D\n", data->hw_addr, ":"); +#endif + + return 0; +} + +static bool +iwl_nvm_no_wide_in_5ghz(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const __be16 *nvm_hw) +{ + /* + * Workaround a bug in Indonesia SKUs where the regulatory in + * some 7000-family OTPs erroneously allow wide channels in + * 5GHz. To check for Indonesia, we take the SKU value from + * bits 1-4 in the subsystem ID and check if it is either 5 or + * 9. In those cases, we need to force-disable wide channels + * in 5GHz otherwise the FW will throw a sysassert when we try + * to use them. + */ + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000) { + /* + * Unlike the other sections in the NVM, the hw + * section uses big-endian. + */ + u16 subsystem_id = be16_to_cpup(nvm_hw + SUBSYSTEM_ID); + u8 sku = (subsystem_id & 0x1e) >> 1; + + if (sku == 5 || sku == 9) { + IWL_DEBUG_EEPROM(trans->dev, + "disabling wide channels in 5GHz (0x%0x %d)\n", + subsystem_id, sku); + return true; + } + } + + return false; +} + +struct iwl_nvm_data * +iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const struct iwl_fw *fw, + const __be16 *nvm_hw, const __le16 *nvm_sw, + const __le16 *nvm_calib, const __le16 *regulatory, + const __le16 *mac_override, const __le16 *phy_sku, + u8 tx_chains, u8 rx_chains) +{ + struct iwl_nvm_data *data; + bool lar_enabled; + u32 sku, radio_cfg; + u32 sbands_flags = 0; + u16 lar_config; + const __le16 *ch_section; + + if (cfg->uhb_supported) + data = kzalloc(struct_size(data, channels, + IWL_NVM_NUM_CHANNELS_UHB), + GFP_KERNEL); + else if (cfg->nvm_type != IWL_NVM_EXT) + data = kzalloc(struct_size(data, channels, + IWL_NVM_NUM_CHANNELS), + GFP_KERNEL); + else + data = kzalloc(struct_size(data, channels, + IWL_NVM_NUM_CHANNELS_EXT), + GFP_KERNEL); + if (!data) + return NULL; + + data->nvm_version = iwl_get_nvm_version(cfg, nvm_sw); + + radio_cfg = iwl_get_radio_cfg(cfg, nvm_sw, phy_sku); + iwl_set_radio_cfg(cfg, data, radio_cfg); + if (data->valid_tx_ant) + tx_chains &= data->valid_tx_ant; + if (data->valid_rx_ant) + rx_chains &= data->valid_rx_ant; + + sku = iwl_get_sku(cfg, nvm_sw, phy_sku); + data->sku_cap_band_24ghz_enable = sku & NVM_SKU_CAP_BAND_24GHZ; + data->sku_cap_band_52ghz_enable = sku & NVM_SKU_CAP_BAND_52GHZ; + data->sku_cap_11n_enable = sku & NVM_SKU_CAP_11N_ENABLE; + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL) + data->sku_cap_11n_enable = false; + data->sku_cap_11ac_enable = data->sku_cap_11n_enable && + (sku & NVM_SKU_CAP_11AC_ENABLE); + data->sku_cap_mimo_disabled = sku & NVM_SKU_CAP_MIMO_DISABLE; + + data->n_hw_addrs = iwl_get_n_hw_addrs(cfg, nvm_sw); + + if (cfg->nvm_type != IWL_NVM_EXT) { + /* Checking for required sections */ + if (!nvm_calib) { + IWL_ERR(trans, + "Can't parse empty Calib NVM sections\n"); + kfree(data); + return NULL; + } + + ch_section = cfg->nvm_type == IWL_NVM_SDP ? + ®ulatory[NVM_CHANNELS_SDP] : + &nvm_sw[NVM_CHANNELS]; + + /* in family 8000 Xtal calibration values moved to OTP */ + data->xtal_calib[0] = *(nvm_calib + XTAL_CALIB); + data->xtal_calib[1] = *(nvm_calib + XTAL_CALIB + 1); + lar_enabled = true; + } else { + u16 lar_offset = data->nvm_version < 0xE39 ? + NVM_LAR_OFFSET_OLD : + NVM_LAR_OFFSET; + + lar_config = le16_to_cpup(regulatory + lar_offset); + data->lar_enabled = !!(lar_config & + NVM_LAR_ENABLED); + lar_enabled = data->lar_enabled; + ch_section = ®ulatory[NVM_CHANNELS_EXTENDED]; + } + + /* If no valid mac address was found - bail out */ + if (iwl_set_hw_address(trans, cfg, data, nvm_hw, mac_override)) { + kfree(data); + return NULL; + } + + if (lar_enabled && + fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_LAR_SUPPORT)) + sbands_flags |= IWL_NVM_SBANDS_FLAGS_LAR; + + if (iwl_nvm_no_wide_in_5ghz(trans, cfg, nvm_hw)) + sbands_flags |= IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ; + + iwl_init_sbands(trans, data, ch_section, tx_chains, rx_chains, + sbands_flags, false, fw); + data->calib_version = 255; + + return data; +} +IWL_EXPORT_SYMBOL(iwl_parse_nvm_data); + +static u32 iwl_nvm_get_regdom_bw_flags(const u16 *nvm_chan, + int ch_idx, u16 nvm_flags, + struct iwl_reg_capa reg_capa, + const struct iwl_cfg *cfg) +{ + u32 flags = NL80211_RRF_NO_HT40; + + if (ch_idx < NUM_2GHZ_CHANNELS && + (nvm_flags & NVM_CHANNEL_40MHZ)) { + if (nvm_chan[ch_idx] <= LAST_2GHZ_HT_PLUS) + flags &= ~NL80211_RRF_NO_HT40PLUS; + if (nvm_chan[ch_idx] >= FIRST_2GHZ_HT_MINUS) + flags &= ~NL80211_RRF_NO_HT40MINUS; + } else if (nvm_flags & NVM_CHANNEL_40MHZ) { + if ((ch_idx - NUM_2GHZ_CHANNELS) % 2 == 0) + flags &= ~NL80211_RRF_NO_HT40PLUS; + else + flags &= ~NL80211_RRF_NO_HT40MINUS; + } + + if (!(nvm_flags & NVM_CHANNEL_80MHZ)) + flags |= NL80211_RRF_NO_80MHZ; + if (!(nvm_flags & NVM_CHANNEL_160MHZ)) + flags |= NL80211_RRF_NO_160MHZ; + + if (!(nvm_flags & NVM_CHANNEL_ACTIVE)) + flags |= NL80211_RRF_NO_IR; + + if (nvm_flags & NVM_CHANNEL_RADAR) + flags |= NL80211_RRF_DFS; + + if (nvm_flags & NVM_CHANNEL_INDOOR_ONLY) + flags |= NL80211_RRF_NO_OUTDOOR; + + /* Set the GO concurrent flag only in case that NO_IR is set. + * Otherwise it is meaningless + */ + if ((nvm_flags & NVM_CHANNEL_GO_CONCURRENT) && + (flags & NL80211_RRF_NO_IR)) + flags |= NL80211_RRF_GO_CONCURRENT; + + /* + * reg_capa is per regulatory domain so apply it for every channel + */ + if (ch_idx >= NUM_2GHZ_CHANNELS) { + if (!reg_capa.allow_40mhz) + flags |= NL80211_RRF_NO_HT40; + + if (!reg_capa.allow_80mhz) + flags |= NL80211_RRF_NO_80MHZ; + + if (!reg_capa.allow_160mhz) + flags |= NL80211_RRF_NO_160MHZ; + } + if (reg_capa.disable_11ax) + flags |= NL80211_RRF_NO_HE; + + return flags; +} + +static struct iwl_reg_capa iwl_get_reg_capa(u16 flags, u8 resp_ver) +{ + struct iwl_reg_capa reg_capa; + + if (resp_ver >= REG_CAPA_V2_RESP_VER) { + reg_capa.allow_40mhz = flags & REG_CAPA_V2_40MHZ_ALLOWED; + reg_capa.allow_80mhz = flags & REG_CAPA_V2_80MHZ_ALLOWED; + reg_capa.allow_160mhz = flags & REG_CAPA_V2_160MHZ_ALLOWED; + reg_capa.disable_11ax = flags & REG_CAPA_V2_11AX_DISABLED; + } else { + reg_capa.allow_40mhz = !(flags & REG_CAPA_40MHZ_FORBIDDEN); + reg_capa.allow_80mhz = flags & REG_CAPA_80MHZ_ALLOWED; + reg_capa.allow_160mhz = flags & REG_CAPA_160MHZ_ALLOWED; + reg_capa.disable_11ax = flags & REG_CAPA_11AX_DISABLED; + } + return reg_capa; +} + +struct ieee80211_regdomain * +iwl_parse_nvm_mcc_info(struct device *dev, const struct iwl_cfg *cfg, + int num_of_ch, __le32 *channels, u16 fw_mcc, + u16 geo_info, u16 cap, u8 resp_ver) +{ + int ch_idx; + u16 ch_flags; + u32 reg_rule_flags, prev_reg_rule_flags = 0; + const u16 *nvm_chan; + struct ieee80211_regdomain *regd, *copy_rd; + struct ieee80211_reg_rule *rule; + enum nl80211_band band; + int center_freq, prev_center_freq = 0; + int valid_rules = 0; + bool new_rule; + int max_num_ch; + struct iwl_reg_capa reg_capa; + + if (cfg->uhb_supported) { + max_num_ch = IWL_NVM_NUM_CHANNELS_UHB; + nvm_chan = iwl_uhb_nvm_channels; + } else if (cfg->nvm_type == IWL_NVM_EXT) { + max_num_ch = IWL_NVM_NUM_CHANNELS_EXT; + nvm_chan = iwl_ext_nvm_channels; + } else { + max_num_ch = IWL_NVM_NUM_CHANNELS; + nvm_chan = iwl_nvm_channels; + } + + if (WARN_ON(num_of_ch > max_num_ch)) + num_of_ch = max_num_ch; + + if (WARN_ON_ONCE(num_of_ch > NL80211_MAX_SUPP_REG_RULES)) + return ERR_PTR(-EINVAL); + + IWL_DEBUG_DEV(dev, IWL_DL_LAR, "building regdom for %d channels\n", + num_of_ch); + + /* build a regdomain rule for every valid channel */ + regd = kzalloc(struct_size(regd, reg_rules, num_of_ch), GFP_KERNEL); + if (!regd) + return ERR_PTR(-ENOMEM); + + /* set alpha2 from FW. */ + regd->alpha2[0] = fw_mcc >> 8; + regd->alpha2[1] = fw_mcc & 0xff; + + /* parse regulatory capability flags */ + reg_capa = iwl_get_reg_capa(cap, resp_ver); + + for (ch_idx = 0; ch_idx < num_of_ch; ch_idx++) { + ch_flags = (u16)__le32_to_cpup(channels + ch_idx); + band = iwl_nl80211_band_from_channel_idx(ch_idx); + center_freq = ieee80211_channel_to_frequency(nvm_chan[ch_idx], + band); + new_rule = false; + + if (!(ch_flags & NVM_CHANNEL_VALID)) { + iwl_nvm_print_channel_flags(dev, IWL_DL_LAR, + nvm_chan[ch_idx], ch_flags); + continue; + } + + reg_rule_flags = iwl_nvm_get_regdom_bw_flags(nvm_chan, ch_idx, + ch_flags, reg_capa, + cfg); + + /* we can't continue the same rule */ + if (ch_idx == 0 || prev_reg_rule_flags != reg_rule_flags || + center_freq - prev_center_freq > 20) { + valid_rules++; + new_rule = true; + } + + rule = ®d->reg_rules[valid_rules - 1]; + + if (new_rule) + rule->freq_range.start_freq_khz = + MHZ_TO_KHZ(center_freq - 10); + + rule->freq_range.end_freq_khz = MHZ_TO_KHZ(center_freq + 10); + + /* this doesn't matter - not used by FW */ + rule->power_rule.max_antenna_gain = DBI_TO_MBI(6); + rule->power_rule.max_eirp = + DBM_TO_MBM(IWL_DEFAULT_MAX_TX_POWER); + + rule->flags = reg_rule_flags; + + /* rely on auto-calculation to merge BW of contiguous chans */ + rule->flags |= NL80211_RRF_AUTO_BW; + rule->freq_range.max_bandwidth_khz = 0; + + prev_center_freq = center_freq; + prev_reg_rule_flags = reg_rule_flags; + + iwl_nvm_print_channel_flags(dev, IWL_DL_LAR, + nvm_chan[ch_idx], ch_flags); + + if (!(geo_info & GEO_WMM_ETSI_5GHZ_INFO) || + band == NL80211_BAND_2GHZ) + continue; + + reg_query_regdb_wmm(regd->alpha2, center_freq, rule); + } + + /* + * Certain firmware versions might report no valid channels + * if booted in RF-kill, i.e. not all calibrations etc. are + * running. We'll get out of this situation later when the + * rfkill is removed and we update the regdomain again, but + * since cfg80211 doesn't accept an empty regdomain, add a + * dummy (unusable) rule here in this case so we can init. + */ + if (!valid_rules) { + valid_rules = 1; + rule = ®d->reg_rules[valid_rules - 1]; + rule->freq_range.start_freq_khz = MHZ_TO_KHZ(2412); + rule->freq_range.end_freq_khz = MHZ_TO_KHZ(2413); + rule->freq_range.max_bandwidth_khz = MHZ_TO_KHZ(1); + rule->power_rule.max_antenna_gain = DBI_TO_MBI(6); + rule->power_rule.max_eirp = + DBM_TO_MBM(IWL_DEFAULT_MAX_TX_POWER); + } + + regd->n_reg_rules = valid_rules; + + /* + * Narrow down regdom for unused regulatory rules to prevent hole + * between reg rules to wmm rules. + */ + copy_rd = kmemdup(regd, struct_size(regd, reg_rules, valid_rules), + GFP_KERNEL); + if (!copy_rd) + copy_rd = ERR_PTR(-ENOMEM); + + kfree(regd); + return copy_rd; +} +IWL_EXPORT_SYMBOL(iwl_parse_nvm_mcc_info); + +#define IWL_MAX_NVM_SECTION_SIZE 0x1b58 +#define IWL_MAX_EXT_NVM_SECTION_SIZE 0x1ffc +#define MAX_NVM_FILE_LEN 16384 + +void iwl_nvm_fixups(u32 hw_id, unsigned int section, u8 *data, + unsigned int len) +{ +#define IWL_4165_DEVICE_ID 0x5501 +#define NVM_SKU_CAP_MIMO_DISABLE BIT(5) + + if (section == NVM_SECTION_TYPE_PHY_SKU && + hw_id == IWL_4165_DEVICE_ID && data && len >= 5 && + (data[4] & NVM_SKU_CAP_MIMO_DISABLE)) + /* OTP 0x52 bug work around: it's a 1x1 device */ + data[3] = ANT_B | (ANT_B << 4); +} +IWL_EXPORT_SYMBOL(iwl_nvm_fixups); + +/* + * Reads external NVM from a file into mvm->nvm_sections + * + * HOW TO CREATE THE NVM FILE FORMAT: + * ------------------------------ + * 1. create hex file, format: + * 3800 -> header + * 0000 -> header + * 5a40 -> data + * + * rev - 6 bit (word1) + * len - 10 bit (word1) + * id - 4 bit (word2) + * rsv - 12 bit (word2) + * + * 2. flip 8bits with 8 bits per line to get the right NVM file format + * + * 3. create binary file from the hex file + * + * 4. save as "iNVM_xxx.bin" under /lib/firmware + */ +int iwl_read_external_nvm(struct iwl_trans *trans, + const char *nvm_file_name, + struct iwl_nvm_section *nvm_sections) +{ + int ret, section_size; + u16 section_id; + const struct firmware *fw_entry; + const struct { + __le16 word1; + __le16 word2; + u8 data[]; + } *file_sec; + const u8 *eof; + u8 *temp; + int max_section_size; + const __le32 *dword_buff; + +#define NVM_WORD1_LEN(x) (8 * (x & 0x03FF)) +#define NVM_WORD2_ID(x) (x >> 12) +#define EXT_NVM_WORD2_LEN(x) (2 * (((x) & 0xFF) << 8 | (x) >> 8)) +#define EXT_NVM_WORD1_ID(x) ((x) >> 4) +#define NVM_HEADER_0 (0x2A504C54) +#define NVM_HEADER_1 (0x4E564D2A) +#define NVM_HEADER_SIZE (4 * sizeof(u32)) + + IWL_DEBUG_EEPROM(trans->dev, "Read from external NVM\n"); + + /* Maximal size depends on NVM version */ + if (trans->cfg->nvm_type != IWL_NVM_EXT) + max_section_size = IWL_MAX_NVM_SECTION_SIZE; + else + max_section_size = IWL_MAX_EXT_NVM_SECTION_SIZE; + + /* + * Obtain NVM image via request_firmware. Since we already used + * request_firmware_nowait() for the firmware binary load and only + * get here after that we assume the NVM request can be satisfied + * synchronously. + */ + ret = request_firmware(&fw_entry, nvm_file_name, trans->dev); + if (ret) { + IWL_ERR(trans, "ERROR: %s isn't available %d\n", + nvm_file_name, ret); + return ret; + } + + IWL_INFO(trans, "Loaded NVM file %s (%zu bytes)\n", + nvm_file_name, fw_entry->size); + + if (fw_entry->size > MAX_NVM_FILE_LEN) { + IWL_ERR(trans, "NVM file too large\n"); + ret = -EINVAL; + goto out; + } + + eof = fw_entry->data + fw_entry->size; + dword_buff = (const __le32 *)fw_entry->data; + + /* some NVM file will contain a header. + * The header is identified by 2 dwords header as follow: + * dword[0] = 0x2A504C54 + * dword[1] = 0x4E564D2A + * + * This header must be skipped when providing the NVM data to the FW. + */ + if (fw_entry->size > NVM_HEADER_SIZE && + dword_buff[0] == cpu_to_le32(NVM_HEADER_0) && + dword_buff[1] == cpu_to_le32(NVM_HEADER_1)) { + file_sec = (const void *)(fw_entry->data + NVM_HEADER_SIZE); + IWL_INFO(trans, "NVM Version %08X\n", le32_to_cpu(dword_buff[2])); + IWL_INFO(trans, "NVM Manufacturing date %08X\n", + le32_to_cpu(dword_buff[3])); + + /* nvm file validation, dword_buff[2] holds the file version */ + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_8000 && + CSR_HW_REV_STEP(trans->hw_rev) == SILICON_C_STEP && + le32_to_cpu(dword_buff[2]) < 0xE4A) { + ret = -EFAULT; + goto out; + } + } else { + file_sec = (const void *)fw_entry->data; + } + + while (true) { + if (file_sec->data > eof) { + IWL_ERR(trans, + "ERROR - NVM file too short for section header\n"); + ret = -EINVAL; + break; + } + + /* check for EOF marker */ + if (!file_sec->word1 && !file_sec->word2) { + ret = 0; + break; + } + + if (trans->cfg->nvm_type != IWL_NVM_EXT) { + section_size = + 2 * NVM_WORD1_LEN(le16_to_cpu(file_sec->word1)); + section_id = NVM_WORD2_ID(le16_to_cpu(file_sec->word2)); + } else { + section_size = 2 * EXT_NVM_WORD2_LEN( + le16_to_cpu(file_sec->word2)); + section_id = EXT_NVM_WORD1_ID( + le16_to_cpu(file_sec->word1)); + } + + if (section_size > max_section_size) { + IWL_ERR(trans, "ERROR - section too large (%d)\n", + section_size); + ret = -EINVAL; + break; + } + + if (!section_size) { + IWL_ERR(trans, "ERROR - section empty\n"); + ret = -EINVAL; + break; + } + + if (file_sec->data + section_size > eof) { + IWL_ERR(trans, + "ERROR - NVM file too short for section (%d bytes)\n", + section_size); + ret = -EINVAL; + break; + } + + if (WARN(section_id >= NVM_MAX_NUM_SECTIONS, + "Invalid NVM section ID %d\n", section_id)) { + ret = -EINVAL; + break; + } + + temp = kmemdup(file_sec->data, section_size, GFP_KERNEL); + if (!temp) { + ret = -ENOMEM; + break; + } + + iwl_nvm_fixups(trans->hw_id, section_id, temp, section_size); + + kfree(nvm_sections[section_id].data); + nvm_sections[section_id].data = temp; + nvm_sections[section_id].length = section_size; + + /* advance to the next section */ + file_sec = (const void *)(file_sec->data + section_size); + } +out: + release_firmware(fw_entry); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_read_external_nvm); + +struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans, + const struct iwl_fw *fw) +{ + struct iwl_nvm_get_info cmd = {}; + struct iwl_nvm_data *nvm; + struct iwl_host_cmd hcmd = { + .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + .data = { &cmd, }, + .len = { sizeof(cmd) }, + .id = WIDE_ID(REGULATORY_AND_NVM_GROUP, NVM_GET_INFO) + }; + int ret; + bool empty_otp; + u32 mac_flags; + u32 sbands_flags = 0; + /* + * All the values in iwl_nvm_get_info_rsp v4 are the same as + * in v3, except for the channel profile part of the + * regulatory. So we can just access the new struct, with the + * exception of the latter. + */ + struct iwl_nvm_get_info_rsp *rsp; + struct iwl_nvm_get_info_rsp_v3 *rsp_v3; + bool v4 = fw_has_api(&fw->ucode_capa, + IWL_UCODE_TLV_API_REGULATORY_NVM_INFO); + size_t rsp_size = v4 ? sizeof(*rsp) : sizeof(*rsp_v3); + void *channel_profile; + + ret = iwl_trans_send_cmd(trans, &hcmd); + if (ret) + return ERR_PTR(ret); + + if (WARN(iwl_rx_packet_payload_len(hcmd.resp_pkt) != rsp_size, + "Invalid payload len in NVM response from FW %d", + iwl_rx_packet_payload_len(hcmd.resp_pkt))) { + ret = -EINVAL; + goto out; + } + + rsp = (void *)hcmd.resp_pkt->data; + empty_otp = !!(le32_to_cpu(rsp->general.flags) & + NVM_GENERAL_FLAGS_EMPTY_OTP); + if (empty_otp) + IWL_INFO(trans, "OTP is empty\n"); + + nvm = kzalloc(struct_size(nvm, channels, IWL_NUM_CHANNELS), GFP_KERNEL); + if (!nvm) { + ret = -ENOMEM; + goto out; + } + + iwl_set_hw_address_from_csr(trans, nvm); + /* TODO: if platform NVM has MAC address - override it here */ + + if (!is_valid_ether_addr(nvm->hw_addr)) { + IWL_ERR(trans, "no valid mac address was found\n"); + ret = -EINVAL; + goto err_free; + } + +#if defined(__linux__) + IWL_INFO(trans, "base HW address: %pM\n", nvm->hw_addr); +#elif defined(__FreeBSD__) + IWL_INFO(trans, "base HW address: %6D\n", nvm->hw_addr, ":"); +#endif + + /* Initialize general data */ + nvm->nvm_version = le16_to_cpu(rsp->general.nvm_version); + nvm->n_hw_addrs = rsp->general.n_hw_addrs; + if (nvm->n_hw_addrs == 0) + IWL_WARN(trans, + "Firmware declares no reserved mac addresses. OTP is empty: %d\n", + empty_otp); + + /* Initialize MAC sku data */ + mac_flags = le32_to_cpu(rsp->mac_sku.mac_sku_flags); + nvm->sku_cap_11ac_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_802_11AC_ENABLED); + nvm->sku_cap_11n_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_802_11N_ENABLED); + nvm->sku_cap_11ax_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_802_11AX_ENABLED); + nvm->sku_cap_band_24ghz_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_BAND_2_4_ENABLED); + nvm->sku_cap_band_52ghz_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_BAND_5_2_ENABLED); + nvm->sku_cap_mimo_disabled = + !!(mac_flags & NVM_MAC_SKU_FLAGS_MIMO_DISABLED); + + /* Initialize PHY sku data */ + nvm->valid_tx_ant = (u8)le32_to_cpu(rsp->phy_sku.tx_chains); + nvm->valid_rx_ant = (u8)le32_to_cpu(rsp->phy_sku.rx_chains); + + if (le32_to_cpu(rsp->regulatory.lar_enabled) && + fw_has_capa(&fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT)) { + nvm->lar_enabled = true; + sbands_flags |= IWL_NVM_SBANDS_FLAGS_LAR; + } + + rsp_v3 = (void *)rsp; + channel_profile = v4 ? (void *)rsp->regulatory.channel_profile : + (void *)rsp_v3->regulatory.channel_profile; + + iwl_init_sbands(trans, nvm, + channel_profile, + nvm->valid_tx_ant & fw->valid_tx_ant, + nvm->valid_rx_ant & fw->valid_rx_ant, + sbands_flags, v4, fw); + + iwl_free_resp(&hcmd); + return nvm; + +err_free: + kfree(nvm); +out: + iwl_free_resp(&hcmd); + return ERR_PTR(ret); +} +IWL_EXPORT_SYMBOL(iwl_get_nvm); diff --git a/sys/contrib/dev/iwlwifi/iwl-nvm-parse.h b/sys/contrib/dev/iwlwifi/iwl-nvm-parse.h new file mode 100644 index 00000000000..e1f5a974185 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-nvm-parse.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2015, 2018-2020 Intel Corporation + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_nvm_parse_h__ +#define __iwl_nvm_parse_h__ + +#include +#include "iwl-eeprom-parse.h" + +/** + * enum iwl_nvm_sbands_flags - modification flags for the channel profiles + * + * @IWL_NVM_SBANDS_FLAGS_LAR: LAR is enabled + * @IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ: disallow 40, 80 and 160MHz on 5GHz + */ +enum iwl_nvm_sbands_flags { + IWL_NVM_SBANDS_FLAGS_LAR = BIT(0), + IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ = BIT(1), +}; + +/** + * iwl_parse_nvm_data - parse NVM data and return values + * + * This function parses all NVM values we need and then + * returns a (newly allocated) struct containing all the + * relevant values for driver use. The struct must be freed + * later with iwl_free_nvm_data(). + */ +struct iwl_nvm_data * +iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const struct iwl_fw *fw, + const __be16 *nvm_hw, const __le16 *nvm_sw, + const __le16 *nvm_calib, const __le16 *regulatory, + const __le16 *mac_override, const __le16 *phy_sku, + u8 tx_chains, u8 rx_chains); + +/** + * iwl_parse_mcc_info - parse MCC (mobile country code) info coming from FW + * + * This function parses the regulatory channel data received as a + * MCC_UPDATE_CMD command. It returns a newly allocation regulatory domain, + * to be fed into the regulatory core. In case the geo_info is set handle + * accordingly. An ERR_PTR is returned on error. + * If not given to the regulatory core, the user is responsible for freeing + * the regdomain returned here with kfree. + */ +struct ieee80211_regdomain * +iwl_parse_nvm_mcc_info(struct device *dev, const struct iwl_cfg *cfg, + int num_of_ch, __le32 *channels, u16 fw_mcc, + u16 geo_info, u16 cap, u8 resp_ver); + +/** + * struct iwl_nvm_section - describes an NVM section in memory. + * + * This struct holds an NVM section read from the NIC using NVM_ACCESS_CMD, + * and saved for later use by the driver. Not all NVM sections are saved + * this way, only the needed ones. + */ +struct iwl_nvm_section { + u16 length; + const u8 *data; +}; + +/** + * iwl_read_external_nvm - Reads external NVM from a file into nvm_sections + */ +int iwl_read_external_nvm(struct iwl_trans *trans, + const char *nvm_file_name, + struct iwl_nvm_section *nvm_sections); +void iwl_nvm_fixups(u32 hw_id, unsigned int section, u8 *data, + unsigned int len); + +/** + * iwl_get_nvm - retrieve NVM data from firmware + * + * Allocates a new iwl_nvm_data structure, fills it with + * NVM data, and returns it to caller. + */ +struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans, + const struct iwl_fw *fw); + +#endif /* __iwl_nvm_parse_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-op-mode.h b/sys/contrib/dev/iwlwifi/iwl-op-mode.h new file mode 100644 index 00000000000..af5f9b210f2 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-op-mode.h @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015 Intel Deutschland GmbH + */ +#ifndef __iwl_op_mode_h__ +#define __iwl_op_mode_h__ + +#include +#include +#include "iwl-dbg-tlv.h" + +struct iwl_op_mode; +struct iwl_trans; +struct sk_buff; +struct iwl_device_cmd; +struct iwl_rx_cmd_buffer; +struct iwl_fw; +struct iwl_cfg; + +/** + * DOC: Operational mode - what is it ? + * + * The operational mode (a.k.a. op_mode) is the layer that implements + * mac80211's handlers. It knows two APIs: mac80211's and the fw's. It uses + * the transport API to access the HW. The op_mode doesn't need to know how the + * underlying HW works, since the transport layer takes care of that. + * + * There can be several op_mode: i.e. different fw APIs will require two + * different op_modes. This is why the op_mode is virtualized. + */ + +/** + * DOC: Life cycle of the Operational mode + * + * The operational mode has a very simple life cycle. + * + * 1) The driver layer (iwl-drv.c) chooses the op_mode based on the + * capabilities advertised by the fw file (in TLV format). + * 2) The driver layer starts the op_mode (ops->start) + * 3) The op_mode registers mac80211 + * 4) The op_mode is governed by mac80211 + * 5) The driver layer stops the op_mode + */ + +/** + * struct iwl_op_mode_ops - op_mode specific operations + * + * The op_mode exports its ops so that external components can start it and + * interact with it. The driver layer typically calls the start and stop + * handlers, the transport layer calls the others. + * + * All the handlers MUST be implemented, except @rx_rss which can be left + * out *iff* the opmode will never run on hardware with multi-queue capability. + * + * @start: start the op_mode. The transport layer is already allocated. + * May sleep + * @stop: stop the op_mode. Must free all the memory allocated. + * May sleep + * @rx: Rx notification to the op_mode. rxb is the Rx buffer itself. Cmd is the + * HCMD this Rx responds to. Can't sleep. + * @rx_rss: data queue RX notification to the op_mode, for (data) notifications + * received on the RSS queue(s). The queue parameter indicates which of the + * RSS queues received this frame; it will always be non-zero. + * This method must not sleep. + * @async_cb: called when an ASYNC command with CMD_WANT_ASYNC_CALLBACK set + * completes. Must be atomic. + * @queue_full: notifies that a HW queue is full. + * Must be atomic and called with BH disabled. + * @queue_not_full: notifies that a HW queue is not full any more. + * Must be atomic and called with BH disabled. + * @hw_rf_kill:notifies of a change in the HW rf kill switch. True means that + * the radio is killed. Return %true if the device should be stopped by + * the transport immediately after the call. May sleep. + * @free_skb: allows the transport layer to free skbs that haven't been + * reclaimed by the op_mode. This can happen when the driver is freed and + * there are Tx packets pending in the transport layer. + * Must be atomic + * @nic_error: error notification. Must be atomic and must be called with BH + * disabled, unless the sync parameter is true. + * @cmd_queue_full: Called when the command queue gets full. Must be atomic and + * called with BH disabled. + * @nic_config: configure NIC, called before firmware is started. + * May sleep + * @wimax_active: invoked when WiMax becomes active. May sleep + * @time_point: called when transport layer wants to collect debug data + */ +struct iwl_op_mode_ops { + struct iwl_op_mode *(*start)(struct iwl_trans *trans, + const struct iwl_cfg *cfg, + const struct iwl_fw *fw, + struct dentry *dbgfs_dir); + void (*stop)(struct iwl_op_mode *op_mode); + void (*rx)(struct iwl_op_mode *op_mode, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb); + void (*rx_rss)(struct iwl_op_mode *op_mode, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, unsigned int queue); + void (*async_cb)(struct iwl_op_mode *op_mode, + const struct iwl_device_cmd *cmd); + void (*queue_full)(struct iwl_op_mode *op_mode, int queue); + void (*queue_not_full)(struct iwl_op_mode *op_mode, int queue); + bool (*hw_rf_kill)(struct iwl_op_mode *op_mode, bool state); + void (*free_skb)(struct iwl_op_mode *op_mode, struct sk_buff *skb); + void (*nic_error)(struct iwl_op_mode *op_mode, bool sync); + void (*cmd_queue_full)(struct iwl_op_mode *op_mode); + void (*nic_config)(struct iwl_op_mode *op_mode); + void (*wimax_active)(struct iwl_op_mode *op_mode); + void (*time_point)(struct iwl_op_mode *op_mode, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data); +}; + +int iwl_opmode_register(const char *name, const struct iwl_op_mode_ops *ops); +void iwl_opmode_deregister(const char *name); + +/** + * struct iwl_op_mode - operational mode + * @ops: pointer to its own ops + * + * This holds an implementation of the mac80211 / fw API. + */ +struct iwl_op_mode { + const struct iwl_op_mode_ops *ops; + + char op_mode_specific[] __aligned(sizeof(void *)); +}; + +static inline void iwl_op_mode_stop(struct iwl_op_mode *op_mode) +{ + might_sleep(); + op_mode->ops->stop(op_mode); +} + +static inline void iwl_op_mode_rx(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb) +{ + return op_mode->ops->rx(op_mode, napi, rxb); +} + +static inline void iwl_op_mode_rx_rss(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, + unsigned int queue) +{ + op_mode->ops->rx_rss(op_mode, napi, rxb, queue); +} + +static inline void iwl_op_mode_async_cb(struct iwl_op_mode *op_mode, + const struct iwl_device_cmd *cmd) +{ + if (op_mode->ops->async_cb) + op_mode->ops->async_cb(op_mode, cmd); +} + +static inline void iwl_op_mode_queue_full(struct iwl_op_mode *op_mode, + int queue) +{ + op_mode->ops->queue_full(op_mode, queue); +} + +static inline void iwl_op_mode_queue_not_full(struct iwl_op_mode *op_mode, + int queue) +{ + op_mode->ops->queue_not_full(op_mode, queue); +} + +static inline bool __must_check +iwl_op_mode_hw_rf_kill(struct iwl_op_mode *op_mode, bool state) +{ + might_sleep(); + return op_mode->ops->hw_rf_kill(op_mode, state); +} + +static inline void iwl_op_mode_free_skb(struct iwl_op_mode *op_mode, + struct sk_buff *skb) +{ + if (WARN_ON_ONCE(!op_mode)) + return; + op_mode->ops->free_skb(op_mode, skb); +} + +static inline void iwl_op_mode_nic_error(struct iwl_op_mode *op_mode, bool sync) +{ + op_mode->ops->nic_error(op_mode, sync); +} + +static inline void iwl_op_mode_cmd_queue_full(struct iwl_op_mode *op_mode) +{ + op_mode->ops->cmd_queue_full(op_mode); +} + +static inline void iwl_op_mode_nic_config(struct iwl_op_mode *op_mode) +{ + might_sleep(); + op_mode->ops->nic_config(op_mode); +} + +static inline void iwl_op_mode_wimax_active(struct iwl_op_mode *op_mode) +{ + might_sleep(); + op_mode->ops->wimax_active(op_mode); +} + +static inline void iwl_op_mode_time_point(struct iwl_op_mode *op_mode, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data) +{ + if (!op_mode || !op_mode->ops || !op_mode->ops->time_point) + return; + op_mode->ops->time_point(op_mode, tp_id, tp_data); +} + +#endif /* __iwl_op_mode_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-phy-db.c b/sys/contrib/dev/iwlwifi/iwl-phy-db.c new file mode 100644 index 00000000000..5378315d017 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-phy-db.c @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2020 Intel Corporation + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#include +#include +#include + +#include "iwl-drv.h" +#include "iwl-phy-db.h" +#include "iwl-debug.h" +#include "iwl-op-mode.h" +#include "iwl-trans.h" + +#define CHANNEL_NUM_SIZE 4 /* num of channels in calib_ch size */ + +struct iwl_phy_db_entry { + u16 size; + u8 *data; +}; + +/** + * struct iwl_phy_db - stores phy configuration and calibration data. + * + * @cfg: phy configuration. + * @calib_nch: non channel specific calibration data. + * @n_group_papd: number of entries in papd channel group. + * @calib_ch_group_papd: calibration data related to papd channel group. + * @n_group_txp: number of entries in tx power channel group. + * @calib_ch_group_txp: calibration data related to tx power chanel group. + * @trans: transport layer + */ +struct iwl_phy_db { + struct iwl_phy_db_entry cfg; + struct iwl_phy_db_entry calib_nch; + int n_group_papd; + struct iwl_phy_db_entry *calib_ch_group_papd; + int n_group_txp; + struct iwl_phy_db_entry *calib_ch_group_txp; + + struct iwl_trans *trans; +}; + +enum iwl_phy_db_section_type { + IWL_PHY_DB_CFG = 1, + IWL_PHY_DB_CALIB_NCH, + IWL_PHY_DB_UNUSED, + IWL_PHY_DB_CALIB_CHG_PAPD, + IWL_PHY_DB_CALIB_CHG_TXP, + IWL_PHY_DB_MAX +}; + +#define PHY_DB_CMD 0x6c + +/* for parsing of tx power channel group data that comes from the firmware*/ +struct iwl_phy_db_chg_txp { + __le32 space; + __le16 max_channel_idx; +} __packed; + +struct iwl_phy_db *iwl_phy_db_init(struct iwl_trans *trans) +{ + struct iwl_phy_db *phy_db = kzalloc(sizeof(struct iwl_phy_db), + GFP_KERNEL); + + if (!phy_db) + return phy_db; + + phy_db->trans = trans; + + phy_db->n_group_txp = -1; + phy_db->n_group_papd = -1; + + /* TODO: add default values of the phy db. */ + return phy_db; +} +IWL_EXPORT_SYMBOL(iwl_phy_db_init); + +/* + * get phy db section: returns a pointer to a phy db section specified by + * type and channel group id. + */ +static struct iwl_phy_db_entry * +iwl_phy_db_get_section(struct iwl_phy_db *phy_db, + enum iwl_phy_db_section_type type, + u16 chg_id) +{ + if (!phy_db || type >= IWL_PHY_DB_MAX) + return NULL; + + switch (type) { + case IWL_PHY_DB_CFG: + return &phy_db->cfg; + case IWL_PHY_DB_CALIB_NCH: + return &phy_db->calib_nch; + case IWL_PHY_DB_CALIB_CHG_PAPD: + if (chg_id >= phy_db->n_group_papd) + return NULL; + return &phy_db->calib_ch_group_papd[chg_id]; + case IWL_PHY_DB_CALIB_CHG_TXP: + if (chg_id >= phy_db->n_group_txp) + return NULL; + return &phy_db->calib_ch_group_txp[chg_id]; + default: + return NULL; + } + return NULL; +} + +static void iwl_phy_db_free_section(struct iwl_phy_db *phy_db, + enum iwl_phy_db_section_type type, + u16 chg_id) +{ + struct iwl_phy_db_entry *entry = + iwl_phy_db_get_section(phy_db, type, chg_id); + if (!entry) + return; + + kfree(entry->data); + entry->data = NULL; + entry->size = 0; +} + +void iwl_phy_db_free(struct iwl_phy_db *phy_db) +{ + int i; + + if (!phy_db) + return; + + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CFG, 0); + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_NCH, 0); + + for (i = 0; i < phy_db->n_group_papd; i++) + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_PAPD, i); + kfree(phy_db->calib_ch_group_papd); + + for (i = 0; i < phy_db->n_group_txp; i++) + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_TXP, i); + kfree(phy_db->calib_ch_group_txp); + + kfree(phy_db); +} +IWL_EXPORT_SYMBOL(iwl_phy_db_free); + +int iwl_phy_db_set_section(struct iwl_phy_db *phy_db, + struct iwl_rx_packet *pkt) +{ + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_calib_res_notif_phy_db *phy_db_notif = + (struct iwl_calib_res_notif_phy_db *)pkt->data; + enum iwl_phy_db_section_type type; + u16 size; + struct iwl_phy_db_entry *entry; + u16 chg_id = 0; + + if (pkt_len < sizeof(*phy_db_notif)) + return -EINVAL; + + type = le16_to_cpu(phy_db_notif->type); + size = le16_to_cpu(phy_db_notif->length); + + if (pkt_len < sizeof(*phy_db_notif) + size) + return -EINVAL; + + if (!phy_db) + return -EINVAL; + + if (type == IWL_PHY_DB_CALIB_CHG_PAPD) { + chg_id = le16_to_cpup((__le16 *)phy_db_notif->data); + if (phy_db && !phy_db->calib_ch_group_papd) { + /* + * Firmware sends the largest index first, so we can use + * it to know how much we should allocate. + */ + phy_db->calib_ch_group_papd = kcalloc(chg_id + 1, + sizeof(struct iwl_phy_db_entry), + GFP_ATOMIC); + if (!phy_db->calib_ch_group_papd) + return -ENOMEM; + phy_db->n_group_papd = chg_id + 1; + } + } else if (type == IWL_PHY_DB_CALIB_CHG_TXP) { + chg_id = le16_to_cpup((__le16 *)phy_db_notif->data); + if (phy_db && !phy_db->calib_ch_group_txp) { + /* + * Firmware sends the largest index first, so we can use + * it to know how much we should allocate. + */ + phy_db->calib_ch_group_txp = kcalloc(chg_id + 1, + sizeof(struct iwl_phy_db_entry), + GFP_ATOMIC); + if (!phy_db->calib_ch_group_txp) + return -ENOMEM; + phy_db->n_group_txp = chg_id + 1; + } + } + + entry = iwl_phy_db_get_section(phy_db, type, chg_id); + if (!entry) + return -EINVAL; + + kfree(entry->data); + entry->data = kmemdup(phy_db_notif->data, size, GFP_ATOMIC); + if (!entry->data) { + entry->size = 0; + return -ENOMEM; + } + + entry->size = size; + + IWL_DEBUG_INFO(phy_db->trans, + "%s(%d): [PHYDB]SET: Type %d , Size: %d\n", + __func__, __LINE__, type, size); + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_phy_db_set_section); + +static int is_valid_channel(u16 ch_id) +{ + if (ch_id <= 14 || + (36 <= ch_id && ch_id <= 64 && ch_id % 4 == 0) || + (100 <= ch_id && ch_id <= 140 && ch_id % 4 == 0) || + (145 <= ch_id && ch_id <= 165 && ch_id % 4 == 1)) + return 1; + return 0; +} + +static u8 ch_id_to_ch_index(u16 ch_id) +{ + if (WARN_ON(!is_valid_channel(ch_id))) + return 0xff; + + if (ch_id <= 14) + return ch_id - 1; + if (ch_id <= 64) + return (ch_id + 20) / 4; + if (ch_id <= 140) + return (ch_id - 12) / 4; + return (ch_id - 13) / 4; +} + + +static u16 channel_id_to_papd(u16 ch_id) +{ + if (WARN_ON(!is_valid_channel(ch_id))) + return 0xff; + + if (1 <= ch_id && ch_id <= 14) + return 0; + if (36 <= ch_id && ch_id <= 64) + return 1; + if (100 <= ch_id && ch_id <= 140) + return 2; + return 3; +} + +static u16 channel_id_to_txp(struct iwl_phy_db *phy_db, u16 ch_id) +{ + struct iwl_phy_db_chg_txp *txp_chg; + int i; + u8 ch_index = ch_id_to_ch_index(ch_id); + if (ch_index == 0xff) + return 0xff; + + for (i = 0; i < phy_db->n_group_txp; i++) { + txp_chg = (void *)phy_db->calib_ch_group_txp[i].data; + if (!txp_chg) + return 0xff; + /* + * Looking for the first channel group that its max channel is + * higher then wanted channel. + */ + if (le16_to_cpu(txp_chg->max_channel_idx) >= ch_index) + return i; + } + return 0xff; +} +static +int iwl_phy_db_get_section_data(struct iwl_phy_db *phy_db, + u32 type, u8 **data, u16 *size, u16 ch_id) +{ + struct iwl_phy_db_entry *entry; + u16 ch_group_id = 0; + + if (!phy_db) + return -EINVAL; + + /* find wanted channel group */ + if (type == IWL_PHY_DB_CALIB_CHG_PAPD) + ch_group_id = channel_id_to_papd(ch_id); + else if (type == IWL_PHY_DB_CALIB_CHG_TXP) + ch_group_id = channel_id_to_txp(phy_db, ch_id); + + entry = iwl_phy_db_get_section(phy_db, type, ch_group_id); + if (!entry) + return -EINVAL; + + *data = entry->data; + *size = entry->size; + + IWL_DEBUG_INFO(phy_db->trans, + "%s(%d): [PHYDB] GET: Type %d , Size: %d\n", + __func__, __LINE__, type, *size); + + return 0; +} + +static int iwl_send_phy_db_cmd(struct iwl_phy_db *phy_db, u16 type, + u16 length, void *data) +{ + struct iwl_phy_db_cmd phy_db_cmd; + struct iwl_host_cmd cmd = { + .id = PHY_DB_CMD, + }; + + IWL_DEBUG_INFO(phy_db->trans, + "Sending PHY-DB hcmd of type %d, of length %d\n", + type, length); + + /* Set phy db cmd variables */ + phy_db_cmd.type = cpu_to_le16(type); + phy_db_cmd.length = cpu_to_le16(length); + + /* Set hcmd variables */ + cmd.data[0] = &phy_db_cmd; + cmd.len[0] = sizeof(struct iwl_phy_db_cmd); + cmd.data[1] = data; + cmd.len[1] = length; + cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY; + + return iwl_trans_send_cmd(phy_db->trans, &cmd); +} + +static int iwl_phy_db_send_all_channel_groups( + struct iwl_phy_db *phy_db, + enum iwl_phy_db_section_type type, + u8 max_ch_groups) +{ + u16 i; + int err; + struct iwl_phy_db_entry *entry; + + /* Send all the channel specific groups to operational fw */ + for (i = 0; i < max_ch_groups; i++) { + entry = iwl_phy_db_get_section(phy_db, + type, + i); + if (!entry) + return -EINVAL; + + if (!entry->size) + continue; + + /* Send the requested PHY DB section */ + err = iwl_send_phy_db_cmd(phy_db, + type, + entry->size, + entry->data); + if (err) { + IWL_ERR(phy_db->trans, + "Can't SEND phy_db section %d (%d), err %d\n", + type, i, err); + return err; + } + + IWL_DEBUG_INFO(phy_db->trans, + "Sent PHY_DB HCMD, type = %d num = %d\n", + type, i); + } + + return 0; +} + +int iwl_send_phy_db_data(struct iwl_phy_db *phy_db) +{ + u8 *data = NULL; + u16 size = 0; + int err; + + IWL_DEBUG_INFO(phy_db->trans, + "Sending phy db data and configuration to runtime image\n"); + + /* Send PHY DB CFG section */ + err = iwl_phy_db_get_section_data(phy_db, IWL_PHY_DB_CFG, + &data, &size, 0); + if (err) { + IWL_ERR(phy_db->trans, "Cannot get Phy DB cfg section\n"); + return err; + } + + err = iwl_send_phy_db_cmd(phy_db, IWL_PHY_DB_CFG, size, data); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send HCMD of Phy DB cfg section\n"); + return err; + } + + err = iwl_phy_db_get_section_data(phy_db, IWL_PHY_DB_CALIB_NCH, + &data, &size, 0); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot get Phy DB non specific channel section\n"); + return err; + } + + err = iwl_send_phy_db_cmd(phy_db, IWL_PHY_DB_CALIB_NCH, size, data); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send HCMD of Phy DB non specific channel section\n"); + return err; + } + + /* Send all the TXP channel specific data */ + err = iwl_phy_db_send_all_channel_groups(phy_db, + IWL_PHY_DB_CALIB_CHG_PAPD, + phy_db->n_group_papd); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send channel specific PAPD groups\n"); + return err; + } + + /* Send all the TXP channel specific data */ + err = iwl_phy_db_send_all_channel_groups(phy_db, + IWL_PHY_DB_CALIB_CHG_TXP, + phy_db->n_group_txp); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send channel specific TX power groups\n"); + return err; + } + + IWL_DEBUG_INFO(phy_db->trans, + "Finished sending phy db non channel data\n"); + return 0; +} +IWL_EXPORT_SYMBOL(iwl_send_phy_db_data); diff --git a/sys/contrib/dev/iwlwifi/iwl-phy-db.h b/sys/contrib/dev/iwlwifi/iwl-phy-db.h new file mode 100644 index 00000000000..0b2fdfa94fc --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-phy-db.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + */ +#ifndef __IWL_PHYDB_H__ +#define __IWL_PHYDB_H__ + +#include + +#include "iwl-op-mode.h" +#include "iwl-trans.h" + +struct iwl_phy_db *iwl_phy_db_init(struct iwl_trans *trans); + +void iwl_phy_db_free(struct iwl_phy_db *phy_db); + +int iwl_phy_db_set_section(struct iwl_phy_db *phy_db, + struct iwl_rx_packet *pkt); + + +int iwl_send_phy_db_data(struct iwl_phy_db *phy_db); + +#endif /* __IWL_PHYDB_H__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-prph.h b/sys/contrib/dev/iwlwifi/iwl-prph.h new file mode 100644 index 00000000000..a84ab02cf9d --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-prph.h @@ -0,0 +1,489 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#ifndef __iwl_prph_h__ +#define __iwl_prph_h__ +#include + +/* + * Registers in this file are internal, not PCI bus memory mapped. + * Driver accesses these via HBUS_TARG_PRPH_* registers. + */ +#define PRPH_BASE (0x00000) +#define PRPH_END (0xFFFFF) + +/* APMG (power management) constants */ +#define APMG_BASE (PRPH_BASE + 0x3000) +#define APMG_CLK_CTRL_REG (APMG_BASE + 0x0000) +#define APMG_CLK_EN_REG (APMG_BASE + 0x0004) +#define APMG_CLK_DIS_REG (APMG_BASE + 0x0008) +#define APMG_PS_CTRL_REG (APMG_BASE + 0x000c) +#define APMG_PCIDEV_STT_REG (APMG_BASE + 0x0010) +#define APMG_RFKILL_REG (APMG_BASE + 0x0014) +#define APMG_RTC_INT_STT_REG (APMG_BASE + 0x001c) +#define APMG_RTC_INT_MSK_REG (APMG_BASE + 0x0020) +#define APMG_DIGITAL_SVR_REG (APMG_BASE + 0x0058) +#define APMG_ANALOG_SVR_REG (APMG_BASE + 0x006C) + +#define APMS_CLK_VAL_MRB_FUNC_MODE (0x00000001) +#define APMG_CLK_VAL_DMA_CLK_RQT (0x00000200) +#define APMG_CLK_VAL_BSM_CLK_RQT (0x00000800) + +#define APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS (0x00400000) +#define APMG_PS_CTRL_VAL_RESET_REQ (0x04000000) +#define APMG_PS_CTRL_MSK_PWR_SRC (0x03000000) +#define APMG_PS_CTRL_VAL_PWR_SRC_VMAIN (0x00000000) +#define APMG_PS_CTRL_VAL_PWR_SRC_VAUX (0x02000000) +#define APMG_SVR_VOLTAGE_CONFIG_BIT_MSK (0x000001E0) /* bit 8:5 */ +#define APMG_SVR_DIGITAL_VOLTAGE_1_32 (0x00000060) + +#define APMG_PCIDEV_STT_VAL_PERSIST_DIS (0x00000200) +#define APMG_PCIDEV_STT_VAL_L1_ACT_DIS (0x00000800) +#define APMG_PCIDEV_STT_VAL_WAKE_ME (0x00004000) + +#define APMG_RTC_INT_STT_RFKILL (0x10000000) + +/* Device system time */ +#define DEVICE_SYSTEM_TIME_REG 0xA0206C + +/* Device NMI register and value for 8000 family and lower hw's */ +#define DEVICE_SET_NMI_REG 0x00a01c30 +#define DEVICE_SET_NMI_VAL_DRV BIT(7) +/* Device NMI register and value for 9000 family and above hw's */ +#define UREG_NIC_SET_NMI_DRIVER 0x00a05c10 +#define UREG_NIC_SET_NMI_DRIVER_NMI_FROM_DRIVER BIT(24) +#define UREG_NIC_SET_NMI_DRIVER_RESET_HANDSHAKE (BIT(24) | BIT(25)) + +/* Shared registers (0x0..0x3ff, via target indirect or periphery */ +#define SHR_BASE 0x00a10000 + +/* Shared GP1 register */ +#define SHR_APMG_GP1_REG 0x01dc +#define SHR_APMG_GP1_REG_PRPH (SHR_BASE + SHR_APMG_GP1_REG) +#define SHR_APMG_GP1_WF_XTAL_LP_EN 0x00000004 +#define SHR_APMG_GP1_CHICKEN_BIT_SELECT 0x80000000 + +/* Shared DL_CFG register */ +#define SHR_APMG_DL_CFG_REG 0x01c4 +#define SHR_APMG_DL_CFG_REG_PRPH (SHR_BASE + SHR_APMG_DL_CFG_REG) +#define SHR_APMG_DL_CFG_RTCS_CLK_SELECTOR_MSK 0x000000c0 +#define SHR_APMG_DL_CFG_RTCS_CLK_INTERNAL_XTAL 0x00000080 +#define SHR_APMG_DL_CFG_DL_CLOCK_POWER_UP 0x00000100 + +/* Shared APMG_XTAL_CFG register */ +#define SHR_APMG_XTAL_CFG_REG 0x1c0 +#define SHR_APMG_XTAL_CFG_XTAL_ON_REQ 0x80000000 + +/* + * Device reset for family 8000 + * write to bit 24 in order to reset the CPU +*/ +#define RELEASE_CPU_RESET (0x300C) +#define RELEASE_CPU_RESET_BIT BIT(24) + +/***************************************************************************** + * 7000/3000 series SHR DTS addresses * + *****************************************************************************/ + +#define SHR_MISC_WFM_DTS_EN (0x00a10024) +#define DTSC_CFG_MODE (0x00a10604) +#define DTSC_VREF_AVG (0x00a10648) +#define DTSC_VREF5_AVG (0x00a1064c) +#define DTSC_CFG_MODE_PERIODIC (0x2) +#define DTSC_PTAT_AVG (0x00a10650) + + +/** + * Tx Scheduler + * + * The Tx Scheduler selects the next frame to be transmitted, choosing TFDs + * (Transmit Frame Descriptors) from up to 16 circular Tx queues resident in + * host DRAM. It steers each frame's Tx command (which contains the frame + * data) into one of up to 7 prioritized Tx DMA FIFO channels within the + * device. A queue maps to only one (selectable by driver) Tx DMA channel, + * but one DMA channel may take input from several queues. + * + * Tx DMA FIFOs have dedicated purposes. + * + * For 5000 series and up, they are used differently + * (cf. iwl5000_default_queue_to_tx_fifo in iwl-5000.c): + * + * 0 -- EDCA BK (background) frames, lowest priority + * 1 -- EDCA BE (best effort) frames, normal priority + * 2 -- EDCA VI (video) frames, higher priority + * 3 -- EDCA VO (voice) and management frames, highest priority + * 4 -- unused + * 5 -- unused + * 6 -- unused + * 7 -- Commands + * + * Driver should normally map queues 0-6 to Tx DMA/FIFO channels 0-6. + * In addition, driver can map the remaining queues to Tx DMA/FIFO + * channels 0-3 to support 11n aggregation via EDCA DMA channels. + * + * The driver sets up each queue to work in one of two modes: + * + * 1) Scheduler-Ack, in which the scheduler automatically supports a + * block-ack (BA) window of up to 64 TFDs. In this mode, each queue + * contains TFDs for a unique combination of Recipient Address (RA) + * and Traffic Identifier (TID), that is, traffic of a given + * Quality-Of-Service (QOS) priority, destined for a single station. + * + * In scheduler-ack mode, the scheduler keeps track of the Tx status of + * each frame within the BA window, including whether it's been transmitted, + * and whether it's been acknowledged by the receiving station. The device + * automatically processes block-acks received from the receiving STA, + * and reschedules un-acked frames to be retransmitted (successful + * Tx completion may end up being out-of-order). + * + * The driver must maintain the queue's Byte Count table in host DRAM + * for this mode. + * This mode does not support fragmentation. + * + * 2) FIFO (a.k.a. non-Scheduler-ACK), in which each TFD is processed in order. + * The device may automatically retry Tx, but will retry only one frame + * at a time, until receiving ACK from receiving station, or reaching + * retry limit and giving up. + * + * The command queue (#4/#9) must use this mode! + * This mode does not require use of the Byte Count table in host DRAM. + * + * Driver controls scheduler operation via 3 means: + * 1) Scheduler registers + * 2) Shared scheduler data base in internal SRAM + * 3) Shared data in host DRAM + * + * Initialization: + * + * When loading, driver should allocate memory for: + * 1) 16 TFD circular buffers, each with space for (typically) 256 TFDs. + * 2) 16 Byte Count circular buffers in 16 KBytes contiguous memory + * (1024 bytes for each queue). + * + * After receiving "Alive" response from uCode, driver must initialize + * the scheduler (especially for queue #4/#9, the command queue, otherwise + * the driver can't issue commands!): + */ +#define SCD_MEM_LOWER_BOUND (0x0000) + +/** + * Max Tx window size is the max number of contiguous TFDs that the scheduler + * can keep track of at one time when creating block-ack chains of frames. + * Note that "64" matches the number of ack bits in a block-ack packet. + */ +#define SCD_WIN_SIZE 64 +#define SCD_FRAME_LIMIT 64 + +#define SCD_TXFIFO_POS_TID (0) +#define SCD_TXFIFO_POS_RA (4) +#define SCD_QUEUE_RA_TID_MAP_RATID_MSK (0x01FF) + +/* agn SCD */ +#define SCD_QUEUE_STTS_REG_POS_TXF (0) +#define SCD_QUEUE_STTS_REG_POS_ACTIVE (3) +#define SCD_QUEUE_STTS_REG_POS_WSL (4) +#define SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN (19) +#define SCD_QUEUE_STTS_REG_MSK (0x017F0000) + +#define SCD_QUEUE_CTX_REG1_CREDIT (0x00FFFF00) +#define SCD_QUEUE_CTX_REG1_SUPER_CREDIT (0xFF000000) +#define SCD_QUEUE_CTX_REG1_VAL(_n, _v) FIELD_PREP(SCD_QUEUE_CTX_REG1_ ## _n, _v) + +#define SCD_QUEUE_CTX_REG2_WIN_SIZE (0x0000007F) +#define SCD_QUEUE_CTX_REG2_FRAME_LIMIT (0x007F0000) +#define SCD_QUEUE_CTX_REG2_VAL(_n, _v) FIELD_PREP(SCD_QUEUE_CTX_REG2_ ## _n, _v) + +#define SCD_GP_CTRL_ENABLE_31_QUEUES BIT(0) +#define SCD_GP_CTRL_AUTO_ACTIVE_MODE BIT(18) + +/* Context Data */ +#define SCD_CONTEXT_MEM_LOWER_BOUND (SCD_MEM_LOWER_BOUND + 0x600) +#define SCD_CONTEXT_MEM_UPPER_BOUND (SCD_MEM_LOWER_BOUND + 0x6A0) + +/* Tx status */ +#define SCD_TX_STTS_MEM_LOWER_BOUND (SCD_MEM_LOWER_BOUND + 0x6A0) +#define SCD_TX_STTS_MEM_UPPER_BOUND (SCD_MEM_LOWER_BOUND + 0x7E0) + +/* Translation Data */ +#define SCD_TRANS_TBL_MEM_LOWER_BOUND (SCD_MEM_LOWER_BOUND + 0x7E0) +#define SCD_TRANS_TBL_MEM_UPPER_BOUND (SCD_MEM_LOWER_BOUND + 0x808) + +#define SCD_CONTEXT_QUEUE_OFFSET(x)\ + (SCD_CONTEXT_MEM_LOWER_BOUND + ((x) * 8)) + +#define SCD_TX_STTS_QUEUE_OFFSET(x)\ + (SCD_TX_STTS_MEM_LOWER_BOUND + ((x) * 16)) + +#define SCD_TRANS_TBL_OFFSET_QUEUE(x) \ + ((SCD_TRANS_TBL_MEM_LOWER_BOUND + ((x) * 2)) & 0xfffc) + +#define SCD_BASE (PRPH_BASE + 0xa02c00) + +#define SCD_SRAM_BASE_ADDR (SCD_BASE + 0x0) +#define SCD_DRAM_BASE_ADDR (SCD_BASE + 0x8) +#define SCD_AIT (SCD_BASE + 0x0c) +#define SCD_TXFACT (SCD_BASE + 0x10) +#define SCD_ACTIVE (SCD_BASE + 0x14) +#define SCD_QUEUECHAIN_SEL (SCD_BASE + 0xe8) +#define SCD_CHAINEXT_EN (SCD_BASE + 0x244) +#define SCD_AGGR_SEL (SCD_BASE + 0x248) +#define SCD_INTERRUPT_MASK (SCD_BASE + 0x108) +#define SCD_GP_CTRL (SCD_BASE + 0x1a8) +#define SCD_EN_CTRL (SCD_BASE + 0x254) + +/*********************** END TX SCHEDULER *************************************/ + +/* Oscillator clock */ +#define OSC_CLK (0xa04068) +#define OSC_CLK_FORCE_CONTROL (0x8) + +#define FH_UCODE_LOAD_STATUS (0x1AF0) + +/* + * Replacing FH_UCODE_LOAD_STATUS + * This register is writen by driver and is read by uCode during boot flow. + * Note this address is cleared after MAC reset. + */ +#define UREG_UCODE_LOAD_STATUS (0xa05c40) +#define UREG_CPU_INIT_RUN (0xa05c44) + +#define LMPM_SECURE_UCODE_LOAD_CPU1_HDR_ADDR (0x1E78) +#define LMPM_SECURE_UCODE_LOAD_CPU2_HDR_ADDR (0x1E7C) + +#define LMPM_SECURE_CPU1_HDR_MEM_SPACE (0x420000) +#define LMPM_SECURE_CPU2_HDR_MEM_SPACE (0x420400) + +#define LMAC2_PRPH_OFFSET (0x100000) + +/* Rx FIFO */ +#define RXF_SIZE_ADDR (0xa00c88) +#define RXF_RD_D_SPACE (0xa00c40) +#define RXF_RD_WR_PTR (0xa00c50) +#define RXF_RD_RD_PTR (0xa00c54) +#define RXF_RD_FENCE_PTR (0xa00c4c) +#define RXF_SET_FENCE_MODE (0xa00c14) +#define RXF_LD_WR2FENCE (0xa00c1c) +#define RXF_FIFO_RD_FENCE_INC (0xa00c68) +#define RXF_SIZE_BYTE_CND_POS (7) +#define RXF_SIZE_BYTE_CNT_MSK (0x3ff << RXF_SIZE_BYTE_CND_POS) +#define RXF_DIFF_FROM_PREV (0x200) +#define RXF2C_DIFF_FROM_PREV (0x4e00) + +#define RXF_LD_FENCE_OFFSET_ADDR (0xa00c10) +#define RXF_FIFO_RD_FENCE_ADDR (0xa00c0c) + +/* Tx FIFO */ +#define TXF_FIFO_ITEM_CNT (0xa00438) +#define TXF_WR_PTR (0xa00414) +#define TXF_RD_PTR (0xa00410) +#define TXF_FENCE_PTR (0xa00418) +#define TXF_LOCK_FENCE (0xa00424) +#define TXF_LARC_NUM (0xa0043c) +#define TXF_READ_MODIFY_DATA (0xa00448) +#define TXF_READ_MODIFY_ADDR (0xa0044c) + +/* UMAC Internal Tx Fifo */ +#define TXF_CPU2_FIFO_ITEM_CNT (0xA00538) +#define TXF_CPU2_WR_PTR (0xA00514) +#define TXF_CPU2_RD_PTR (0xA00510) +#define TXF_CPU2_FENCE_PTR (0xA00518) +#define TXF_CPU2_LOCK_FENCE (0xA00524) +#define TXF_CPU2_NUM (0xA0053C) +#define TXF_CPU2_READ_MODIFY_DATA (0xA00548) +#define TXF_CPU2_READ_MODIFY_ADDR (0xA0054C) + +/* Radio registers access */ +#define RSP_RADIO_CMD (0xa02804) +#define RSP_RADIO_RDDAT (0xa02814) +#define RADIO_RSP_ADDR_POS (6) +#define RADIO_RSP_RD_CMD (3) + +/* LTR control (Qu only) */ +#define HPM_MAC_LTR_CSR 0xa0348c +#define HPM_MAC_LRT_ENABLE_ALL 0xf +/* also uses CSR_LTR_* for values */ +#define HPM_UMAC_LTR 0xa03480 + +/* FW monitor */ +#define MON_BUFF_SAMPLE_CTL (0xa03c00) +#define MON_BUFF_BASE_ADDR (0xa03c1c) +#define MON_BUFF_END_ADDR (0xa03c40) +#define MON_BUFF_WRPTR (0xa03c44) +#define MON_BUFF_CYCLE_CNT (0xa03c48) +/* FW monitor family 8000 and on */ +#define MON_BUFF_BASE_ADDR_VER2 (0xa03c1c) +#define MON_BUFF_END_ADDR_VER2 (0xa03c20) +#define MON_BUFF_WRPTR_VER2 (0xa03c24) +#define MON_BUFF_CYCLE_CNT_VER2 (0xa03c28) +#define MON_BUFF_SHIFT_VER2 (0x8) +/* FW monitor familiy AX210 and on */ +#define DBGC_CUR_DBGBUF_BASE_ADDR_LSB (0xd03c20) +#define DBGC_CUR_DBGBUF_BASE_ADDR_MSB (0xd03c24) +#define DBGC_CUR_DBGBUF_STATUS (0xd03c1c) +#define DBGC_DBGBUF_WRAP_AROUND (0xd03c2c) +#define DBGC_CUR_DBGBUF_STATUS_OFFSET_MSK (0x00ffffff) +#define DBGC_CUR_DBGBUF_STATUS_IDX_MSK (0x0f000000) + +#define MON_DMARB_RD_CTL_ADDR (0xa03c60) +#define MON_DMARB_RD_DATA_ADDR (0xa03c5c) + +#define DBGC_IN_SAMPLE (0xa03c00) +#define DBGC_OUT_CTRL (0xa03c0c) + +/* M2S registers */ +#define LDBG_M2S_BUF_WPTR (0xa0476c) +#define LDBG_M2S_BUF_WRAP_CNT (0xa04774) +#define LDBG_M2S_BUF_WPTR_VAL_MSK (0x000fffff) +#define LDBG_M2S_BUF_WRAP_CNT_VAL_MSK (0x000fffff) + +/* enable the ID buf for read */ +#define WFPM_PS_CTL_CLR 0xA0300C +#define WFMP_MAC_ADDR_0 0xA03080 +#define WFMP_MAC_ADDR_1 0xA03084 +#define LMPM_PMG_EN 0xA01CEC +#define RADIO_REG_SYS_MANUAL_DFT_0 0xAD4078 +#define RFIC_REG_RD 0xAD0470 +#define WFPM_CTRL_REG 0xA03030 +#define WFPM_CTRL_REG_GEN2 0xd03030 +#define WFPM_OTP_CFG1_ADDR 0x00a03098 +#define WFPM_OTP_CFG1_ADDR_GEN2 0x00d03098 +#define WFPM_OTP_CFG1_IS_JACKET_BIT BIT(4) +#define WFPM_OTP_CFG1_IS_CDB_BIT BIT(5) + +#define WFPM_GP2 0xA030B4 + +/* DBGI SRAM Register details */ +#define DBGI_SRAM_TARGET_ACCESS_CFG 0x00A2E14C +#define DBGI_SRAM_TARGET_ACCESS_CFG_RESET_ADDRESS_MSK 0x10000 +#define DBGI_SRAM_TARGET_ACCESS_RDATA_LSB 0x00A2E154 +#define DBGI_SRAM_TARGET_ACCESS_RDATA_MSB 0x00A2E158 + +enum { + ENABLE_WFPM = BIT(31), + WFPM_AUX_CTL_AUX_IF_MAC_OWNER_MSK = 0x80000000, +}; + +#define CNVI_AUX_MISC_CHIP 0xA200B0 +#define CNVR_AUX_MISC_CHIP 0xA2B800 +#define CNVR_SCU_SD_REGS_SD_REG_DIG_DCDC_VTRIM 0xA29890 +#define CNVR_SCU_SD_REGS_SD_REG_ACTIVE_VDIG_MIRROR 0xA29938 + +#define PREG_AUX_BUS_WPROT_0 0xA04CC0 + +/* device family 9000 WPROT register */ +#define PREG_PRPH_WPROT_9000 0xA04CE0 +/* device family 22000 WPROT register */ +#define PREG_PRPH_WPROT_22000 0xA04D00 + +#define SB_MODIFY_CFG_FLAG 0xA03088 +#define SB_CPU_1_STATUS 0xA01E30 +#define SB_CPU_2_STATUS 0xA01E34 +#define UMAG_SB_CPU_1_STATUS 0xA038C0 +#define UMAG_SB_CPU_2_STATUS 0xA038C4 +#define UMAG_GEN_HW_STATUS 0xA038C8 +#define UREG_UMAC_CURRENT_PC 0xa05c18 +#define UREG_LMAC1_CURRENT_PC 0xa05c1c +#define UREG_LMAC2_CURRENT_PC 0xa05c20 + +/* For UMAG_GEN_HW_STATUS reg check */ +enum { + UMAG_GEN_HW_IS_FPGA = BIT(1), +}; + +/* FW chicken bits */ +#define LMPM_CHICK 0xA01FF8 +enum { + LMPM_CHICK_EXTENDED_ADDR_SPACE = BIT(0), +}; + +/* FW chicken bits */ +#define LMPM_PAGE_PASS_NOTIF 0xA03824 +enum { + LMPM_PAGE_PASS_NOTIF_POS = BIT(20), +}; + +/* + * CRF ID register + * + * type: bits 0-11 + * reserved: bits 12-18 + * slave_exist: bit 19 + * dash: bits 20-23 + * step: bits 24-26 + * flavor: bits 27-31 + */ +#define REG_CRF_ID_TYPE(val) (((val) & 0x00000FFF) >> 0) +#define REG_CRF_ID_SLAVE(val) (((val) & 0x00080000) >> 19) +#define REG_CRF_ID_DASH(val) (((val) & 0x00F00000) >> 20) +#define REG_CRF_ID_STEP(val) (((val) & 0x07000000) >> 24) +#define REG_CRF_ID_FLAVOR(val) (((val) & 0xF8000000) >> 27) + +#define UREG_CHICK (0xA05C00) +#define UREG_CHICK_MSI_ENABLE BIT(24) +#define UREG_CHICK_MSIX_ENABLE BIT(25) + +#define SD_REG_VER 0xa29600 +#define SD_REG_VER_GEN2 0x00a2b800 + +#define REG_CRF_ID_TYPE_JF_1 0x201 +#define REG_CRF_ID_TYPE_JF_2 0x202 +#define REG_CRF_ID_TYPE_HR_CDB 0x503 +#define REG_CRF_ID_TYPE_HR_NONE_CDB 0x504 +#define REG_CRF_ID_TYPE_HR_NONE_CDB_1X1 0x501 +#define REG_CRF_ID_TYPE_HR_NONE_CDB_CCP 0x532 +#define REG_CRF_ID_TYPE_GF 0x410 +#define REG_CRF_ID_TYPE_GF_TC 0xF08 +#define REG_CRF_ID_TYPE_MR 0x810 +#define REG_CRF_ID_TYPE_FM 0x910 + +#define HPM_DEBUG 0xA03440 +#define PERSISTENCE_BIT BIT(12) +#define PREG_WFPM_ACCESS BIT(12) + +#define HPM_HIPM_GEN_CFG 0xA03458 +#define HPM_HIPM_GEN_CFG_CR_PG_EN BIT(0) +#define HPM_HIPM_GEN_CFG_CR_SLP_EN BIT(1) +#define HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE BIT(10) + +#define UREG_DOORBELL_TO_ISR6 0xA05C04 +#define UREG_DOORBELL_TO_ISR6_NMI_BIT BIT(0) +#define UREG_DOORBELL_TO_ISR6_RESET_HANDSHAKE (BIT(0) | BIT(1)) +#define UREG_DOORBELL_TO_ISR6_SUSPEND BIT(18) +#define UREG_DOORBELL_TO_ISR6_RESUME BIT(19) +#define UREG_DOORBELL_TO_ISR6_PNVM BIT(20) + +#define CNVI_MBOX_C 0xA3400C + +#define FSEQ_ERROR_CODE 0xA340C8 +#define FSEQ_TOP_INIT_VERSION 0xA34038 +#define FSEQ_CNVIO_INIT_VERSION 0xA3403C +#define FSEQ_OTP_VERSION 0xA340FC +#define FSEQ_TOP_CONTENT_VERSION 0xA340F4 +#define FSEQ_ALIVE_TOKEN 0xA340F0 +#define FSEQ_CNVI_ID 0xA3408C +#define FSEQ_CNVR_ID 0xA34090 + +#define IWL_D3_SLEEP_STATUS_SUSPEND 0xD3 +#define IWL_D3_SLEEP_STATUS_RESUME 0xD0 + +#define WMAL_INDRCT_RD_CMD1_OPMOD_POS 28 +#define WMAL_INDRCT_RD_CMD1_BYTE_ADDRESS_MSK 0xFFFFF +#define WMAL_CMD_READ_BURST_ACCESS 2 +#define WMAL_MRSPF_1 0xADFC20 +#define WMAL_INDRCT_RD_CMD1 0xADFD44 +#define WMAL_INDRCT_CMD1 0xADFC14 +#define WMAL_INDRCT_CMD(addr) \ + ((WMAL_CMD_READ_BURST_ACCESS << WMAL_INDRCT_RD_CMD1_OPMOD_POS) | \ + ((addr) & WMAL_INDRCT_RD_CMD1_BYTE_ADDRESS_MSK)) + +#define WFPM_LMAC1_PS_CTL_RW 0xA03380 +#define WFPM_LMAC2_PS_CTL_RW 0xA033C0 +#define WFPM_PS_CTL_RW_PHYRF_PD_FSM_CURSTATE_MSK 0x0000000F +#define WFPM_PHYRF_STATE_ON 5 +#define HBUS_TIMEOUT 0xA5A5A5A1 +#define WFPM_DPHY_OFF 0xDF10FF + +#endif /* __iwl_prph_h__ */ diff --git a/sys/contrib/dev/iwlwifi/iwl-scd.h b/sys/contrib/dev/iwlwifi/iwl-scd.h new file mode 100644 index 00000000000..1eaff898ca3 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-scd.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2014 Intel Mobile Communications GmbH + */ +#ifndef __iwl_scd_h__ +#define __iwl_scd_h__ + +#include "iwl-trans.h" +#include "iwl-io.h" +#include "iwl-prph.h" + + +static inline void iwl_scd_txq_set_chain(struct iwl_trans *trans, + u16 txq_id) +{ + iwl_set_bits_prph(trans, SCD_QUEUECHAIN_SEL, BIT(txq_id)); +} + +static inline void iwl_scd_txq_enable_agg(struct iwl_trans *trans, + u16 txq_id) +{ + iwl_set_bits_prph(trans, SCD_AGGR_SEL, BIT(txq_id)); +} + +static inline void iwl_scd_txq_disable_agg(struct iwl_trans *trans, + u16 txq_id) +{ + iwl_clear_bits_prph(trans, SCD_AGGR_SEL, BIT(txq_id)); +} + +static inline void iwl_scd_disable_agg(struct iwl_trans *trans) +{ + iwl_set_bits_prph(trans, SCD_AGGR_SEL, 0); +} + +static inline void iwl_scd_activate_fifos(struct iwl_trans *trans) +{ + iwl_write_prph(trans, SCD_TXFACT, IWL_MASK(0, 7)); +} + +static inline void iwl_scd_deactivate_fifos(struct iwl_trans *trans) +{ + iwl_write_prph(trans, SCD_TXFACT, 0); +} + +static inline void iwl_scd_enable_set_active(struct iwl_trans *trans, + u32 value) +{ + iwl_write_prph(trans, SCD_EN_CTRL, value); +} + +static inline unsigned int SCD_QUEUE_WRPTR(unsigned int chnl) +{ + if (chnl < 20) + return SCD_BASE + 0x18 + chnl * 4; + WARN_ON_ONCE(chnl >= 32); + return SCD_BASE + 0x284 + (chnl - 20) * 4; +} + +static inline unsigned int SCD_QUEUE_RDPTR(unsigned int chnl) +{ + if (chnl < 20) + return SCD_BASE + 0x68 + chnl * 4; + WARN_ON_ONCE(chnl >= 32); + return SCD_BASE + 0x2B4 + chnl * 4; +} + +static inline unsigned int SCD_QUEUE_STATUS_BITS(unsigned int chnl) +{ + if (chnl < 20) + return SCD_BASE + 0x10c + chnl * 4; + WARN_ON_ONCE(chnl >= 32); + return SCD_BASE + 0x334 + chnl * 4; +} + +static inline void iwl_scd_txq_set_inactive(struct iwl_trans *trans, + u16 txq_id) +{ + iwl_write_prph(trans, SCD_QUEUE_STATUS_BITS(txq_id), + (0 << SCD_QUEUE_STTS_REG_POS_ACTIVE)| + (1 << SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN)); +} + +#endif diff --git a/sys/contrib/dev/iwlwifi/iwl-trans.c b/sys/contrib/dev/iwlwifi/iwl-trans.c new file mode 100644 index 00000000000..775e86e5b2c --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-trans.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + * Copyright (C) 2019-2021 Intel Corporation + */ +#include +#include + +#include "fw/api/tx.h" +#include "iwl-trans.h" +#include "iwl-drv.h" +#include "iwl-fh.h" +#include "queue/tx.h" +#include +#include "fw/api/commands.h" + +struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, + struct device *dev, + const struct iwl_trans_ops *ops, + const struct iwl_cfg_trans_params *cfg_trans) +{ + struct iwl_trans *trans; +#ifdef CONFIG_LOCKDEP + static struct lock_class_key __key; +#endif + + trans = devm_kzalloc(dev, sizeof(*trans) + priv_size, GFP_KERNEL); + if (!trans) + return NULL; + + trans->trans_cfg = cfg_trans; + +#ifdef CONFIG_LOCKDEP + lockdep_init_map(&trans->sync_cmd_lockdep_map, "sync_cmd_lockdep_map", + &__key, 0); +#endif + + trans->dev = dev; + trans->ops = ops; + trans->num_rx_queues = 1; + + WARN_ON(!ops->wait_txq_empty && !ops->wait_tx_queues_empty); + + if (trans->trans_cfg->use_tfh) { + trans->txqs.tfd.addr_size = 64; + trans->txqs.tfd.max_tbs = IWL_TFH_NUM_TBS; + trans->txqs.tfd.size = sizeof(struct iwl_tfh_tfd); + } else { + trans->txqs.tfd.addr_size = 36; + trans->txqs.tfd.max_tbs = IWL_NUM_OF_TBS; + trans->txqs.tfd.size = sizeof(struct iwl_tfd); + } + trans->max_skb_frags = IWL_TRANS_MAX_FRAGS(trans); + + return trans; +} + +int iwl_trans_init(struct iwl_trans *trans) +{ + int txcmd_size, txcmd_align; + + if (!trans->trans_cfg->gen2) { + txcmd_size = sizeof(struct iwl_tx_cmd); + txcmd_align = sizeof(void *); + } else if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) { + txcmd_size = sizeof(struct iwl_tx_cmd_gen2); + txcmd_align = 64; + } else { + txcmd_size = sizeof(struct iwl_tx_cmd_gen3); + txcmd_align = 128; + } + + txcmd_size += sizeof(struct iwl_cmd_header); + txcmd_size += 36; /* biggest possible 802.11 header */ + + /* Ensure device TX cmd cannot reach/cross a page boundary in gen2 */ + if (WARN_ON(trans->trans_cfg->gen2 && txcmd_size >= txcmd_align)) + return -EINVAL; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + trans->txqs.bc_tbl_size = sizeof(struct iwl_gen3_bc_tbl); + else + trans->txqs.bc_tbl_size = sizeof(struct iwlagn_scd_bc_tbl); + /* + * For gen2 devices, we use a single allocation for each byte-count + * table, but they're pretty small (1k) so use a DMA pool that we + * allocate here. + */ + if (trans->trans_cfg->gen2) { + trans->txqs.bc_pool = dmam_pool_create("iwlwifi:bc", trans->dev, + trans->txqs.bc_tbl_size, + 256, 0); + if (!trans->txqs.bc_pool) + return -ENOMEM; + } + + /* Some things must not change even if the config does */ + WARN_ON(trans->txqs.tfd.addr_size != + (trans->trans_cfg->use_tfh ? 64 : 36)); + + snprintf(trans->dev_cmd_pool_name, sizeof(trans->dev_cmd_pool_name), + "iwl_cmd_pool:%s", dev_name(trans->dev)); + trans->dev_cmd_pool = + kmem_cache_create(trans->dev_cmd_pool_name, + txcmd_size, txcmd_align, + SLAB_HWCACHE_ALIGN, NULL); + if (!trans->dev_cmd_pool) + return -ENOMEM; + +#ifdef CONFIG_INET + trans->txqs.tso_hdr_page = alloc_percpu(struct iwl_tso_hdr_page); + if (!trans->txqs.tso_hdr_page) { + kmem_cache_destroy(trans->dev_cmd_pool); + return -ENOMEM; + } +#endif + + /* Initialize the wait queue for commands */ + init_waitqueue_head(&trans->wait_command_queue); + + return 0; +} + +void iwl_trans_free(struct iwl_trans *trans) +{ +#ifdef CONFIG_INET + int i; + + if (trans->txqs.tso_hdr_page) { + for_each_possible_cpu(i) { + struct iwl_tso_hdr_page *p = + per_cpu_ptr(trans->txqs.tso_hdr_page, i); + + if (p && p->page) + __free_page(p->page); + } + + free_percpu(trans->txqs.tso_hdr_page); + } +#endif + + kmem_cache_destroy(trans->dev_cmd_pool); +} + +int iwl_trans_send_cmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) +{ + int ret; + + if (unlikely(!(cmd->flags & CMD_SEND_IN_RFKILL) && + test_bit(STATUS_RFKILL_OPMODE, &trans->status))) + return -ERFKILL; + + /* + * We can't test IWL_MVM_STATUS_IN_D3 in mvm->status because this + * bit is set early in the D3 flow, before we send all the commands + * that configure the firmware for D3 operation (power, patterns, ...) + * and we don't want to flag all those with CMD_SEND_IN_D3. + * So use the system_pm_mode instead. The only command sent after + * we set system_pm_mode is D3_CONFIG_CMD, which we now flag with + * CMD_SEND_IN_D3. + */ + if (unlikely(trans->system_pm_mode == IWL_PLAT_PM_MODE_D3 && + !(cmd->flags & CMD_SEND_IN_D3))) + return -EHOSTDOWN; + + if (unlikely(test_bit(STATUS_FW_ERROR, &trans->status))) + return -EIO; + + if (unlikely(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + if (WARN_ON((cmd->flags & CMD_WANT_ASYNC_CALLBACK) && + !(cmd->flags & CMD_ASYNC))) + return -EINVAL; + + if (!(cmd->flags & CMD_ASYNC)) + lock_map_acquire_read(&trans->sync_cmd_lockdep_map); + + if (trans->wide_cmd_header && !iwl_cmd_groupid(cmd->id)) { + if (cmd->id != REPLY_ERROR) + cmd->id = DEF_ID(cmd->id); + } + + ret = iwl_trans_txq_send_hcmd(trans, cmd); + + if (!(cmd->flags & CMD_ASYNC)) + lock_map_release(&trans->sync_cmd_lockdep_map); + + if (WARN_ON((cmd->flags & CMD_WANT_SKB) && !ret && !cmd->resp_pkt)) + return -EIO; + + return ret; +} +IWL_EXPORT_SYMBOL(iwl_trans_send_cmd); + +/* Comparator for struct iwl_hcmd_names. + * Used in the binary search over a list of host commands. + * + * @key: command_id that we're looking for. + * @elt: struct iwl_hcmd_names candidate for match. + * + * @return 0 iff equal. + */ +static int iwl_hcmd_names_cmp(const void *key, const void *elt) +{ + const struct iwl_hcmd_names *name = elt; + const u8 *cmd1 = key; + u8 cmd2 = name->cmd_id; + + return (*cmd1 - cmd2); +} + +const char *iwl_get_cmd_string(struct iwl_trans *trans, u32 id) +{ + u8 grp, cmd; + struct iwl_hcmd_names *ret; + const struct iwl_hcmd_arr *arr; + size_t size = sizeof(struct iwl_hcmd_names); + + grp = iwl_cmd_groupid(id); + cmd = iwl_cmd_opcode(id); + + if (!trans->command_groups || grp >= trans->command_groups_size || + !trans->command_groups[grp].arr) + return "UNKNOWN"; + + arr = &trans->command_groups[grp]; + ret = bsearch(&cmd, arr->arr, arr->size, size, iwl_hcmd_names_cmp); + if (!ret) + return "UNKNOWN"; + return ret->cmd_name; +} +IWL_EXPORT_SYMBOL(iwl_get_cmd_string); + +int iwl_cmd_groups_verify_sorted(const struct iwl_trans_config *trans) +{ + int i, j; + const struct iwl_hcmd_arr *arr; + + for (i = 0; i < trans->command_groups_size; i++) { + arr = &trans->command_groups[i]; + if (!arr->arr) + continue; + for (j = 0; j < arr->size - 1; j++) + if (arr->arr[j].cmd_id > arr->arr[j + 1].cmd_id) + return -1; + } + return 0; +} +IWL_EXPORT_SYMBOL(iwl_cmd_groups_verify_sorted); diff --git a/sys/contrib/dev/iwlwifi/iwl-trans.h b/sys/contrib/dev/iwlwifi/iwl-trans.h new file mode 100644 index 00000000000..2868b3c85b4 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-trans.h @@ -0,0 +1,1504 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_trans_h__ +#define __iwl_trans_h__ + +#include +#include /* for page_address */ +#include +#include + +#include "iwl-debug.h" +#include "iwl-config.h" +#include "fw/img.h" +#include "iwl-op-mode.h" +#include +#include "fw/api/cmdhdr.h" +#include "fw/api/txq.h" +#include "fw/api/dbg-tlv.h" +#include "iwl-dbg-tlv.h" +#if defined(__FreeBSD__) +#include +#include "iwl-modparams.h" +#endif + +/** + * DOC: Transport layer - what is it ? + * + * The transport layer is the layer that deals with the HW directly. It provides + * an abstraction of the underlying HW to the upper layer. The transport layer + * doesn't provide any policy, algorithm or anything of this kind, but only + * mechanisms to make the HW do something. It is not completely stateless but + * close to it. + * We will have an implementation for each different supported bus. + */ + +/** + * DOC: Life cycle of the transport layer + * + * The transport layer has a very precise life cycle. + * + * 1) A helper function is called during the module initialization and + * registers the bus driver's ops with the transport's alloc function. + * 2) Bus's probe calls to the transport layer's allocation functions. + * Of course this function is bus specific. + * 3) This allocation functions will spawn the upper layer which will + * register mac80211. + * + * 4) At some point (i.e. mac80211's start call), the op_mode will call + * the following sequence: + * start_hw + * start_fw + * + * 5) Then when finished (or reset): + * stop_device + * + * 6) Eventually, the free function will be called. + */ + +#define IWL_TRANS_FW_DBG_DOMAIN(trans) IWL_FW_INI_DOMAIN_ALWAYS_ON + +#define FH_RSCSR_FRAME_SIZE_MSK 0x00003FFF /* bits 0-13 */ +#define FH_RSCSR_FRAME_INVALID 0x55550000 +#define FH_RSCSR_FRAME_ALIGN 0x40 +#define FH_RSCSR_RPA_EN BIT(25) +#define FH_RSCSR_RADA_EN BIT(26) +#define FH_RSCSR_RXQ_POS 16 +#define FH_RSCSR_RXQ_MASK 0x3F0000 + +struct iwl_rx_packet { + /* + * The first 4 bytes of the RX frame header contain both the RX frame + * size and some flags. + * Bit fields: + * 31: flag flush RB request + * 30: flag ignore TC (terminal counter) request + * 29: flag fast IRQ request + * 28-27: Reserved + * 26: RADA enabled + * 25: Offload enabled + * 24: RPF enabled + * 23: RSS enabled + * 22: Checksum enabled + * 21-16: RX queue + * 15-14: Reserved + * 13-00: RX frame size + */ + __le32 len_n_flags; + struct iwl_cmd_header hdr; + u8 data[]; +} __packed; + +static inline u32 iwl_rx_packet_len(const struct iwl_rx_packet *pkt) +{ + return le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; +} + +static inline u32 iwl_rx_packet_payload_len(const struct iwl_rx_packet *pkt) +{ + return iwl_rx_packet_len(pkt) - sizeof(pkt->hdr); +} + +/** + * enum CMD_MODE - how to send the host commands ? + * + * @CMD_ASYNC: Return right away and don't wait for the response + * @CMD_WANT_SKB: Not valid with CMD_ASYNC. The caller needs the buffer of + * the response. The caller needs to call iwl_free_resp when done. + * @CMD_WANT_ASYNC_CALLBACK: the op_mode's async callback function must be + * called after this command completes. Valid only with CMD_ASYNC. + * @CMD_SEND_IN_D3: Allow the command to be sent in D3 mode, relevant to + * SUSPEND and RESUME commands. We are in D3 mode when we set + * trans->system_pm_mode to IWL_PLAT_PM_MODE_D3. + */ +enum CMD_MODE { + CMD_ASYNC = BIT(0), + CMD_WANT_SKB = BIT(1), + CMD_SEND_IN_RFKILL = BIT(2), + CMD_WANT_ASYNC_CALLBACK = BIT(3), + CMD_SEND_IN_D3 = BIT(4), +}; + +#define DEF_CMD_PAYLOAD_SIZE 320 + +/** + * struct iwl_device_cmd + * + * For allocation of the command and tx queues, this establishes the overall + * size of the largest command we send to uCode, except for commands that + * aren't fully copied and use other TFD space. + */ +struct iwl_device_cmd { + union { + struct { + struct iwl_cmd_header hdr; /* uCode API */ + u8 payload[DEF_CMD_PAYLOAD_SIZE]; + }; + struct { + struct iwl_cmd_header_wide hdr_wide; + u8 payload_wide[DEF_CMD_PAYLOAD_SIZE - + sizeof(struct iwl_cmd_header_wide) + + sizeof(struct iwl_cmd_header)]; + }; + }; +} __packed; + +/** + * struct iwl_device_tx_cmd - buffer for TX command + * @hdr: the header + * @payload: the payload placeholder + * + * The actual structure is sized dynamically according to need. + */ +struct iwl_device_tx_cmd { + struct iwl_cmd_header hdr; + u8 payload[]; +} __packed; + +#define TFD_MAX_PAYLOAD_SIZE (sizeof(struct iwl_device_cmd)) + +/* + * number of transfer buffers (fragments) per transmit frame descriptor; + * this is just the driver's idea, the hardware supports 20 + */ +#define IWL_MAX_CMD_TBS_PER_TFD 2 + +/* We need 2 entries for the TX command and header, and another one might + * be needed for potential data in the SKB's head. The remaining ones can + * be used for frags. + */ +#define IWL_TRANS_MAX_FRAGS(trans) ((trans)->txqs.tfd.max_tbs - 3) + +/** + * enum iwl_hcmd_dataflag - flag for each one of the chunks of the command + * + * @IWL_HCMD_DFL_NOCOPY: By default, the command is copied to the host command's + * ring. The transport layer doesn't map the command's buffer to DMA, but + * rather copies it to a previously allocated DMA buffer. This flag tells + * the transport layer not to copy the command, but to map the existing + * buffer (that is passed in) instead. This saves the memcpy and allows + * commands that are bigger than the fixed buffer to be submitted. + * Note that a TFD entry after a NOCOPY one cannot be a normal copied one. + * @IWL_HCMD_DFL_DUP: Only valid without NOCOPY, duplicate the memory for this + * chunk internally and free it again after the command completes. This + * can (currently) be used only once per command. + * Note that a TFD entry after a DUP one cannot be a normal copied one. + */ +enum iwl_hcmd_dataflag { + IWL_HCMD_DFL_NOCOPY = BIT(0), + IWL_HCMD_DFL_DUP = BIT(1), +}; + +enum iwl_error_event_table_status { + IWL_ERROR_EVENT_TABLE_LMAC1 = BIT(0), + IWL_ERROR_EVENT_TABLE_LMAC2 = BIT(1), + IWL_ERROR_EVENT_TABLE_UMAC = BIT(2), + IWL_ERROR_EVENT_TABLE_TCM = BIT(3), +}; + +/** + * struct iwl_host_cmd - Host command to the uCode + * + * @data: array of chunks that composes the data of the host command + * @resp_pkt: response packet, if %CMD_WANT_SKB was set + * @_rx_page_order: (internally used to free response packet); + * FreeBSD uses _page instead. + * @_rx_page_addr: (internally used to free response packet) + * @flags: can be CMD_* + * @len: array of the lengths of the chunks in data + * @dataflags: IWL_HCMD_DFL_* + * @id: command id of the host command, for wide commands encoding the + * version and group as well + */ +struct iwl_host_cmd { + const void *data[IWL_MAX_CMD_TBS_PER_TFD]; + struct iwl_rx_packet *resp_pkt; +#if defined(__linux__) + unsigned long _rx_page_addr; +#elif defined(__FreeBSD__) + struct page *_page; +#endif + u32 _rx_page_order; + + u32 flags; + u32 id; + u16 len[IWL_MAX_CMD_TBS_PER_TFD]; + u8 dataflags[IWL_MAX_CMD_TBS_PER_TFD]; +}; + +static inline void iwl_free_resp(struct iwl_host_cmd *cmd) +{ +#if defined(__linux__) + free_pages(cmd->_rx_page_addr, cmd->_rx_page_order); +#elif defined(__FreeBSD__) + __free_pages(cmd->_page, cmd->_rx_page_order); +#endif +} + +struct iwl_rx_cmd_buffer { + struct page *_page; + int _offset; + bool _page_stolen; + u32 _rx_page_order; + unsigned int truesize; +}; + +static inline void *rxb_addr(struct iwl_rx_cmd_buffer *r) +{ + return (void *)((unsigned long)page_address(r->_page) + r->_offset); +} + +static inline int rxb_offset(struct iwl_rx_cmd_buffer *r) +{ + return r->_offset; +} + +static inline struct page *rxb_steal_page(struct iwl_rx_cmd_buffer *r) +{ + r->_page_stolen = true; + get_page(r->_page); + return r->_page; +} + +static inline void iwl_free_rxb(struct iwl_rx_cmd_buffer *r) +{ + __free_pages(r->_page, r->_rx_page_order); +} + +#define MAX_NO_RECLAIM_CMDS 6 + +#define IWL_MASK(lo, hi) ((1 << (hi)) | ((1 << (hi)) - (1 << (lo)))) + +/* + * Maximum number of HW queues the transport layer + * currently supports + */ +#define IWL_MAX_HW_QUEUES 32 +#define IWL_MAX_TVQM_QUEUES 512 + +#define IWL_MAX_TID_COUNT 8 +#define IWL_MGMT_TID 15 +#define IWL_FRAME_LIMIT 64 +#define IWL_MAX_RX_HW_QUEUES 16 +#define IWL_9000_MAX_RX_HW_QUEUES 6 + +/** + * enum iwl_wowlan_status - WoWLAN image/device status + * @IWL_D3_STATUS_ALIVE: firmware is still running after resume + * @IWL_D3_STATUS_RESET: device was reset while suspended + */ +enum iwl_d3_status { + IWL_D3_STATUS_ALIVE, + IWL_D3_STATUS_RESET, +}; + +/** + * enum iwl_trans_status: transport status flags + * @STATUS_SYNC_HCMD_ACTIVE: a SYNC command is being processed + * @STATUS_DEVICE_ENABLED: APM is enabled + * @STATUS_TPOWER_PMI: the device might be asleep (need to wake it up) + * @STATUS_INT_ENABLED: interrupts are enabled + * @STATUS_RFKILL_HW: the actual HW state of the RF-kill switch + * @STATUS_RFKILL_OPMODE: RF-kill state reported to opmode + * @STATUS_FW_ERROR: the fw is in error state + * @STATUS_TRANS_GOING_IDLE: shutting down the trans, only special commands + * are sent + * @STATUS_TRANS_IDLE: the trans is idle - general commands are not to be sent + * @STATUS_TRANS_DEAD: trans is dead - avoid any read/write operation + */ +enum iwl_trans_status { + STATUS_SYNC_HCMD_ACTIVE, + STATUS_DEVICE_ENABLED, + STATUS_TPOWER_PMI, + STATUS_INT_ENABLED, + STATUS_RFKILL_HW, + STATUS_RFKILL_OPMODE, + STATUS_FW_ERROR, + STATUS_TRANS_GOING_IDLE, + STATUS_TRANS_IDLE, + STATUS_TRANS_DEAD, +}; + +static inline int +iwl_trans_get_rb_size_order(enum iwl_amsdu_size rb_size) +{ + switch (rb_size) { + case IWL_AMSDU_2K: + return get_order(2 * 1024); + case IWL_AMSDU_4K: + return get_order(4 * 1024); + case IWL_AMSDU_8K: + return get_order(8 * 1024); + case IWL_AMSDU_12K: + return get_order(16 * 1024); + default: + WARN_ON(1); + return -1; + } +} + +static inline int +iwl_trans_get_rb_size(enum iwl_amsdu_size rb_size) +{ + switch (rb_size) { + case IWL_AMSDU_2K: + return 2 * 1024; + case IWL_AMSDU_4K: + return 4 * 1024; + case IWL_AMSDU_8K: + return 8 * 1024; + case IWL_AMSDU_12K: + return 16 * 1024; + default: + WARN_ON(1); + return 0; + } +} + +struct iwl_hcmd_names { + u8 cmd_id; + const char *const cmd_name; +}; + +#define HCMD_NAME(x) \ + { .cmd_id = x, .cmd_name = #x } + +struct iwl_hcmd_arr { + const struct iwl_hcmd_names *arr; + int size; +}; + +#define HCMD_ARR(x) \ + { .arr = x, .size = ARRAY_SIZE(x) } + +/** + * struct iwl_dump_sanitize_ops - dump sanitization operations + * @frob_txf: Scrub the TX FIFO data + * @frob_hcmd: Scrub a host command, the %hcmd pointer is to the header + * but that might be short or long (&struct iwl_cmd_header or + * &struct iwl_cmd_header_wide) + * @frob_mem: Scrub memory data + */ +struct iwl_dump_sanitize_ops { + void (*frob_txf)(void *ctx, void *buf, size_t buflen); + void (*frob_hcmd)(void *ctx, void *hcmd, size_t buflen); + void (*frob_mem)(void *ctx, u32 mem_addr, void *mem, size_t buflen); +}; + +/** + * struct iwl_trans_config - transport configuration + * + * @op_mode: pointer to the upper layer. + * @cmd_queue: the index of the command queue. + * Must be set before start_fw. + * @cmd_fifo: the fifo for host commands + * @cmd_q_wdg_timeout: the timeout of the watchdog timer for the command queue. + * @no_reclaim_cmds: Some devices erroneously don't set the + * SEQ_RX_FRAME bit on some notifications, this is the + * list of such notifications to filter. Max length is + * %MAX_NO_RECLAIM_CMDS. + * @n_no_reclaim_cmds: # of commands in list + * @rx_buf_size: RX buffer size needed for A-MSDUs + * if unset 4k will be the RX buffer size + * @bc_table_dword: set to true if the BC table expects the byte count to be + * in DWORD (as opposed to bytes) + * @scd_set_active: should the transport configure the SCD for HCMD queue + * @command_groups: array of command groups, each member is an array of the + * commands in the group; for debugging only + * @command_groups_size: number of command groups, to avoid illegal access + * @cb_data_offs: offset inside skb->cb to store transport data at, must have + * space for at least two pointers + * @fw_reset_handshake: firmware supports reset flow handshake + */ +struct iwl_trans_config { + struct iwl_op_mode *op_mode; + + u8 cmd_queue; + u8 cmd_fifo; + unsigned int cmd_q_wdg_timeout; + const u8 *no_reclaim_cmds; + unsigned int n_no_reclaim_cmds; + + enum iwl_amsdu_size rx_buf_size; + bool bc_table_dword; + bool scd_set_active; + const struct iwl_hcmd_arr *command_groups; + int command_groups_size; + + u8 cb_data_offs; + bool fw_reset_handshake; +}; + +struct iwl_trans_dump_data { + u32 len; + u8 data[]; +}; + +struct iwl_trans; + +struct iwl_trans_txq_scd_cfg { + u8 fifo; + u8 sta_id; + u8 tid; + bool aggregate; + int frame_limit; +}; + +/** + * struct iwl_trans_rxq_dma_data - RX queue DMA data + * @fr_bd_cb: DMA address of free BD cyclic buffer + * @fr_bd_wid: Initial write index of the free BD cyclic buffer + * @urbd_stts_wrptr: DMA address of urbd_stts_wrptr + * @ur_bd_cb: DMA address of used BD cyclic buffer + */ +struct iwl_trans_rxq_dma_data { + u64 fr_bd_cb; + u32 fr_bd_wid; + u64 urbd_stts_wrptr; + u64 ur_bd_cb; +}; + +/** + * struct iwl_trans_ops - transport specific operations + * + * All the handlers MUST be implemented + * + * @start_hw: starts the HW. From that point on, the HW can send interrupts. + * May sleep. + * @op_mode_leave: Turn off the HW RF kill indication if on + * May sleep + * @start_fw: allocates and inits all the resources for the transport + * layer. Also kick a fw image. + * May sleep + * @fw_alive: called when the fw sends alive notification. If the fw provides + * the SCD base address in SRAM, then provide it here, or 0 otherwise. + * May sleep + * @stop_device: stops the whole device (embedded CPU put to reset) and stops + * the HW. From that point on, the HW will be stopped but will still issue + * an interrupt if the HW RF kill switch is triggered. + * This callback must do the right thing and not crash even if %start_hw() + * was called but not &start_fw(). May sleep. + * @d3_suspend: put the device into the correct mode for WoWLAN during + * suspend. This is optional, if not implemented WoWLAN will not be + * supported. This callback may sleep. + * @d3_resume: resume the device after WoWLAN, enabling the opmode to + * talk to the WoWLAN image to get its status. This is optional, if not + * implemented WoWLAN will not be supported. This callback may sleep. + * @send_cmd:send a host command. Must return -ERFKILL if RFkill is asserted. + * If RFkill is asserted in the middle of a SYNC host command, it must + * return -ERFKILL straight away. + * May sleep only if CMD_ASYNC is not set + * @tx: send an skb. The transport relies on the op_mode to zero the + * the ieee80211_tx_info->driver_data. If the MPDU is an A-MSDU, all + * the CSUM will be taken care of (TCP CSUM and IP header in case of + * IPv4). If the MPDU is a single MSDU, the op_mode must compute the IP + * header if it is IPv4. + * Must be atomic + * @reclaim: free packet until ssn. Returns a list of freed packets. + * Must be atomic + * @txq_enable: setup a queue. To setup an AC queue, use the + * iwl_trans_ac_txq_enable wrapper. fw_alive must have been called before + * this one. The op_mode must not configure the HCMD queue. The scheduler + * configuration may be %NULL, in which case the hardware will not be + * configured. If true is returned, the operation mode needs to increment + * the sequence number of the packets routed to this queue because of a + * hardware scheduler bug. May sleep. + * @txq_disable: de-configure a Tx queue to send AMPDUs + * Must be atomic + * @txq_set_shared_mode: change Tx queue shared/unshared marking + * @wait_tx_queues_empty: wait until tx queues are empty. May sleep. + * @wait_txq_empty: wait until specific tx queue is empty. May sleep. + * @freeze_txq_timer: prevents the timer of the queue from firing until the + * queue is set to awake. Must be atomic. + * @block_txq_ptrs: stop updating the write pointers of the Tx queues. Note + * that the transport needs to refcount the calls since this function + * will be called several times with block = true, and then the queues + * need to be unblocked only after the same number of calls with + * block = false. + * @write8: write a u8 to a register at offset ofs from the BAR + * @write32: write a u32 to a register at offset ofs from the BAR + * @read32: read a u32 register at offset ofs from the BAR + * @read_prph: read a DWORD from a periphery register + * @write_prph: write a DWORD to a periphery register + * @read_mem: read device's SRAM in DWORD + * @write_mem: write device's SRAM in DWORD. If %buf is %NULL, then the memory + * will be zeroed. + * @read_config32: read a u32 value from the device's config space at + * the given offset. + * @configure: configure parameters required by the transport layer from + * the op_mode. May be called several times before start_fw, can't be + * called after that. + * @set_pmi: set the power pmi state + * @grab_nic_access: wake the NIC to be able to access non-HBUS regs. + * Sleeping is not allowed between grab_nic_access and + * release_nic_access. + * @release_nic_access: let the NIC go to sleep. The "flags" parameter + * must be the same one that was sent before to the grab_nic_access. + * @set_bits_mask - set SRAM register according to value and mask. + * @dump_data: return a vmalloc'ed buffer with debug data, maybe containing last + * TX'ed commands and similar. The buffer will be vfree'd by the caller. + * Note that the transport must fill in the proper file headers. + * @debugfs_cleanup: used in the driver unload flow to make a proper cleanup + * of the trans debugfs + * @set_pnvm: set the pnvm data in the prph scratch buffer, inside the + * context info. + * @interrupts: disable/enable interrupts to transport + */ +struct iwl_trans_ops { + + int (*start_hw)(struct iwl_trans *iwl_trans); + void (*op_mode_leave)(struct iwl_trans *iwl_trans); + int (*start_fw)(struct iwl_trans *trans, const struct fw_img *fw, + bool run_in_rfkill); + void (*fw_alive)(struct iwl_trans *trans, u32 scd_addr); + void (*stop_device)(struct iwl_trans *trans); + + int (*d3_suspend)(struct iwl_trans *trans, bool test, bool reset); + int (*d3_resume)(struct iwl_trans *trans, enum iwl_d3_status *status, + bool test, bool reset); + + int (*send_cmd)(struct iwl_trans *trans, struct iwl_host_cmd *cmd); + + int (*tx)(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int queue); + void (*reclaim)(struct iwl_trans *trans, int queue, int ssn, + struct sk_buff_head *skbs); + + void (*set_q_ptrs)(struct iwl_trans *trans, int queue, int ptr); + + bool (*txq_enable)(struct iwl_trans *trans, int queue, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int queue_wdg_timeout); + void (*txq_disable)(struct iwl_trans *trans, int queue, + bool configure_scd); + /* 22000 functions */ + int (*txq_alloc)(struct iwl_trans *trans, + __le16 flags, u8 sta_id, u8 tid, + int cmd_id, int size, + unsigned int queue_wdg_timeout); + void (*txq_free)(struct iwl_trans *trans, int queue); + int (*rxq_dma_data)(struct iwl_trans *trans, int queue, + struct iwl_trans_rxq_dma_data *data); + + void (*txq_set_shared_mode)(struct iwl_trans *trans, u32 txq_id, + bool shared); + + int (*wait_tx_queues_empty)(struct iwl_trans *trans, u32 txq_bm); + int (*wait_txq_empty)(struct iwl_trans *trans, int queue); + void (*freeze_txq_timer)(struct iwl_trans *trans, unsigned long txqs, + bool freeze); + void (*block_txq_ptrs)(struct iwl_trans *trans, bool block); + + void (*write8)(struct iwl_trans *trans, u32 ofs, u8 val); + void (*write32)(struct iwl_trans *trans, u32 ofs, u32 val); + u32 (*read32)(struct iwl_trans *trans, u32 ofs); + u32 (*read_prph)(struct iwl_trans *trans, u32 ofs); + void (*write_prph)(struct iwl_trans *trans, u32 ofs, u32 val); + int (*read_mem)(struct iwl_trans *trans, u32 addr, + void *buf, int dwords); + int (*write_mem)(struct iwl_trans *trans, u32 addr, + const void *buf, int dwords); + int (*read_config32)(struct iwl_trans *trans, u32 ofs, u32 *val); + void (*configure)(struct iwl_trans *trans, + const struct iwl_trans_config *trans_cfg); + void (*set_pmi)(struct iwl_trans *trans, bool state); + void (*sw_reset)(struct iwl_trans *trans); + bool (*grab_nic_access)(struct iwl_trans *trans); + void (*release_nic_access)(struct iwl_trans *trans); + void (*set_bits_mask)(struct iwl_trans *trans, u32 reg, u32 mask, + u32 value); + + struct iwl_trans_dump_data *(*dump_data)(struct iwl_trans *trans, + u32 dump_mask, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx); + void (*debugfs_cleanup)(struct iwl_trans *trans); + void (*sync_nmi)(struct iwl_trans *trans); + int (*set_pnvm)(struct iwl_trans *trans, const void *data, u32 len); + int (*set_reduce_power)(struct iwl_trans *trans, + const void *data, u32 len); + void (*interrupts)(struct iwl_trans *trans, bool enable); +}; + +/** + * enum iwl_trans_state - state of the transport layer + * + * @IWL_TRANS_NO_FW: firmware wasn't started yet, or crashed + * @IWL_TRANS_FW_STARTED: FW was started, but not alive yet + * @IWL_TRANS_FW_ALIVE: FW has sent an alive response + */ +enum iwl_trans_state { + IWL_TRANS_NO_FW, + IWL_TRANS_FW_STARTED, + IWL_TRANS_FW_ALIVE, +}; + +/** + * DOC: Platform power management + * + * In system-wide power management the entire platform goes into a low + * power state (e.g. idle or suspend to RAM) at the same time and the + * device is configured as a wakeup source for the entire platform. + * This is usually triggered by userspace activity (e.g. the user + * presses the suspend button or a power management daemon decides to + * put the platform in low power mode). The device's behavior in this + * mode is dictated by the wake-on-WLAN configuration. + * + * The terms used for the device's behavior are as follows: + * + * - D0: the device is fully powered and the host is awake; + * - D3: the device is in low power mode and only reacts to + * specific events (e.g. magic-packet received or scan + * results found); + * + * These terms reflect the power modes in the firmware and are not to + * be confused with the physical device power state. + */ + +/** + * enum iwl_plat_pm_mode - platform power management mode + * + * This enumeration describes the device's platform power management + * behavior when in system-wide suspend (i.e WoWLAN). + * + * @IWL_PLAT_PM_MODE_DISABLED: power management is disabled for this + * device. In system-wide suspend mode, it means that the all + * connections will be closed automatically by mac80211 before + * the platform is suspended. + * @IWL_PLAT_PM_MODE_D3: the device goes into D3 mode (i.e. WoWLAN). + */ +enum iwl_plat_pm_mode { + IWL_PLAT_PM_MODE_DISABLED, + IWL_PLAT_PM_MODE_D3, +}; + +/** + * enum iwl_ini_cfg_state + * @IWL_INI_CFG_STATE_NOT_LOADED: no debug cfg was given + * @IWL_INI_CFG_STATE_LOADED: debug cfg was found and loaded + * @IWL_INI_CFG_STATE_CORRUPTED: debug cfg was found and some of the TLVs + * are corrupted. The rest of the debug TLVs will still be used + */ +enum iwl_ini_cfg_state { + IWL_INI_CFG_STATE_NOT_LOADED, + IWL_INI_CFG_STATE_LOADED, + IWL_INI_CFG_STATE_CORRUPTED, +}; + +/* Max time to wait for nmi interrupt */ +#define IWL_TRANS_NMI_TIMEOUT (HZ / 4) + +/** + * struct iwl_dram_data + * @physical: page phy pointer + * @block: pointer to the allocated block/page + * @size: size of the block/page + */ +struct iwl_dram_data { + dma_addr_t physical; + void *block; + int size; +}; + +/** + * struct iwl_fw_mon - fw monitor per allocation id + * @num_frags: number of fragments + * @frags: an array of DRAM buffer fragments + */ +struct iwl_fw_mon { + u32 num_frags; + struct iwl_dram_data *frags; +}; + +/** + * struct iwl_self_init_dram - dram data used by self init process + * @fw: lmac and umac dram data + * @fw_cnt: total number of items in array + * @paging: paging dram data + * @paging_cnt: total number of items in array + */ +struct iwl_self_init_dram { + struct iwl_dram_data *fw; + int fw_cnt; + struct iwl_dram_data *paging; + int paging_cnt; +}; + +/** + * struct iwl_trans_debug - transport debug related data + * + * @n_dest_reg: num of reg_ops in %dbg_dest_tlv + * @rec_on: true iff there is a fw debug recording currently active + * @dest_tlv: points to the destination TLV for debug + * @conf_tlv: array of pointers to configuration TLVs for debug + * @trigger_tlv: array of pointers to triggers TLVs for debug + * @lmac_error_event_table: addrs of lmacs error tables + * @umac_error_event_table: addr of umac error table + * @tcm_error_event_table: address of TCM error table + * @error_event_table_tlv_status: bitmap that indicates what error table + * pointers was recevied via TLV. uses enum &iwl_error_event_table_status + * @internal_ini_cfg: internal debug cfg state. Uses &enum iwl_ini_cfg_state + * @external_ini_cfg: external debug cfg state. Uses &enum iwl_ini_cfg_state + * @fw_mon_cfg: debug buffer allocation configuration + * @fw_mon_ini: DRAM buffer fragments per allocation id + * @fw_mon: DRAM buffer for firmware monitor + * @hw_error: equals true if hw error interrupt was received from the FW + * @ini_dest: debug monitor destination uses &enum iwl_fw_ini_buffer_location + * @active_regions: active regions + * @debug_info_tlv_list: list of debug info TLVs + * @time_point: array of debug time points + * @periodic_trig_list: periodic triggers list + * @domains_bitmap: bitmap of active domains other than &IWL_FW_INI_DOMAIN_ALWAYS_ON + * @ucode_preset: preset based on ucode + */ +struct iwl_trans_debug { + u8 n_dest_reg; + bool rec_on; + + const struct iwl_fw_dbg_dest_tlv_v1 *dest_tlv; + const struct iwl_fw_dbg_conf_tlv *conf_tlv[FW_DBG_CONF_MAX]; + struct iwl_fw_dbg_trigger_tlv * const *trigger_tlv; + + u32 lmac_error_event_table[2]; + u32 umac_error_event_table; + u32 tcm_error_event_table; + unsigned int error_event_table_tlv_status; + + enum iwl_ini_cfg_state internal_ini_cfg; + enum iwl_ini_cfg_state external_ini_cfg; + + struct iwl_fw_ini_allocation_tlv fw_mon_cfg[IWL_FW_INI_ALLOCATION_NUM]; + struct iwl_fw_mon fw_mon_ini[IWL_FW_INI_ALLOCATION_NUM]; + + struct iwl_dram_data fw_mon; + + bool hw_error; + enum iwl_fw_ini_buffer_location ini_dest; + + u64 unsupported_region_msk; + struct iwl_ucode_tlv *active_regions[IWL_FW_INI_MAX_REGION_ID]; + struct list_head debug_info_tlv_list; + struct iwl_dbg_tlv_time_point_data + time_point[IWL_FW_INI_TIME_POINT_NUM]; + struct list_head periodic_trig_list; + + u32 domains_bitmap; + u32 ucode_preset; +}; + +struct iwl_dma_ptr { + dma_addr_t dma; + void *addr; + size_t size; +}; + +struct iwl_cmd_meta { + /* only for SYNC commands, iff the reply skb is wanted */ + struct iwl_host_cmd *source; + u32 flags; + u32 tbs; +}; + +/* + * The FH will write back to the first TB only, so we need to copy some data + * into the buffer regardless of whether it should be mapped or not. + * This indicates how big the first TB must be to include the scratch buffer + * and the assigned PN. + * Since PN location is 8 bytes at offset 12, it's 20 now. + * If we make it bigger then allocations will be bigger and copy slower, so + * that's probably not useful. + */ +#define IWL_FIRST_TB_SIZE 20 +#define IWL_FIRST_TB_SIZE_ALIGN ALIGN(IWL_FIRST_TB_SIZE, 64) + +struct iwl_pcie_txq_entry { + void *cmd; + struct sk_buff *skb; + /* buffer to free after command completes */ + const void *free_buf; + struct iwl_cmd_meta meta; +}; + +struct iwl_pcie_first_tb_buf { + u8 buf[IWL_FIRST_TB_SIZE_ALIGN]; +}; + +/** + * struct iwl_txq - Tx Queue for DMA + * @q: generic Rx/Tx queue descriptor + * @tfds: transmit frame descriptors (DMA memory) + * @first_tb_bufs: start of command headers, including scratch buffers, for + * the writeback -- this is DMA memory and an array holding one buffer + * for each command on the queue + * @first_tb_dma: DMA address for the first_tb_bufs start + * @entries: transmit entries (driver state) + * @lock: queue lock + * @stuck_timer: timer that fires if queue gets stuck + * @trans: pointer back to transport (for timer) + * @need_update: indicates need to update read/write index + * @ampdu: true if this queue is an ampdu queue for an specific RA/TID + * @wd_timeout: queue watchdog timeout (jiffies) - per queue + * @frozen: tx stuck queue timer is frozen + * @frozen_expiry_remainder: remember how long until the timer fires + * @bc_tbl: byte count table of the queue (relevant only for gen2 transport) + * @write_ptr: 1-st empty entry (index) host_w + * @read_ptr: last used entry (index) host_r + * @dma_addr: physical addr for BD's + * @n_window: safe queue window + * @id: queue id + * @low_mark: low watermark, resume queue if free space more than this + * @high_mark: high watermark, stop queue if free space less than this + * + * A Tx queue consists of circular buffer of BDs (a.k.a. TFDs, transmit frame + * descriptors) and required locking structures. + * + * Note the difference between TFD_QUEUE_SIZE_MAX and n_window: the hardware + * always assumes 256 descriptors, so TFD_QUEUE_SIZE_MAX is always 256 (unless + * there might be HW changes in the future). For the normal TX + * queues, n_window, which is the size of the software queue data + * is also 256; however, for the command queue, n_window is only + * 32 since we don't need so many commands pending. Since the HW + * still uses 256 BDs for DMA though, TFD_QUEUE_SIZE_MAX stays 256. + * This means that we end up with the following: + * HW entries: | 0 | ... | N * 32 | ... | N * 32 + 31 | ... | 255 | + * SW entries: | 0 | ... | 31 | + * where N is a number between 0 and 7. This means that the SW + * data is a window overlayed over the HW queue. + */ +struct iwl_txq { + void *tfds; + struct iwl_pcie_first_tb_buf *first_tb_bufs; + dma_addr_t first_tb_dma; + struct iwl_pcie_txq_entry *entries; + /* lock for syncing changes on the queue */ + spinlock_t lock; + unsigned long frozen_expiry_remainder; + struct timer_list stuck_timer; + struct iwl_trans *trans; + bool need_update; + bool frozen; + bool ampdu; + int block; + unsigned long wd_timeout; + struct sk_buff_head overflow_q; + struct iwl_dma_ptr bc_tbl; + + int write_ptr; + int read_ptr; + dma_addr_t dma_addr; + int n_window; + u32 id; + int low_mark; + int high_mark; + + bool overflow_tx; +}; + +/** + * struct iwl_trans_txqs - transport tx queues data + * + * @bc_table_dword: true if the BC table expects DWORD (as opposed to bytes) + * @page_offs: offset from skb->cb to mac header page pointer + * @dev_cmd_offs: offset from skb->cb to iwl_device_tx_cmd pointer + * @queue_used - bit mask of used queues + * @queue_stopped - bit mask of stopped queues + * @scd_bc_tbls: gen1 pointer to the byte count table of the scheduler + */ +struct iwl_trans_txqs { + unsigned long queue_used[BITS_TO_LONGS(IWL_MAX_TVQM_QUEUES)]; + unsigned long queue_stopped[BITS_TO_LONGS(IWL_MAX_TVQM_QUEUES)]; + struct iwl_txq *txq[IWL_MAX_TVQM_QUEUES]; + struct dma_pool *bc_pool; + size_t bc_tbl_size; + bool bc_table_dword; + u8 page_offs; + u8 dev_cmd_offs; + struct iwl_tso_hdr_page __percpu *tso_hdr_page; + + struct { + u8 fifo; + u8 q_id; + unsigned int wdg_timeout; + } cmd; + + struct { + u8 max_tbs; + u16 size; + u8 addr_size; + } tfd; + + struct iwl_dma_ptr scd_bc_tbls; +}; + +/** + * struct iwl_trans - transport common data + * + * @ops - pointer to iwl_trans_ops + * @op_mode - pointer to the op_mode + * @trans_cfg: the trans-specific configuration part + * @cfg - pointer to the configuration + * @drv - pointer to iwl_drv + * @status: a bit-mask of transport status flags + * @dev - pointer to struct device * that represents the device + * @max_skb_frags: maximum number of fragments an SKB can have when transmitted. + * 0 indicates that frag SKBs (NETIF_F_SG) aren't supported. + * @hw_rf_id a u32 with the device RF ID + * @hw_id: a u32 with the ID of the device / sub-device. + * Set during transport allocation. + * @hw_id_str: a string with info about HW ID. Set during transport allocation. + * @pm_support: set to true in start_hw if link pm is supported + * @ltr_enabled: set to true if the LTR is enabled + * @wide_cmd_header: true when ucode supports wide command header format + * @wait_command_queue: wait queue for sync commands + * @num_rx_queues: number of RX queues allocated by the transport; + * the transport must set this before calling iwl_drv_start() + * @iml_len: the length of the image loader + * @iml: a pointer to the image loader itself + * @dev_cmd_pool: pool for Tx cmd allocation - for internal use only. + * The user should use iwl_trans_{alloc,free}_tx_cmd. + * @rx_mpdu_cmd: MPDU RX command ID, must be assigned by opmode before + * starting the firmware, used for tracing + * @rx_mpdu_cmd_hdr_size: used for tracing, amount of data before the + * start of the 802.11 header in the @rx_mpdu_cmd + * @dflt_pwr_limit: default power limit fetched from the platform (ACPI) + * @system_pm_mode: the system-wide power management mode in use. + * This mode is set dynamically, depending on the WoWLAN values + * configured from the userspace at runtime. + * @iwl_trans_txqs: transport tx queues data. + */ +struct iwl_trans { + const struct iwl_trans_ops *ops; + struct iwl_op_mode *op_mode; + const struct iwl_cfg_trans_params *trans_cfg; + const struct iwl_cfg *cfg; + struct iwl_drv *drv; + enum iwl_trans_state state; + unsigned long status; + + struct device *dev; + u32 max_skb_frags; + u32 hw_rev; + u32 hw_rf_id; + u32 hw_id; + char hw_id_str[52]; + u32 sku_id[3]; + + u8 rx_mpdu_cmd, rx_mpdu_cmd_hdr_size; + + bool pm_support; + bool ltr_enabled; + u8 pnvm_loaded:1; + u8 reduce_power_loaded:1; + + const struct iwl_hcmd_arr *command_groups; + int command_groups_size; + bool wide_cmd_header; + + wait_queue_head_t wait_command_queue; + u8 num_rx_queues; + + size_t iml_len; + u8 *iml; + + /* The following fields are internal only */ + struct kmem_cache *dev_cmd_pool; + char dev_cmd_pool_name[50]; + + struct dentry *dbgfs_dir; + +#ifdef CONFIG_LOCKDEP + struct lockdep_map sync_cmd_lockdep_map; +#endif + + struct iwl_trans_debug dbg; + struct iwl_self_init_dram init_dram; + + enum iwl_plat_pm_mode system_pm_mode; + + const char *name; + struct iwl_trans_txqs txqs; + + /* pointer to trans specific struct */ + /*Ensure that this pointer will always be aligned to sizeof pointer */ + char trans_specific[] __aligned(sizeof(void *)); +}; + +const char *iwl_get_cmd_string(struct iwl_trans *trans, u32 id); +int iwl_cmd_groups_verify_sorted(const struct iwl_trans_config *trans); + +static inline void iwl_trans_configure(struct iwl_trans *trans, + const struct iwl_trans_config *trans_cfg) +{ + trans->op_mode = trans_cfg->op_mode; + + trans->ops->configure(trans, trans_cfg); + WARN_ON(iwl_cmd_groups_verify_sorted(trans_cfg)); +} + +static inline int iwl_trans_start_hw(struct iwl_trans *trans) +{ + might_sleep(); + + return trans->ops->start_hw(trans); +} + +static inline void iwl_trans_op_mode_leave(struct iwl_trans *trans) +{ + might_sleep(); + + if (trans->ops->op_mode_leave) + trans->ops->op_mode_leave(trans); + + trans->op_mode = NULL; + + trans->state = IWL_TRANS_NO_FW; +} + +static inline void iwl_trans_fw_alive(struct iwl_trans *trans, u32 scd_addr) +{ + might_sleep(); + + trans->state = IWL_TRANS_FW_ALIVE; + + trans->ops->fw_alive(trans, scd_addr); +} + +static inline int iwl_trans_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, + bool run_in_rfkill) +{ + int ret; + + might_sleep(); + + WARN_ON_ONCE(!trans->rx_mpdu_cmd); + + clear_bit(STATUS_FW_ERROR, &trans->status); + ret = trans->ops->start_fw(trans, fw, run_in_rfkill); + if (ret == 0) + trans->state = IWL_TRANS_FW_STARTED; + + return ret; +} + +static inline void iwl_trans_stop_device(struct iwl_trans *trans) +{ + might_sleep(); + + trans->ops->stop_device(trans); + + trans->state = IWL_TRANS_NO_FW; +} + +static inline int iwl_trans_d3_suspend(struct iwl_trans *trans, bool test, + bool reset) +{ + might_sleep(); + if (!trans->ops->d3_suspend) + return 0; + + return trans->ops->d3_suspend(trans, test, reset); +} + +static inline int iwl_trans_d3_resume(struct iwl_trans *trans, + enum iwl_d3_status *status, + bool test, bool reset) +{ + might_sleep(); + if (!trans->ops->d3_resume) + return 0; + + return trans->ops->d3_resume(trans, status, test, reset); +} + +static inline struct iwl_trans_dump_data * +iwl_trans_dump_data(struct iwl_trans *trans, u32 dump_mask, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx) +{ + if (!trans->ops->dump_data) + return NULL; + return trans->ops->dump_data(trans, dump_mask, + sanitize_ops, sanitize_ctx); +} + +static inline struct iwl_device_tx_cmd * +iwl_trans_alloc_tx_cmd(struct iwl_trans *trans) +{ + return kmem_cache_zalloc(trans->dev_cmd_pool, GFP_ATOMIC); +} + +int iwl_trans_send_cmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd); + +static inline void iwl_trans_free_tx_cmd(struct iwl_trans *trans, + struct iwl_device_tx_cmd *dev_cmd) +{ + kmem_cache_free(trans->dev_cmd_pool, dev_cmd); +} + +static inline int iwl_trans_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int queue) +{ + if (unlikely(test_bit(STATUS_FW_ERROR, &trans->status))) + return -EIO; + + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + return trans->ops->tx(trans, skb, dev_cmd, queue); +} + +static inline void iwl_trans_reclaim(struct iwl_trans *trans, int queue, + int ssn, struct sk_buff_head *skbs) +{ + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return; + } + + trans->ops->reclaim(trans, queue, ssn, skbs); +} + +static inline void iwl_trans_set_q_ptrs(struct iwl_trans *trans, int queue, + int ptr) +{ + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return; + } + + trans->ops->set_q_ptrs(trans, queue, ptr); +} + +static inline void iwl_trans_txq_disable(struct iwl_trans *trans, int queue, + bool configure_scd) +{ + trans->ops->txq_disable(trans, queue, configure_scd); +} + +static inline bool +iwl_trans_txq_enable_cfg(struct iwl_trans *trans, int queue, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int queue_wdg_timeout) +{ + might_sleep(); + + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return false; + } + + return trans->ops->txq_enable(trans, queue, ssn, + cfg, queue_wdg_timeout); +} + +static inline int +iwl_trans_get_rxq_dma_data(struct iwl_trans *trans, int queue, + struct iwl_trans_rxq_dma_data *data) +{ + if (WARN_ON_ONCE(!trans->ops->rxq_dma_data)) + return -ENOTSUPP; + + return trans->ops->rxq_dma_data(trans, queue, data); +} + +static inline void +iwl_trans_txq_free(struct iwl_trans *trans, int queue) +{ + if (WARN_ON_ONCE(!trans->ops->txq_free)) + return; + + trans->ops->txq_free(trans, queue); +} + +static inline int +iwl_trans_txq_alloc(struct iwl_trans *trans, + __le16 flags, u8 sta_id, u8 tid, + int cmd_id, int size, + unsigned int wdg_timeout) +{ + might_sleep(); + + if (WARN_ON_ONCE(!trans->ops->txq_alloc)) + return -ENOTSUPP; + + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + return trans->ops->txq_alloc(trans, flags, sta_id, tid, + cmd_id, size, wdg_timeout); +} + +static inline void iwl_trans_txq_set_shared_mode(struct iwl_trans *trans, + int queue, bool shared_mode) +{ + if (trans->ops->txq_set_shared_mode) + trans->ops->txq_set_shared_mode(trans, queue, shared_mode); +} + +static inline void iwl_trans_txq_enable(struct iwl_trans *trans, int queue, + int fifo, int sta_id, int tid, + int frame_limit, u16 ssn, + unsigned int queue_wdg_timeout) +{ + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = fifo, + .sta_id = sta_id, + .tid = tid, + .frame_limit = frame_limit, + .aggregate = sta_id >= 0, + }; + + iwl_trans_txq_enable_cfg(trans, queue, ssn, &cfg, queue_wdg_timeout); +} + +static inline +void iwl_trans_ac_txq_enable(struct iwl_trans *trans, int queue, int fifo, + unsigned int queue_wdg_timeout) +{ + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = fifo, + .sta_id = -1, + .tid = IWL_MAX_TID_COUNT, + .frame_limit = IWL_FRAME_LIMIT, + .aggregate = false, + }; + + iwl_trans_txq_enable_cfg(trans, queue, 0, &cfg, queue_wdg_timeout); +} + +static inline void iwl_trans_freeze_txq_timer(struct iwl_trans *trans, + unsigned long txqs, + bool freeze) +{ + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return; + } + + if (trans->ops->freeze_txq_timer) + trans->ops->freeze_txq_timer(trans, txqs, freeze); +} + +static inline void iwl_trans_block_txq_ptrs(struct iwl_trans *trans, + bool block) +{ + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return; + } + + if (trans->ops->block_txq_ptrs) + trans->ops->block_txq_ptrs(trans, block); +} + +static inline int iwl_trans_wait_tx_queues_empty(struct iwl_trans *trans, + u32 txqs) +{ + if (WARN_ON_ONCE(!trans->ops->wait_tx_queues_empty)) + return -ENOTSUPP; + + /* No need to wait if the firmware is not alive */ + if (trans->state != IWL_TRANS_FW_ALIVE) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + return trans->ops->wait_tx_queues_empty(trans, txqs); +} + +static inline int iwl_trans_wait_txq_empty(struct iwl_trans *trans, int queue) +{ + if (WARN_ON_ONCE(!trans->ops->wait_txq_empty)) + return -ENOTSUPP; + + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + return trans->ops->wait_txq_empty(trans, queue); +} + +static inline void iwl_trans_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + trans->ops->write8(trans, ofs, val); +} + +static inline void iwl_trans_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + trans->ops->write32(trans, ofs, val); +} + +static inline u32 iwl_trans_read32(struct iwl_trans *trans, u32 ofs) +{ + return trans->ops->read32(trans, ofs); +} + +static inline u32 iwl_trans_read_prph(struct iwl_trans *trans, u32 ofs) +{ + return trans->ops->read_prph(trans, ofs); +} + +static inline void iwl_trans_write_prph(struct iwl_trans *trans, u32 ofs, + u32 val) +{ + return trans->ops->write_prph(trans, ofs, val); +} + +static inline int iwl_trans_read_mem(struct iwl_trans *trans, u32 addr, + void *buf, int dwords) +{ + return trans->ops->read_mem(trans, addr, buf, dwords); +} + +#define iwl_trans_read_mem_bytes(trans, addr, buf, bufsize) \ + do { \ + if (__builtin_constant_p(bufsize)) \ + BUILD_BUG_ON((bufsize) % sizeof(u32)); \ + iwl_trans_read_mem(trans, addr, buf, (bufsize) / sizeof(u32));\ + } while (0) + +static inline u32 iwl_trans_read_mem32(struct iwl_trans *trans, u32 addr) +{ + u32 value; + + if (WARN_ON(iwl_trans_read_mem(trans, addr, &value, 1))) + return 0xa5a5a5a5; + + return value; +} + +static inline int iwl_trans_write_mem(struct iwl_trans *trans, u32 addr, + const void *buf, int dwords) +{ + return trans->ops->write_mem(trans, addr, buf, dwords); +} + +static inline u32 iwl_trans_write_mem32(struct iwl_trans *trans, u32 addr, + u32 val) +{ + return iwl_trans_write_mem(trans, addr, &val, 1); +} + +static inline void iwl_trans_set_pmi(struct iwl_trans *trans, bool state) +{ + if (trans->ops->set_pmi) + trans->ops->set_pmi(trans, state); +} + +static inline void iwl_trans_sw_reset(struct iwl_trans *trans) +{ + if (trans->ops->sw_reset) + trans->ops->sw_reset(trans); +} + +static inline void +iwl_trans_set_bits_mask(struct iwl_trans *trans, u32 reg, u32 mask, u32 value) +{ + trans->ops->set_bits_mask(trans, reg, mask, value); +} + +#define iwl_trans_grab_nic_access(trans) \ + __cond_lock(nic_access, \ + likely((trans)->ops->grab_nic_access(trans))) + +static inline void __releases(nic_access) +iwl_trans_release_nic_access(struct iwl_trans *trans) +{ + trans->ops->release_nic_access(trans); + __release(nic_access); +} + +static inline void iwl_trans_fw_error(struct iwl_trans *trans, bool sync) +{ + if (WARN_ON_ONCE(!trans->op_mode)) + return; + + /* prevent double restarts due to the same erroneous FW */ + if (!test_and_set_bit(STATUS_FW_ERROR, &trans->status)) { + iwl_op_mode_nic_error(trans->op_mode, sync); + trans->state = IWL_TRANS_NO_FW; + } +} + +static inline bool iwl_trans_fw_running(struct iwl_trans *trans) +{ + return trans->state == IWL_TRANS_FW_ALIVE; +} + +static inline void iwl_trans_sync_nmi(struct iwl_trans *trans) +{ + if (trans->ops->sync_nmi) + trans->ops->sync_nmi(trans); +} + +void iwl_trans_sync_nmi_with_addr(struct iwl_trans *trans, u32 inta_addr, + u32 sw_err_bit); + +static inline int iwl_trans_set_pnvm(struct iwl_trans *trans, + const void *data, u32 len) +{ + if (trans->ops->set_pnvm) { + int ret = trans->ops->set_pnvm(trans, data, len); + + if (ret) + return ret; + } + + trans->pnvm_loaded = true; + + return 0; +} + +static inline int iwl_trans_set_reduce_power(struct iwl_trans *trans, + const void *data, u32 len) +{ + if (trans->ops->set_reduce_power) { + int ret = trans->ops->set_reduce_power(trans, data, len); + + if (ret) + return ret; + } + + trans->reduce_power_loaded = true; + return 0; +} + +static inline bool iwl_trans_dbg_ini_valid(struct iwl_trans *trans) +{ + return trans->dbg.internal_ini_cfg != IWL_INI_CFG_STATE_NOT_LOADED || + trans->dbg.external_ini_cfg != IWL_INI_CFG_STATE_NOT_LOADED; +} + +static inline void iwl_trans_interrupts(struct iwl_trans *trans, bool enable) +{ + if (trans->ops->interrupts) + trans->ops->interrupts(trans, enable); +} + +/***************************************************** + * transport helper functions + *****************************************************/ +struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, + struct device *dev, + const struct iwl_trans_ops *ops, + const struct iwl_cfg_trans_params *cfg_trans); +int iwl_trans_init(struct iwl_trans *trans); +void iwl_trans_free(struct iwl_trans *trans); + +/***************************************************** +* driver (transport) register/unregister functions +******************************************************/ +int __must_check iwl_pci_register_driver(void); +void iwl_pci_unregister_driver(void); + +#endif /* __iwl_trans_h__ */ diff --git a/sys/contrib/dev/iwlwifi/mvm/binding.c b/sys/contrib/dev/iwlwifi/mvm/binding.c new file mode 100644 index 00000000000..0aac306304c --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/binding.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2020 Intel Corporation + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#include +#include "fw-api.h" +#include "mvm.h" + +struct iwl_mvm_iface_iterator_data { + struct ieee80211_vif *ignore_vif; + int idx; + + struct iwl_mvm_phy_ctxt *phyctxt; + + u16 ids[MAX_MACS_IN_BINDING]; + u16 colors[MAX_MACS_IN_BINDING]; +}; + +static int iwl_mvm_binding_cmd(struct iwl_mvm *mvm, u32 action, + struct iwl_mvm_iface_iterator_data *data) +{ + struct iwl_binding_cmd cmd; + struct iwl_mvm_phy_ctxt *phyctxt = data->phyctxt; + int i, ret; + u32 status; + int size; + + memset(&cmd, 0, sizeof(cmd)); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT)) { + size = sizeof(cmd); + cmd.lmac_id = cpu_to_le32(iwl_mvm_get_lmac_id(mvm->fw, + phyctxt->channel->band)); + } else { + size = IWL_BINDING_CMD_SIZE_V1; + } + + cmd.id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(phyctxt->id, + phyctxt->color)); + cmd.action = cpu_to_le32(action); + cmd.phy = cpu_to_le32(FW_CMD_ID_AND_COLOR(phyctxt->id, + phyctxt->color)); + + for (i = 0; i < MAX_MACS_IN_BINDING; i++) + cmd.macs[i] = cpu_to_le32(FW_CTXT_INVALID); + for (i = 0; i < data->idx; i++) + cmd.macs[i] = cpu_to_le32(FW_CMD_ID_AND_COLOR(data->ids[i], + data->colors[i])); + + status = 0; + ret = iwl_mvm_send_cmd_pdu_status(mvm, BINDING_CONTEXT_CMD, + size, &cmd, &status); + if (ret) { + IWL_ERR(mvm, "Failed to send binding (action:%d): %d\n", + action, ret); + return ret; + } + + if (status) { + IWL_ERR(mvm, "Binding command failed: %u\n", status); + ret = -EIO; + } + + return ret; +} + +static void iwl_mvm_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif == data->ignore_vif) + return; + + if (mvmvif->phy_ctxt != data->phyctxt) + return; + + if (WARN_ON_ONCE(data->idx >= MAX_MACS_IN_BINDING)) + return; + + data->ids[data->idx] = mvmvif->id; + data->colors[data->idx] = mvmvif->color; + data->idx++; +} + +static int iwl_mvm_binding_update(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_phy_ctxt *phyctxt, + bool add) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_iface_iterator_data data = { + .ignore_vif = vif, + .phyctxt = phyctxt, + }; + u32 action = FW_CTXT_ACTION_MODIFY; + + lockdep_assert_held(&mvm->mutex); + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_iface_iterator, + &data); + + /* + * If there are no other interfaces yet we + * need to create a new binding. + */ + if (data.idx == 0) { + if (add) + action = FW_CTXT_ACTION_ADD; + else + action = FW_CTXT_ACTION_REMOVE; + } + + if (add) { + if (WARN_ON_ONCE(data.idx >= MAX_MACS_IN_BINDING)) + return -EINVAL; + + data.ids[data.idx] = mvmvif->id; + data.colors[data.idx] = mvmvif->color; + data.idx++; + } + + return iwl_mvm_binding_cmd(mvm, action, &data); +} + +int iwl_mvm_binding_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) + return -EINVAL; + + /* + * Update SF - Disable if needed. if this fails, SF might still be on + * while many macs are bound, which is forbidden - so fail the binding. + */ + if (iwl_mvm_sf_update(mvm, vif, false)) + return -EINVAL; + + return iwl_mvm_binding_update(mvm, vif, mvmvif->phy_ctxt, true); +} + +int iwl_mvm_binding_remove_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) + return -EINVAL; + + ret = iwl_mvm_binding_update(mvm, vif, mvmvif->phy_ctxt, false); + + if (!ret) + if (iwl_mvm_sf_update(mvm, vif, true)) + IWL_ERR(mvm, "Failed to update SF state\n"); + + return ret; +} diff --git a/sys/contrib/dev/iwlwifi/mvm/coex.c b/sys/contrib/dev/iwlwifi/mvm/coex.c new file mode 100644 index 00000000000..9b194cb8d65 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/coex.c @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2013-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + */ +#include +#include +#include + +#include "fw/api/coex.h" +#include "iwl-modparams.h" +#include "mvm.h" +#include "iwl-debug.h" + +/* 20MHz / 40MHz below / 40Mhz above*/ +static const __le64 iwl_ci_mask[][3] = { + /* dummy entry for channel 0 */ + {cpu_to_le64(0), cpu_to_le64(0), cpu_to_le64(0)}, + { + cpu_to_le64(0x0000001FFFULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x00007FFFFFULL), + }, + { + cpu_to_le64(0x000000FFFFULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x0003FFFFFFULL), + }, + { + cpu_to_le64(0x000003FFFCULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x000FFFFFFCULL), + }, + { + cpu_to_le64(0x00001FFFE0ULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x007FFFFFE0ULL), + }, + { + cpu_to_le64(0x00007FFF80ULL), + cpu_to_le64(0x00007FFFFFULL), + cpu_to_le64(0x01FFFFFF80ULL), + }, + { + cpu_to_le64(0x0003FFFC00ULL), + cpu_to_le64(0x0003FFFFFFULL), + cpu_to_le64(0x0FFFFFFC00ULL), + }, + { + cpu_to_le64(0x000FFFF000ULL), + cpu_to_le64(0x000FFFFFFCULL), + cpu_to_le64(0x3FFFFFF000ULL), + }, + { + cpu_to_le64(0x007FFF8000ULL), + cpu_to_le64(0x007FFFFFE0ULL), + cpu_to_le64(0xFFFFFF8000ULL), + }, + { + cpu_to_le64(0x01FFFE0000ULL), + cpu_to_le64(0x01FFFFFF80ULL), + cpu_to_le64(0xFFFFFE0000ULL), + }, + { + cpu_to_le64(0x0FFFF00000ULL), + cpu_to_le64(0x0FFFFFFC00ULL), + cpu_to_le64(0x0ULL), + }, + { + cpu_to_le64(0x3FFFC00000ULL), + cpu_to_le64(0x3FFFFFF000ULL), + cpu_to_le64(0x0) + }, + { + cpu_to_le64(0xFFFE000000ULL), + cpu_to_le64(0xFFFFFF8000ULL), + cpu_to_le64(0x0) + }, + { + cpu_to_le64(0xFFF8000000ULL), + cpu_to_le64(0xFFFFFE0000ULL), + cpu_to_le64(0x0) + }, + { + cpu_to_le64(0xFE00000000ULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x0ULL) + }, +}; + +static enum iwl_bt_coex_lut_type +iwl_get_coex_type(struct iwl_mvm *mvm, const struct ieee80211_vif *vif) +{ + struct ieee80211_chanctx_conf *chanctx_conf; + enum iwl_bt_coex_lut_type ret; + u16 phy_ctx_id; + u32 primary_ch_phy_id, secondary_ch_phy_id; + + /* + * Checking that we hold mvm->mutex is a good idea, but the rate + * control can't acquire the mutex since it runs in Tx path. + * So this is racy in that case, but in the worst case, the AMPDU + * size limit will be wrong for a short time which is not a big + * issue. + */ + + rcu_read_lock(); + + chanctx_conf = rcu_dereference(vif->chanctx_conf); + + if (!chanctx_conf || + chanctx_conf->def.chan->band != NL80211_BAND_2GHZ) { + rcu_read_unlock(); + return BT_COEX_INVALID_LUT; + } + + ret = BT_COEX_TX_DIS_LUT; + + if (mvm->cfg->bt_shared_single_ant) { + rcu_read_unlock(); + return ret; + } + + phy_ctx_id = *((u16 *)chanctx_conf->drv_priv); + primary_ch_phy_id = le32_to_cpu(mvm->last_bt_ci_cmd.primary_ch_phy_id); + secondary_ch_phy_id = + le32_to_cpu(mvm->last_bt_ci_cmd.secondary_ch_phy_id); + + if (primary_ch_phy_id == phy_ctx_id) + ret = le32_to_cpu(mvm->last_bt_notif.primary_ch_lut); + else if (secondary_ch_phy_id == phy_ctx_id) + ret = le32_to_cpu(mvm->last_bt_notif.secondary_ch_lut); + /* else - default = TX TX disallowed */ + + rcu_read_unlock(); + + return ret; +} + +int iwl_mvm_send_bt_init_conf(struct iwl_mvm *mvm) +{ + struct iwl_bt_coex_cmd bt_cmd = {}; + u32 mode; + + lockdep_assert_held(&mvm->mutex); + + if (unlikely(mvm->bt_force_ant_mode != BT_FORCE_ANT_DIS)) { + switch (mvm->bt_force_ant_mode) { + case BT_FORCE_ANT_BT: + mode = BT_COEX_BT; + break; + case BT_FORCE_ANT_WIFI: + mode = BT_COEX_WIFI; + break; + default: + WARN_ON(1); + mode = 0; + } + + bt_cmd.mode = cpu_to_le32(mode); + goto send_cmd; + } + + bt_cmd.mode = cpu_to_le32(BT_COEX_NW); + + if (IWL_MVM_BT_COEX_SYNC2SCO) + bt_cmd.enabled_modules |= + cpu_to_le32(BT_COEX_SYNC2SCO_ENABLED); + + if (iwl_mvm_is_mplut_supported(mvm)) + bt_cmd.enabled_modules |= cpu_to_le32(BT_COEX_MPLUT_ENABLED); + + bt_cmd.enabled_modules |= cpu_to_le32(BT_COEX_HIGH_BAND_RET); + +send_cmd: + memset(&mvm->last_bt_notif, 0, sizeof(mvm->last_bt_notif)); + memset(&mvm->last_bt_ci_cmd, 0, sizeof(mvm->last_bt_ci_cmd)); + + return iwl_mvm_send_cmd_pdu(mvm, BT_CONFIG, 0, sizeof(bt_cmd), &bt_cmd); +} + +static int iwl_mvm_bt_coex_reduced_txp(struct iwl_mvm *mvm, u8 sta_id, + bool enable) +{ + struct iwl_bt_coex_reduced_txp_update_cmd cmd = {}; + struct iwl_mvm_sta *mvmsta; + u32 value; + + mvmsta = iwl_mvm_sta_from_staid_protected(mvm, sta_id); + if (!mvmsta) + return 0; + + /* nothing to do */ + if (mvmsta->bt_reduced_txpower == enable) + return 0; + + value = mvmsta->sta_id; + + if (enable) + value |= BT_REDUCED_TX_POWER_BIT; + + IWL_DEBUG_COEX(mvm, "%sable reduced Tx Power for sta %d\n", + enable ? "en" : "dis", sta_id); + + cmd.reduced_txp = cpu_to_le32(value); + mvmsta->bt_reduced_txpower = enable; + + return iwl_mvm_send_cmd_pdu(mvm, BT_COEX_UPDATE_REDUCED_TXP, + CMD_ASYNC, sizeof(cmd), &cmd); +} + +struct iwl_bt_iterator_data { + struct iwl_bt_coex_profile_notif *notif; + struct iwl_mvm *mvm; + struct ieee80211_chanctx_conf *primary; + struct ieee80211_chanctx_conf *secondary; + bool primary_ll; + u8 primary_load; + u8 secondary_load; +}; + +static inline +void iwl_mvm_bt_coex_enable_rssi_event(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool enable, int rssi) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->bf_data.last_bt_coex_event = rssi; + mvmvif->bf_data.bt_coex_max_thold = + enable ? -IWL_MVM_BT_COEX_EN_RED_TXP_THRESH : 0; + mvmvif->bf_data.bt_coex_min_thold = + enable ? -IWL_MVM_BT_COEX_DIS_RED_TXP_THRESH : 0; +} + +#define MVM_COEX_TCM_PERIOD (HZ * 10) + +static void iwl_mvm_bt_coex_tcm_based_ci(struct iwl_mvm *mvm, + struct iwl_bt_iterator_data *data) +{ + unsigned long now = jiffies; + + if (!time_after(now, mvm->bt_coex_last_tcm_ts + MVM_COEX_TCM_PERIOD)) + return; + + mvm->bt_coex_last_tcm_ts = now; + + /* We assume here that we don't have more than 2 vifs on 2.4GHz */ + + /* if the primary is low latency, it will stay primary */ + if (data->primary_ll) + return; + + if (data->primary_load >= data->secondary_load) + return; + + swap(data->primary, data->secondary); +} + +/* must be called under rcu_read_lock */ +static void iwl_mvm_bt_notif_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_bt_iterator_data *data = _data; + struct iwl_mvm *mvm = data->mvm; + struct ieee80211_chanctx_conf *chanctx_conf; + /* default smps_mode is AUTOMATIC - only used for client modes */ + enum ieee80211_smps_mode smps_mode = IEEE80211_SMPS_AUTOMATIC; + u32 bt_activity_grading, min_ag_for_static_smps; + int ave_rssi; + + lockdep_assert_held(&mvm->mutex); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + break; + case NL80211_IFTYPE_AP: + if (!mvmvif->ap_ibss_active) + return; + break; + default: + return; + } + + chanctx_conf = rcu_dereference(vif->chanctx_conf); + + /* If channel context is invalid or not on 2.4GHz .. */ + if ((!chanctx_conf || + chanctx_conf->def.chan->band != NL80211_BAND_2GHZ)) { + if (vif->type == NL80211_IFTYPE_STATION) { + /* ... relax constraints and disable rssi events */ + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_BT_COEX, + smps_mode); + iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, + false); + iwl_mvm_bt_coex_enable_rssi_event(mvm, vif, false, 0); + } + return; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2)) + min_ag_for_static_smps = BT_VERY_HIGH_TRAFFIC; + else + min_ag_for_static_smps = BT_HIGH_TRAFFIC; + + bt_activity_grading = le32_to_cpu(data->notif->bt_activity_grading); + if (bt_activity_grading >= min_ag_for_static_smps) + smps_mode = IEEE80211_SMPS_STATIC; + else if (bt_activity_grading >= BT_LOW_TRAFFIC) + smps_mode = IEEE80211_SMPS_DYNAMIC; + + /* relax SMPS constraints for next association */ + if (!vif->bss_conf.assoc) + smps_mode = IEEE80211_SMPS_AUTOMATIC; + + if (mvmvif->phy_ctxt && + (mvm->last_bt_notif.rrc_status & BIT(mvmvif->phy_ctxt->id))) + smps_mode = IEEE80211_SMPS_AUTOMATIC; + + IWL_DEBUG_COEX(data->mvm, + "mac %d: bt_activity_grading %d smps_req %d\n", + mvmvif->id, bt_activity_grading, smps_mode); + + if (vif->type == NL80211_IFTYPE_STATION) + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_BT_COEX, + smps_mode); + + /* low latency is always primary */ + if (iwl_mvm_vif_low_latency(mvmvif)) { + data->primary_ll = true; + + data->secondary = data->primary; + data->primary = chanctx_conf; + } + + if (vif->type == NL80211_IFTYPE_AP) { + if (!mvmvif->ap_ibss_active) + return; + + if (chanctx_conf == data->primary) + return; + + if (!data->primary_ll) { + /* + * downgrade the current primary no matter what its + * type is. + */ + data->secondary = data->primary; + data->primary = chanctx_conf; + } else { + /* there is low latency vif - we will be secondary */ + data->secondary = chanctx_conf; + } + + if (data->primary == chanctx_conf) + data->primary_load = mvm->tcm.result.load[mvmvif->id]; + else if (data->secondary == chanctx_conf) + data->secondary_load = mvm->tcm.result.load[mvmvif->id]; + return; + } + + /* + * STA / P2P Client, try to be primary if first vif. If we are in low + * latency mode, we are already in primary and just don't do much + */ + if (!data->primary || data->primary == chanctx_conf) + data->primary = chanctx_conf; + else if (!data->secondary) + /* if secondary is not NULL, it might be a GO */ + data->secondary = chanctx_conf; + + if (data->primary == chanctx_conf) + data->primary_load = mvm->tcm.result.load[mvmvif->id]; + else if (data->secondary == chanctx_conf) + data->secondary_load = mvm->tcm.result.load[mvmvif->id]; + /* + * don't reduce the Tx power if one of these is true: + * we are in LOOSE + * single share antenna product + * BT is inactive + * we are not associated + */ + if (iwl_get_coex_type(mvm, vif) == BT_COEX_LOOSE_LUT || + mvm->cfg->bt_shared_single_ant || !vif->bss_conf.assoc || + le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) == BT_OFF) { + iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, false); + iwl_mvm_bt_coex_enable_rssi_event(mvm, vif, false, 0); + return; + } + + /* try to get the avg rssi from fw */ + ave_rssi = mvmvif->bf_data.ave_beacon_signal; + + /* if the RSSI isn't valid, fake it is very low */ + if (!ave_rssi) + ave_rssi = -100; + if (ave_rssi > -IWL_MVM_BT_COEX_EN_RED_TXP_THRESH) { + if (iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, true)) + IWL_ERR(mvm, "Couldn't send BT_CONFIG cmd\n"); + } else if (ave_rssi < -IWL_MVM_BT_COEX_DIS_RED_TXP_THRESH) { + if (iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, false)) + IWL_ERR(mvm, "Couldn't send BT_CONFIG cmd\n"); + } + + /* Begin to monitor the RSSI: it may influence the reduced Tx power */ + iwl_mvm_bt_coex_enable_rssi_event(mvm, vif, true, ave_rssi); +} + +static void iwl_mvm_bt_coex_notif_handle(struct iwl_mvm *mvm) +{ + struct iwl_bt_iterator_data data = { + .mvm = mvm, + .notif = &mvm->last_bt_notif, + }; + struct iwl_bt_coex_ci_cmd cmd = {}; + u8 ci_bw_idx; + + /* Ignore updates if we are in force mode */ + if (unlikely(mvm->bt_force_ant_mode != BT_FORCE_ANT_DIS)) + return; + + rcu_read_lock(); + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bt_notif_iterator, &data); + + iwl_mvm_bt_coex_tcm_based_ci(mvm, &data); + + if (data.primary) { + struct ieee80211_chanctx_conf *chan = data.primary; + if (WARN_ON(!chan->def.chan)) { + rcu_read_unlock(); + return; + } + + if (chan->def.width < NL80211_CHAN_WIDTH_40) { + ci_bw_idx = 0; + } else { + if (chan->def.center_freq1 > + chan->def.chan->center_freq) + ci_bw_idx = 2; + else + ci_bw_idx = 1; + } + + cmd.bt_primary_ci = + iwl_ci_mask[chan->def.chan->hw_value][ci_bw_idx]; + cmd.primary_ch_phy_id = + cpu_to_le32(*((u16 *)data.primary->drv_priv)); + } + + if (data.secondary) { + struct ieee80211_chanctx_conf *chan = data.secondary; + if (WARN_ON(!data.secondary->def.chan)) { + rcu_read_unlock(); + return; + } + + if (chan->def.width < NL80211_CHAN_WIDTH_40) { + ci_bw_idx = 0; + } else { + if (chan->def.center_freq1 > + chan->def.chan->center_freq) + ci_bw_idx = 2; + else + ci_bw_idx = 1; + } + + cmd.bt_secondary_ci = + iwl_ci_mask[chan->def.chan->hw_value][ci_bw_idx]; + cmd.secondary_ch_phy_id = + cpu_to_le32(*((u16 *)data.secondary->drv_priv)); + } + + rcu_read_unlock(); + + /* Don't spam the fw with the same command over and over */ + if (memcmp(&cmd, &mvm->last_bt_ci_cmd, sizeof(cmd))) { + if (iwl_mvm_send_cmd_pdu(mvm, BT_COEX_CI, 0, + sizeof(cmd), &cmd)) + IWL_ERR(mvm, "Failed to send BT_CI cmd\n"); + memcpy(&mvm->last_bt_ci_cmd, &cmd, sizeof(cmd)); + } +} + +void iwl_mvm_rx_bt_coex_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_bt_coex_profile_notif *notif = (void *)pkt->data; + + IWL_DEBUG_COEX(mvm, "BT Coex Notification received\n"); + IWL_DEBUG_COEX(mvm, "\tBT ci compliance %d\n", notif->bt_ci_compliance); + IWL_DEBUG_COEX(mvm, "\tBT primary_ch_lut %d\n", + le32_to_cpu(notif->primary_ch_lut)); + IWL_DEBUG_COEX(mvm, "\tBT secondary_ch_lut %d\n", + le32_to_cpu(notif->secondary_ch_lut)); + IWL_DEBUG_COEX(mvm, "\tBT activity grading %d\n", + le32_to_cpu(notif->bt_activity_grading)); + + /* remember this notification for future use: rssi fluctuations */ + memcpy(&mvm->last_bt_notif, notif, sizeof(mvm->last_bt_notif)); + + iwl_mvm_bt_coex_notif_handle(mvm); +} + +void iwl_mvm_bt_rssi_event(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum ieee80211_rssi_event_data rssi_event) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + lockdep_assert_held(&mvm->mutex); + + /* Ignore updates if we are in force mode */ + if (unlikely(mvm->bt_force_ant_mode != BT_FORCE_ANT_DIS)) + return; + + /* + * Rssi update while not associated - can happen since the statistics + * are handled asynchronously + */ + if (mvmvif->ap_sta_id == IWL_MVM_INVALID_STA) + return; + + /* No BT - reports should be disabled */ + if (le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) == BT_OFF) + return; + + IWL_DEBUG_COEX(mvm, "RSSI for %pM is now %s\n", vif->bss_conf.bssid, + rssi_event == RSSI_EVENT_HIGH ? "HIGH" : "LOW"); + + /* + * Check if rssi is good enough for reduced Tx power, but not in loose + * scheme. + */ + if (rssi_event == RSSI_EVENT_LOW || mvm->cfg->bt_shared_single_ant || + iwl_get_coex_type(mvm, vif) == BT_COEX_LOOSE_LUT) + ret = iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, + false); + else + ret = iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, true); + + if (ret) + IWL_ERR(mvm, "couldn't send BT_CONFIG HCMD upon RSSI event\n"); +} + +#define LINK_QUAL_AGG_TIME_LIMIT_DEF (4000) +#define LINK_QUAL_AGG_TIME_LIMIT_BT_ACT (1200) + +u16 iwl_mvm_coex_agg_time_limit(struct iwl_mvm *mvm, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + struct iwl_mvm_phy_ctxt *phy_ctxt = mvmvif->phy_ctxt; + enum iwl_bt_coex_lut_type lut_type; + + if (mvm->last_bt_notif.ttc_status & BIT(phy_ctxt->id)) + return LINK_QUAL_AGG_TIME_LIMIT_DEF; + + if (le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) < + BT_HIGH_TRAFFIC) + return LINK_QUAL_AGG_TIME_LIMIT_DEF; + + lut_type = iwl_get_coex_type(mvm, mvmsta->vif); + + if (lut_type == BT_COEX_LOOSE_LUT || lut_type == BT_COEX_INVALID_LUT) + return LINK_QUAL_AGG_TIME_LIMIT_DEF; + + /* tight coex, high bt traffic, reduce AGG time limit */ + return LINK_QUAL_AGG_TIME_LIMIT_BT_ACT; +} + +bool iwl_mvm_bt_coex_is_mimo_allowed(struct iwl_mvm *mvm, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + struct iwl_mvm_phy_ctxt *phy_ctxt = mvmvif->phy_ctxt; + enum iwl_bt_coex_lut_type lut_type; + + if (mvm->last_bt_notif.ttc_status & BIT(phy_ctxt->id)) + return true; + + if (le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) < + BT_HIGH_TRAFFIC) + return true; + + /* + * In Tight / TxTxDis, BT can't Rx while we Tx, so use both antennas + * since BT is already killed. + * In Loose, BT can Rx while we Tx, so forbid MIMO to let BT Rx while + * we Tx. + * When we are in 5GHz, we'll get BT_COEX_INVALID_LUT allowing MIMO. + */ + lut_type = iwl_get_coex_type(mvm, mvmsta->vif); + return lut_type != BT_COEX_LOOSE_LUT; +} + +bool iwl_mvm_bt_coex_is_ant_avail(struct iwl_mvm *mvm, u8 ant) +{ + /* there is no other antenna, shared antenna is always available */ + if (mvm->cfg->bt_shared_single_ant) + return true; + + if (ant & mvm->cfg->non_shared_ant) + return true; + + return le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) < + BT_HIGH_TRAFFIC; +} + +bool iwl_mvm_bt_coex_is_shared_ant_avail(struct iwl_mvm *mvm) +{ + /* there is no other antenna, shared antenna is always available */ + if (mvm->cfg->bt_shared_single_ant) + return true; + + return le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) < BT_HIGH_TRAFFIC; +} + +bool iwl_mvm_bt_coex_is_tpc_allowed(struct iwl_mvm *mvm, + enum nl80211_band band) +{ + u32 bt_activity = le32_to_cpu(mvm->last_bt_notif.bt_activity_grading); + + if (band != NL80211_BAND_2GHZ) + return false; + + return bt_activity >= BT_LOW_TRAFFIC; +} + +u8 iwl_mvm_bt_coex_get_single_ant_msk(struct iwl_mvm *mvm, u8 enabled_ants) +{ + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2) && + (mvm->cfg->non_shared_ant & enabled_ants)) + return mvm->cfg->non_shared_ant; + + return first_antenna(enabled_ants); +} + +u8 iwl_mvm_bt_coex_tx_prio(struct iwl_mvm *mvm, struct ieee80211_hdr *hdr, + struct ieee80211_tx_info *info, u8 ac) +{ + __le16 fc = hdr->frame_control; + bool mplut_enabled = iwl_mvm_is_mplut_supported(mvm); + + if (info->band != NL80211_BAND_2GHZ) + return 0; + + if (unlikely(mvm->bt_tx_prio)) + return mvm->bt_tx_prio - 1; + + if (likely(ieee80211_is_data(fc))) { + if (likely(ieee80211_is_data_qos(fc))) { + switch (ac) { + case IEEE80211_AC_BE: + return mplut_enabled ? 1 : 0; + case IEEE80211_AC_VI: + return mplut_enabled ? 2 : 3; + case IEEE80211_AC_VO: + return 3; + default: + return 0; + } + } else if (is_multicast_ether_addr(hdr->addr1)) { + return 3; + } else + return 0; + } else if (ieee80211_is_mgmt(fc)) { + return ieee80211_is_disassoc(fc) ? 0 : 3; + } else if (ieee80211_is_ctl(fc)) { + /* ignore cfend and cfendack frames as we never send those */ + return 3; + } + + return 0; +} + +void iwl_mvm_bt_coex_vif_change(struct iwl_mvm *mvm) +{ + iwl_mvm_bt_coex_notif_handle(mvm); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/constants.h b/sys/contrib/dev/iwlwifi/mvm/constants.h new file mode 100644 index 00000000000..9d0d01f27d9 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/constants.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2013-2014, 2018-2021 Intel Corporation + * Copyright (C) 2015 Intel Deutschland GmbH + */ +#ifndef __MVM_CONSTANTS_H +#define __MVM_CONSTANTS_H + +#include +#include "fw-api.h" + +#define IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM 20 + +#define IWL_MVM_DEFAULT_PS_TX_DATA_TIMEOUT (100 * USEC_PER_MSEC) +#define IWL_MVM_DEFAULT_PS_RX_DATA_TIMEOUT (100 * USEC_PER_MSEC) +#define IWL_MVM_WOWLAN_PS_TX_DATA_TIMEOUT (10 * USEC_PER_MSEC) +#define IWL_MVM_WOWLAN_PS_RX_DATA_TIMEOUT (10 * USEC_PER_MSEC) +#define IWL_MVM_SHORT_PS_TX_DATA_TIMEOUT (2 * 1024) /* defined in TU */ +#define IWL_MVM_SHORT_PS_RX_DATA_TIMEOUT (40 * 1024) /* defined in TU */ +#define IWL_MVM_P2P_LOWLATENCY_PS_ENABLE 0 +#define IWL_MVM_UAPSD_RX_DATA_TIMEOUT (50 * USEC_PER_MSEC) +#define IWL_MVM_UAPSD_TX_DATA_TIMEOUT (50 * USEC_PER_MSEC) +#define IWL_MVM_UAPSD_QUEUES (IEEE80211_WMM_IE_STA_QOSINFO_AC_VO |\ + IEEE80211_WMM_IE_STA_QOSINFO_AC_VI |\ + IEEE80211_WMM_IE_STA_QOSINFO_AC_BK |\ + IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) +#define IWL_MVM_PS_HEAVY_TX_THLD_PACKETS 20 +#define IWL_MVM_PS_HEAVY_RX_THLD_PACKETS 8 +#define IWL_MVM_PS_SNOOZE_HEAVY_TX_THLD_PACKETS 30 +#define IWL_MVM_PS_SNOOZE_HEAVY_RX_THLD_PACKETS 20 +#define IWL_MVM_PS_HEAVY_TX_THLD_PERCENT 50 +#define IWL_MVM_PS_HEAVY_RX_THLD_PERCENT 50 +#define IWL_MVM_PS_SNOOZE_INTERVAL 25 +#define IWL_MVM_PS_SNOOZE_WINDOW 50 +#define IWL_MVM_WOWLAN_PS_SNOOZE_WINDOW 25 +#define IWL_MVM_LOWLAT_QUOTA_MIN_PERCENT 64 +#define IWL_MVM_BT_COEX_EN_RED_TXP_THRESH 62 +#define IWL_MVM_BT_COEX_DIS_RED_TXP_THRESH 65 +#define IWL_MVM_BT_COEX_SYNC2SCO 1 +#define IWL_MVM_BT_COEX_MPLUT 1 +#define IWL_MVM_BT_COEX_RRC 1 +#define IWL_MVM_BT_COEX_TTC 1 +#define IWL_MVM_BT_COEX_MPLUT_REG0 0x22002200 +#define IWL_MVM_BT_COEX_MPLUT_REG1 0x11118451 +#define IWL_MVM_BT_COEX_ANTENNA_COUPLING_THRS 30 +#define IWL_MVM_FW_MCAST_FILTER_PASS_ALL 0 +#define IWL_MVM_FW_BCAST_FILTER_PASS_ALL 0 +#define IWL_MVM_QUOTA_THRESHOLD 4 +#define IWL_MVM_RS_RSSI_BASED_INIT_RATE 0 +#define IWL_MVM_RS_80_20_FAR_RANGE_TWEAK 1 +#define IWL_MVM_TOF_IS_RESPONDER 0 +#define IWL_MVM_HW_CSUM_DISABLE 0 +#define IWL_MVM_PARSE_NVM 0 +#define IWL_MVM_ADWELL_ENABLE 1 +#define IWL_MVM_ADWELL_MAX_BUDGET 0 +#define IWL_MVM_TCM_LOAD_MEDIUM_THRESH 10 /* percentage */ +#define IWL_MVM_TCM_LOAD_HIGH_THRESH 50 /* percentage */ +#define IWL_MVM_TCM_LOWLAT_ENABLE_THRESH 100 /* packets/10 seconds */ +#define IWL_MVM_UAPSD_NONAGG_PERIOD 5000 /* msecs */ +#define IWL_MVM_UAPSD_NOAGG_LIST_LEN IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM +#define IWL_MVM_NON_TRANSMITTING_AP 0 +#define IWL_MVM_RS_NUM_TRY_BEFORE_ANT_TOGGLE 1 +#define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE 2 +#define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE_TW 1 +#define IWL_MVM_RS_INITIAL_MIMO_NUM_RATES 3 +#define IWL_MVM_RS_INITIAL_SISO_NUM_RATES 3 +#define IWL_MVM_RS_INITIAL_LEGACY_NUM_RATES 2 +#define IWL_MVM_RS_INITIAL_LEGACY_RETRIES 2 +#define IWL_MVM_RS_SECONDARY_LEGACY_RETRIES 1 +#define IWL_MVM_RS_SECONDARY_LEGACY_NUM_RATES 16 +#define IWL_MVM_RS_SECONDARY_SISO_NUM_RATES 3 +#define IWL_MVM_RS_SECONDARY_SISO_RETRIES 1 +#define IWL_MVM_RS_RATE_MIN_FAILURE_TH 3 +#define IWL_MVM_RS_RATE_MIN_SUCCESS_TH 8 +#define IWL_MVM_RS_STAY_IN_COLUMN_TIMEOUT 5 /* Seconds */ +#define IWL_MVM_RS_IDLE_TIMEOUT 5 /* Seconds */ +#define IWL_MVM_RS_MISSED_RATE_MAX 15 +#define IWL_MVM_RS_LEGACY_FAILURE_LIMIT 160 +#define IWL_MVM_RS_LEGACY_SUCCESS_LIMIT 480 +#define IWL_MVM_RS_LEGACY_TABLE_COUNT 160 +#define IWL_MVM_RS_NON_LEGACY_FAILURE_LIMIT 400 +#define IWL_MVM_RS_NON_LEGACY_SUCCESS_LIMIT 4500 +#define IWL_MVM_RS_NON_LEGACY_TABLE_COUNT 1500 +#define IWL_MVM_RS_SR_FORCE_DECREASE 15 /* percent */ +#define IWL_MVM_RS_SR_NO_DECREASE 85 /* percent */ +#define IWL_MVM_RS_AGG_TIME_LIMIT 4000 /* 4 msecs. valid 100-8000 */ +#define IWL_MVM_RS_AGG_DISABLE_START 3 +#define IWL_MVM_RS_AGG_START_THRESHOLD 10 /* num frames per second */ +#define IWL_MVM_RS_TPC_SR_FORCE_INCREASE 75 /* percent */ +#define IWL_MVM_RS_TPC_SR_NO_INCREASE 85 /* percent */ +#define IWL_MVM_RS_TPC_TX_POWER_STEP 3 +#define IWL_MVM_ENABLE_EBS 1 +#define IWL_MVM_FTM_INITIATOR_ALGO IWL_TOF_ALGO_TYPE_MAX_LIKE +#define IWL_MVM_FTM_INITIATOR_DYNACK true +#define IWL_MVM_FTM_LMR_FEEDBACK_TERMINATE false +#define IWL_MVM_FTM_R2I_MAX_REP 7 +#define IWL_MVM_FTM_I2R_MAX_REP 7 +#define IWL_MVM_FTM_R2I_MAX_STS 1 +#define IWL_MVM_FTM_I2R_MAX_STS 1 +#define IWL_MVM_FTM_R2I_MAX_TOTAL_LTF 3 +#define IWL_MVM_FTM_I2R_MAX_TOTAL_LTF 3 +#define IWL_MVM_FTM_INITIATOR_SECURE_LTF false +#define IWL_MVM_FTM_RESP_NDP_SUPPORT true +#define IWL_MVM_FTM_RESP_LMR_FEEDBACK_SUPPORT true +#define IWL_MVM_FTM_NON_TB_MIN_TIME_BETWEEN_MSR 5 +#define IWL_MVM_FTM_NON_TB_MAX_TIME_BETWEEN_MSR 1000 +#define IWL_MVM_D3_DEBUG false +#define IWL_MVM_USE_TWT true +#define IWL_MVM_AMPDU_CONSEC_DROPS_DELBA 10 +#define IWL_MVM_USE_NSSN_SYNC 0 +#define IWL_MVM_PHY_FILTER_CHAIN_A 0 +#define IWL_MVM_PHY_FILTER_CHAIN_B 0 +#define IWL_MVM_PHY_FILTER_CHAIN_C 0 +#define IWL_MVM_PHY_FILTER_CHAIN_D 0 +#define IWL_MVM_FTM_INITIATOR_ENABLE_SMOOTH false +#define IWL_MVM_FTM_INITIATOR_SMOOTH_ALPHA 40 +/* 20016 pSec is 6 meter RTT, meaning 3 meter range */ +#define IWL_MVM_FTM_INITIATOR_SMOOTH_UNDERSHOOT 20016 +#define IWL_MVM_FTM_INITIATOR_SMOOTH_OVERSHOOT 20016 +#define IWL_MVM_FTM_INITIATOR_SMOOTH_AGE_SEC 2 +#define IWL_MVM_DISABLE_AP_FILS false +#define IWL_MVM_6GHZ_PASSIVE_SCAN_TIMEOUT 3000 /* in seconds */ +#define IWL_MVM_6GHZ_PASSIVE_SCAN_ASSOC_TIMEOUT 60 /* in seconds */ + +#endif /* __MVM_CONSTANTS_H */ diff --git a/sys/contrib/dev/iwlwifi/mvm/ftm-initiator.c b/sys/contrib/dev/iwlwifi/mvm/ftm-initiator.c new file mode 100644 index 00000000000..949fb790f8f --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/ftm-initiator.c @@ -0,0 +1,1368 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#include +#include +#include +#include "mvm.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "constants.h" + +struct iwl_mvm_loc_entry { + struct list_head list; + u8 addr[ETH_ALEN]; + u8 lci_len, civic_len; + u8 buf[]; +}; + +struct iwl_mvm_smooth_entry { + struct list_head list; + u8 addr[ETH_ALEN]; + s64 rtt_avg; + u64 host_time; +}; + +struct iwl_mvm_ftm_pasn_entry { + struct list_head list; + u8 addr[ETH_ALEN]; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + u8 cipher; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; +}; + +int iwl_mvm_ftm_add_pasn_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + u8 *addr, u32 cipher, u8 *tk, u32 tk_len, + u8 *hltk, u32 hltk_len) +{ + struct iwl_mvm_ftm_pasn_entry *pasn = kzalloc(sizeof(*pasn), + GFP_KERNEL); + u32 expected_tk_len; + + lockdep_assert_held(&mvm->mutex); + + if (!pasn) + return -ENOBUFS; + + pasn->cipher = iwl_mvm_cipher_to_location_cipher(cipher); + + switch (pasn->cipher) { + case IWL_LOCATION_CIPHER_CCMP_128: + case IWL_LOCATION_CIPHER_GCMP_128: + expected_tk_len = WLAN_KEY_LEN_CCMP; + break; + case IWL_LOCATION_CIPHER_GCMP_256: + expected_tk_len = WLAN_KEY_LEN_GCMP_256; + break; + default: + goto out; + } + + /* + * If associated to this AP and already have security context, + * the TK is already configured for this station, so it + * shouldn't be set again here. + */ + if (vif->bss_conf.assoc && + !memcmp(addr, vif->bss_conf.bssid, ETH_ALEN)) { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_sta *sta; + + rcu_read_lock(); + sta = rcu_dereference(mvm->fw_id_to_mac_id[mvmvif->ap_sta_id]); + if (!IS_ERR_OR_NULL(sta) && sta->mfp) + expected_tk_len = 0; + rcu_read_unlock(); + } + + if (tk_len != expected_tk_len || hltk_len != sizeof(pasn->hltk)) { + IWL_ERR(mvm, "Invalid key length: tk_len=%u hltk_len=%u\n", + tk_len, hltk_len); + goto out; + } + + memcpy(pasn->addr, addr, sizeof(pasn->addr)); + memcpy(pasn->hltk, hltk, sizeof(pasn->hltk)); + + if (tk && tk_len) + memcpy(pasn->tk, tk, sizeof(pasn->tk)); + + list_add_tail(&pasn->list, &mvm->ftm_initiator.pasn_list); + return 0; +out: + kfree(pasn); + return -EINVAL; +} + +void iwl_mvm_ftm_remove_pasn_sta(struct iwl_mvm *mvm, u8 *addr) +{ + struct iwl_mvm_ftm_pasn_entry *entry, *prev; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry_safe(entry, prev, &mvm->ftm_initiator.pasn_list, + list) { + if (memcmp(entry->addr, addr, sizeof(entry->addr))) + continue; + + list_del(&entry->list); + kfree(entry); + return; + } +} + +static void iwl_mvm_ftm_reset(struct iwl_mvm *mvm) +{ + struct iwl_mvm_loc_entry *e, *t; + + mvm->ftm_initiator.req = NULL; + mvm->ftm_initiator.req_wdev = NULL; + memset(mvm->ftm_initiator.responses, 0, + sizeof(mvm->ftm_initiator.responses)); + + list_for_each_entry_safe(e, t, &mvm->ftm_initiator.loc_list, list) { + list_del(&e->list); + kfree(e); + } +} + +void iwl_mvm_ftm_restart(struct iwl_mvm *mvm) +{ + struct cfg80211_pmsr_result result = { + .status = NL80211_PMSR_STATUS_FAILURE, + .final = 1, + .host_time = ktime_get_boottime_ns(), + .type = NL80211_PMSR_TYPE_FTM, + }; + int i; + + lockdep_assert_held(&mvm->mutex); + + if (!mvm->ftm_initiator.req) + return; + + for (i = 0; i < mvm->ftm_initiator.req->n_peers; i++) { + memcpy(result.addr, mvm->ftm_initiator.req->peers[i].addr, + ETH_ALEN); + result.ftm.burst_index = mvm->ftm_initiator.responses[i]; + + cfg80211_pmsr_report(mvm->ftm_initiator.req_wdev, + mvm->ftm_initiator.req, + &result, GFP_KERNEL); + } + + cfg80211_pmsr_complete(mvm->ftm_initiator.req_wdev, + mvm->ftm_initiator.req, GFP_KERNEL); + iwl_mvm_ftm_reset(mvm); +} + +void iwl_mvm_ftm_initiator_smooth_config(struct iwl_mvm *mvm) +{ + INIT_LIST_HEAD(&mvm->ftm_initiator.smooth.resp); + + IWL_DEBUG_INFO(mvm, + "enable=%u, alpha=%u, age_jiffies=%u, thresh=(%u:%u)\n", + IWL_MVM_FTM_INITIATOR_ENABLE_SMOOTH, + IWL_MVM_FTM_INITIATOR_SMOOTH_ALPHA, + IWL_MVM_FTM_INITIATOR_SMOOTH_AGE_SEC * HZ, + IWL_MVM_FTM_INITIATOR_SMOOTH_OVERSHOOT, + IWL_MVM_FTM_INITIATOR_SMOOTH_UNDERSHOOT); +} + +void iwl_mvm_ftm_initiator_smooth_stop(struct iwl_mvm *mvm) +{ + struct iwl_mvm_smooth_entry *se, *st; + + list_for_each_entry_safe(se, st, &mvm->ftm_initiator.smooth.resp, + list) { + list_del(&se->list); + kfree(se); + } +} + +static int +iwl_ftm_range_request_status_to_err(enum iwl_tof_range_request_status s) +{ + switch (s) { + case IWL_TOF_RANGE_REQUEST_STATUS_SUCCESS: + return 0; + case IWL_TOF_RANGE_REQUEST_STATUS_BUSY: + return -EBUSY; + default: + WARN_ON_ONCE(1); + return -EIO; + } +} + +static void iwl_mvm_ftm_cmd_v5(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_tof_range_req_cmd_v5 *cmd, + struct cfg80211_pmsr_request *req) +{ + int i; + + cmd->request_id = req->cookie; + cmd->num_of_ap = req->n_peers; + + /* use maximum for "no timeout" or bigger than what we can do */ + if (!req->timeout || req->timeout > 255 * 100) + cmd->req_timeout = 255; + else + cmd->req_timeout = DIV_ROUND_UP(req->timeout, 100); + + /* + * We treat it always as random, since if not we'll + * have filled our local address there instead. + */ + cmd->macaddr_random = 1; + memcpy(cmd->macaddr_template, req->mac_addr, ETH_ALEN); + for (i = 0; i < ETH_ALEN; i++) + cmd->macaddr_mask[i] = ~req->mac_addr_mask[i]; + + if (vif->bss_conf.assoc) + memcpy(cmd->range_req_bssid, vif->bss_conf.bssid, ETH_ALEN); + else + eth_broadcast_addr(cmd->range_req_bssid); +} + +static void iwl_mvm_ftm_cmd_common(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_tof_range_req_cmd_v9 *cmd, + struct cfg80211_pmsr_request *req) +{ + int i; + + cmd->initiator_flags = + cpu_to_le32(IWL_TOF_INITIATOR_FLAGS_MACADDR_RANDOM | + IWL_TOF_INITIATOR_FLAGS_NON_ASAP_SUPPORT); + cmd->request_id = req->cookie; + cmd->num_of_ap = req->n_peers; + + /* + * Use a large value for "no timeout". Don't use the maximum value + * because of fw limitations. + */ + if (req->timeout) + cmd->req_timeout_ms = cpu_to_le32(req->timeout); + else + cmd->req_timeout_ms = cpu_to_le32(0xfffff); + + memcpy(cmd->macaddr_template, req->mac_addr, ETH_ALEN); + for (i = 0; i < ETH_ALEN; i++) + cmd->macaddr_mask[i] = ~req->mac_addr_mask[i]; + + if (vif->bss_conf.assoc) { + memcpy(cmd->range_req_bssid, vif->bss_conf.bssid, ETH_ALEN); + + /* AP's TSF is only relevant if associated */ + for (i = 0; i < req->n_peers; i++) { + if (req->peers[i].report_ap_tsf) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(vif); + + cmd->tsf_mac_id = cpu_to_le32(mvmvif->id); + return; + } + } + } else { + eth_broadcast_addr(cmd->range_req_bssid); + } + + /* Don't report AP's TSF */ + cmd->tsf_mac_id = cpu_to_le32(0xff); +} + +static void iwl_mvm_ftm_cmd_v8(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_tof_range_req_cmd_v8 *cmd, + struct cfg80211_pmsr_request *req) +{ + iwl_mvm_ftm_cmd_common(mvm, vif, (void *)cmd, req); +} + +static int +iwl_mvm_ftm_target_chandef_v1(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + u8 *channel, u8 *bandwidth, + u8 *ctrl_ch_position) +{ + u32 freq = peer->chandef.chan->center_freq; + + *channel = ieee80211_frequency_to_channel(freq); + + switch (peer->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + *bandwidth = IWL_TOF_BW_20_LEGACY; + break; + case NL80211_CHAN_WIDTH_20: + *bandwidth = IWL_TOF_BW_20_HT; + break; + case NL80211_CHAN_WIDTH_40: + *bandwidth = IWL_TOF_BW_40; + break; + case NL80211_CHAN_WIDTH_80: + *bandwidth = IWL_TOF_BW_80; + break; + default: + IWL_ERR(mvm, "Unsupported BW in FTM request (%d)\n", + peer->chandef.width); + return -EINVAL; + } + + *ctrl_ch_position = (peer->chandef.width > NL80211_CHAN_WIDTH_20) ? + iwl_mvm_get_ctrl_pos(&peer->chandef) : 0; + + return 0; +} + +static int +iwl_mvm_ftm_target_chandef_v2(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + u8 *channel, u8 *format_bw, + u8 *ctrl_ch_position) +{ + u32 freq = peer->chandef.chan->center_freq; + u8 cmd_ver; + + *channel = ieee80211_frequency_to_channel(freq); + + switch (peer->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + *format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY; + *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_20: + *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; + *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_40: + *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; + *format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_80: + *format_bw = IWL_LOCATION_FRAME_FORMAT_VHT; + *format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_160: + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LOCATION_GROUP, + TOF_RANGE_REQ_CMD, + IWL_FW_CMD_VER_UNKNOWN); + + if (cmd_ver >= 13) { + *format_bw = IWL_LOCATION_FRAME_FORMAT_HE; + *format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS; + break; + } + fallthrough; + default: + IWL_ERR(mvm, "Unsupported BW in FTM request (%d)\n", + peer->chandef.width); + return -EINVAL; + } + + /* non EDCA based measurement must use HE preamble */ + if (peer->ftm.trigger_based || peer->ftm.non_trigger_based) + *format_bw |= IWL_LOCATION_FRAME_FORMAT_HE; + + *ctrl_ch_position = (peer->chandef.width > NL80211_CHAN_WIDTH_20) ? + iwl_mvm_get_ctrl_pos(&peer->chandef) : 0; + + return 0; +} + +static int +iwl_mvm_ftm_put_target_v2(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v2 *target) +{ + int ret; + + ret = iwl_mvm_ftm_target_chandef_v1(mvm, peer, &target->channel_num, + &target->bandwidth, + &target->ctrl_ch_position); + if (ret) + return ret; + + memcpy(target->bssid, peer->addr, ETH_ALEN); + target->burst_period = + cpu_to_le16(peer->ftm.burst_period); + target->samples_per_burst = peer->ftm.ftms_per_burst; + target->num_of_bursts = peer->ftm.num_bursts_exp; + target->measure_type = 0; /* regular two-sided FTM */ + target->retries_per_sample = peer->ftm.ftmr_retries; + target->asap_mode = peer->ftm.asap; + target->enable_dyn_ack = IWL_MVM_FTM_INITIATOR_DYNACK; + + if (peer->ftm.request_lci) + target->location_req |= IWL_TOF_LOC_LCI; + if (peer->ftm.request_civicloc) + target->location_req |= IWL_TOF_LOC_CIVIC; + + target->algo_type = IWL_MVM_FTM_INITIATOR_ALGO; + + return 0; +} + +#define FTM_PUT_FLAG(flag) (target->initiator_ap_flags |= \ + cpu_to_le32(IWL_INITIATOR_AP_FLAGS_##flag)) + +static void +iwl_mvm_ftm_put_target_common(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v6 *target) +{ + memcpy(target->bssid, peer->addr, ETH_ALEN); + target->burst_period = + cpu_to_le16(peer->ftm.burst_period); + target->samples_per_burst = peer->ftm.ftms_per_burst; + target->num_of_bursts = peer->ftm.num_bursts_exp; + target->ftmr_max_retries = peer->ftm.ftmr_retries; + target->initiator_ap_flags = cpu_to_le32(0); + + if (peer->ftm.asap) + FTM_PUT_FLAG(ASAP); + + if (peer->ftm.request_lci) + FTM_PUT_FLAG(LCI_REQUEST); + + if (peer->ftm.request_civicloc) + FTM_PUT_FLAG(CIVIC_REQUEST); + + if (IWL_MVM_FTM_INITIATOR_DYNACK) + FTM_PUT_FLAG(DYN_ACK); + + if (IWL_MVM_FTM_INITIATOR_ALGO == IWL_TOF_ALGO_TYPE_LINEAR_REG) + FTM_PUT_FLAG(ALGO_LR); + else if (IWL_MVM_FTM_INITIATOR_ALGO == IWL_TOF_ALGO_TYPE_FFT) + FTM_PUT_FLAG(ALGO_FFT); + + if (peer->ftm.trigger_based) + FTM_PUT_FLAG(TB); + else if (peer->ftm.non_trigger_based) + FTM_PUT_FLAG(NON_TB); + + if ((peer->ftm.trigger_based || peer->ftm.non_trigger_based) && + peer->ftm.lmr_feedback) + FTM_PUT_FLAG(LMR_FEEDBACK); +} + +static int +iwl_mvm_ftm_put_target_v3(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v3 *target) +{ + int ret; + + ret = iwl_mvm_ftm_target_chandef_v1(mvm, peer, &target->channel_num, + &target->bandwidth, + &target->ctrl_ch_position); + if (ret) + return ret; + + /* + * Versions 3 and 4 has some common fields, so + * iwl_mvm_ftm_put_target_common() can be used for version 7 too. + */ + iwl_mvm_ftm_put_target_common(mvm, peer, (void *)target); + + return 0; +} + +static int +iwl_mvm_ftm_put_target_v4(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v4 *target) +{ + int ret; + + ret = iwl_mvm_ftm_target_chandef_v2(mvm, peer, &target->channel_num, + &target->format_bw, + &target->ctrl_ch_position); + if (ret) + return ret; + + iwl_mvm_ftm_put_target_common(mvm, peer, (void *)target); + + return 0; +} + +static int +iwl_mvm_ftm_put_target(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v6 *target) +{ + int ret; + + ret = iwl_mvm_ftm_target_chandef_v2(mvm, peer, &target->channel_num, + &target->format_bw, + &target->ctrl_ch_position); + if (ret) + return ret; + + iwl_mvm_ftm_put_target_common(mvm, peer, target); + + if (vif->bss_conf.assoc && + !memcmp(peer->addr, vif->bss_conf.bssid, ETH_ALEN)) { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_sta *sta; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[mvmvif->ap_sta_id]); + if (sta->mfp) + FTM_PUT_FLAG(PMF); + + rcu_read_unlock(); + + target->sta_id = mvmvif->ap_sta_id; + } else { + target->sta_id = IWL_MVM_INVALID_STA; + } + + /* + * TODO: Beacon interval is currently unknown, so use the common value + * of 100 TUs. + */ + target->beacon_interval = cpu_to_le16(100); + return 0; +} + +static int iwl_mvm_ftm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *hcmd) +{ + u32 status; + int err = iwl_mvm_send_cmd_status(mvm, hcmd, &status); + + if (!err && status) { + IWL_ERR(mvm, "FTM range request command failure, status: %u\n", + status); + err = iwl_ftm_range_request_status_to_err(status); + } + + return err; +} + +static int iwl_mvm_ftm_start_v5(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v5 cmd_v5; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(TOF_RANGE_REQ_CMD, LOCATION_GROUP, 0), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd_v5, + .len[0] = sizeof(cmd_v5), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_v5(mvm, vif, &cmd_v5, req); + + for (i = 0; i < cmd_v5.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + + err = iwl_mvm_ftm_put_target_v2(mvm, peer, &cmd_v5.ap[i]); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static int iwl_mvm_ftm_start_v7(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v7 cmd_v7; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(TOF_RANGE_REQ_CMD, LOCATION_GROUP, 0), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd_v7, + .len[0] = sizeof(cmd_v7), + }; + u8 i; + int err; + + /* + * Versions 7 and 8 has the same structure except from the responders + * list, so iwl_mvm_ftm_cmd() can be used for version 7 too. + */ + iwl_mvm_ftm_cmd_v8(mvm, vif, (void *)&cmd_v7, req); + + for (i = 0; i < cmd_v7.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + + err = iwl_mvm_ftm_put_target_v3(mvm, peer, &cmd_v7.ap[i]); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static int iwl_mvm_ftm_start_v8(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v8 cmd; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(TOF_RANGE_REQ_CMD, LOCATION_GROUP, 0), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_v8(mvm, vif, (void *)&cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + + err = iwl_mvm_ftm_put_target_v4(mvm, peer, &cmd.ap[i]); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static int iwl_mvm_ftm_start_v9(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v9 cmd; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(TOF_RANGE_REQ_CMD, LOCATION_GROUP, 0), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_common(mvm, vif, &cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + struct iwl_tof_range_req_ap_entry_v6 *target = &cmd.ap[i]; + + err = iwl_mvm_ftm_put_target(mvm, vif, peer, target); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static void iter(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *data) +{ + struct iwl_tof_range_req_ap_entry_v6 *target = data; + + if (!sta || memcmp(sta->addr, target->bssid, ETH_ALEN)) + return; + + WARN_ON(!sta->mfp); + + if (WARN_ON(key->keylen > sizeof(target->tk))) + return; + + memcpy(target->tk, key->key, key->keylen); + target->cipher = iwl_mvm_cipher_to_location_cipher(key->cipher); + WARN_ON(target->cipher == IWL_LOCATION_CIPHER_INVALID); +} + +static void +iwl_mvm_ftm_set_secured_ranging(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_tof_range_req_ap_entry_v7 *target) +{ + struct iwl_mvm_ftm_pasn_entry *entry; + u32 flags = le32_to_cpu(target->initiator_ap_flags); + + if (!(flags & (IWL_INITIATOR_AP_FLAGS_NON_TB | + IWL_INITIATOR_AP_FLAGS_TB))) + return; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry(entry, &mvm->ftm_initiator.pasn_list, list) { + if (memcmp(entry->addr, target->bssid, sizeof(entry->addr))) + continue; + + target->cipher = entry->cipher; + memcpy(target->hltk, entry->hltk, sizeof(target->hltk)); + + if (vif->bss_conf.assoc && + !memcmp(vif->bss_conf.bssid, target->bssid, + sizeof(target->bssid))) + ieee80211_iter_keys(mvm->hw, vif, iter, target); + else + memcpy(target->tk, entry->tk, sizeof(target->tk)); + + memcpy(target->rx_pn, entry->rx_pn, sizeof(target->rx_pn)); + memcpy(target->tx_pn, entry->tx_pn, sizeof(target->tx_pn)); + + target->initiator_ap_flags |= + cpu_to_le32(IWL_INITIATOR_AP_FLAGS_SECURED); + return; + } +} + +static int +iwl_mvm_ftm_put_target_v7(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v7 *target) +{ + int err = iwl_mvm_ftm_put_target(mvm, vif, peer, (void *)target); + if (err) + return err; + + iwl_mvm_ftm_set_secured_ranging(mvm, vif, target); + return err; +} + +static int iwl_mvm_ftm_start_v11(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v11 cmd; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(TOF_RANGE_REQ_CMD, LOCATION_GROUP, 0), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_common(mvm, vif, (void *)&cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + struct iwl_tof_range_req_ap_entry_v7 *target = &cmd.ap[i]; + + err = iwl_mvm_ftm_put_target_v7(mvm, vif, peer, target); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static void +iwl_mvm_ftm_set_ndp_params(struct iwl_mvm *mvm, + struct iwl_tof_range_req_ap_entry_v8 *target) +{ + /* Only 2 STS are supported on Tx */ + u32 i2r_max_sts = IWL_MVM_FTM_I2R_MAX_STS > 1 ? 1 : + IWL_MVM_FTM_I2R_MAX_STS; + + target->r2i_ndp_params = IWL_MVM_FTM_R2I_MAX_REP | + (IWL_MVM_FTM_R2I_MAX_STS << IWL_LOCATION_MAX_STS_POS); + target->i2r_ndp_params = IWL_MVM_FTM_I2R_MAX_REP | + (i2r_max_sts << IWL_LOCATION_MAX_STS_POS); + target->r2i_max_total_ltf = IWL_MVM_FTM_R2I_MAX_TOTAL_LTF; + target->i2r_max_total_ltf = IWL_MVM_FTM_I2R_MAX_TOTAL_LTF; +} + +static int +iwl_mvm_ftm_put_target_v8(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v8 *target) +{ + u32 flags; + int ret = iwl_mvm_ftm_put_target_v7(mvm, vif, peer, (void *)target); + + if (ret) + return ret; + + iwl_mvm_ftm_set_ndp_params(mvm, target); + + /* + * If secure LTF is turned off, replace the flag with PMF only + */ + flags = le32_to_cpu(target->initiator_ap_flags); + if ((flags & IWL_INITIATOR_AP_FLAGS_SECURED) && + !IWL_MVM_FTM_INITIATOR_SECURE_LTF) { + flags &= ~IWL_INITIATOR_AP_FLAGS_SECURED; + flags |= IWL_INITIATOR_AP_FLAGS_PMF; + target->initiator_ap_flags = cpu_to_le32(flags); + } + + return 0; +} + +static int iwl_mvm_ftm_start_v12(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v12 cmd; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(TOF_RANGE_REQ_CMD, LOCATION_GROUP, 0), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_common(mvm, vif, (void *)&cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + struct iwl_tof_range_req_ap_entry_v8 *target = &cmd.ap[i]; + + err = iwl_mvm_ftm_put_target_v8(mvm, vif, peer, target); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static int iwl_mvm_ftm_start_v13(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v13 cmd; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(TOF_RANGE_REQ_CMD, LOCATION_GROUP, 0), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_common(mvm, vif, (void *)&cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + struct iwl_tof_range_req_ap_entry_v9 *target = &cmd.ap[i]; + + err = iwl_mvm_ftm_put_target_v8(mvm, vif, peer, (void *)target); + if (err) + return err; + + if (peer->ftm.trigger_based || peer->ftm.non_trigger_based) + target->bss_color = peer->ftm.bss_color; + + if (peer->ftm.non_trigger_based) { + target->min_time_between_msr = + cpu_to_le16(IWL_MVM_FTM_NON_TB_MIN_TIME_BETWEEN_MSR); + target->burst_period = + cpu_to_le16(IWL_MVM_FTM_NON_TB_MAX_TIME_BETWEEN_MSR); + } else { + target->min_time_between_msr = cpu_to_le16(0); + } + + target->band = + iwl_mvm_phy_band_from_nl80211(peer->chandef.chan->band); + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +int iwl_mvm_ftm_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + bool new_api = fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ); + int err; + + lockdep_assert_held(&mvm->mutex); + + if (mvm->ftm_initiator.req) + return -EBUSY; + + if (new_api) { + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LOCATION_GROUP, + TOF_RANGE_REQ_CMD, + IWL_FW_CMD_VER_UNKNOWN); + + switch (cmd_ver) { + case 13: + err = iwl_mvm_ftm_start_v13(mvm, vif, req); + break; + case 12: + err = iwl_mvm_ftm_start_v12(mvm, vif, req); + break; + case 11: + err = iwl_mvm_ftm_start_v11(mvm, vif, req); + break; + case 9: + case 10: + err = iwl_mvm_ftm_start_v9(mvm, vif, req); + break; + case 8: + err = iwl_mvm_ftm_start_v8(mvm, vif, req); + break; + default: + err = iwl_mvm_ftm_start_v7(mvm, vif, req); + break; + } + } else { + err = iwl_mvm_ftm_start_v5(mvm, vif, req); + } + + if (!err) { + mvm->ftm_initiator.req = req; + mvm->ftm_initiator.req_wdev = ieee80211_vif_to_wdev(vif); + } + + return err; +} + +void iwl_mvm_ftm_abort(struct iwl_mvm *mvm, struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_abort_cmd cmd = { + .request_id = req->cookie, + }; + + lockdep_assert_held(&mvm->mutex); + + if (req != mvm->ftm_initiator.req) + return; + + iwl_mvm_ftm_reset(mvm); + + if (iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(TOF_RANGE_ABORT_CMD, + LOCATION_GROUP, 0), + 0, sizeof(cmd), &cmd)) + IWL_ERR(mvm, "failed to abort FTM process\n"); +} + +static int iwl_mvm_ftm_find_peer(struct cfg80211_pmsr_request *req, + const u8 *addr) +{ + int i; + + for (i = 0; i < req->n_peers; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + + if (ether_addr_equal_unaligned(peer->addr, addr)) + return i; + } + + return -ENOENT; +} + +static u64 iwl_mvm_ftm_get_host_time(struct iwl_mvm *mvm, __le32 fw_gp2_ts) +{ + u32 gp2_ts = le32_to_cpu(fw_gp2_ts); + u32 curr_gp2, diff; + u64 now_from_boot_ns; + + iwl_mvm_get_sync_time(mvm, CLOCK_BOOTTIME, &curr_gp2, + &now_from_boot_ns, NULL); + + if (curr_gp2 >= gp2_ts) + diff = curr_gp2 - gp2_ts; + else + diff = curr_gp2 + (U32_MAX - gp2_ts + 1); + + return now_from_boot_ns - (u64)diff * 1000; +} + +static void iwl_mvm_ftm_get_lci_civic(struct iwl_mvm *mvm, + struct cfg80211_pmsr_result *res) +{ + struct iwl_mvm_loc_entry *entry; + + list_for_each_entry(entry, &mvm->ftm_initiator.loc_list, list) { + if (!ether_addr_equal_unaligned(res->addr, entry->addr)) + continue; + + if (entry->lci_len) { + res->ftm.lci_len = entry->lci_len; + res->ftm.lci = entry->buf; + } + + if (entry->civic_len) { + res->ftm.civicloc_len = entry->civic_len; + res->ftm.civicloc = entry->buf + entry->lci_len; + } + + /* we found the entry we needed */ + break; + } +} + +static int iwl_mvm_ftm_range_resp_valid(struct iwl_mvm *mvm, u8 request_id, + u8 num_of_aps) +{ + lockdep_assert_held(&mvm->mutex); + + if (request_id != (u8)mvm->ftm_initiator.req->cookie) { + IWL_ERR(mvm, "Request ID mismatch, got %u, active %u\n", + request_id, (u8)mvm->ftm_initiator.req->cookie); + return -EINVAL; + } + + if (num_of_aps > mvm->ftm_initiator.req->n_peers) { + IWL_ERR(mvm, "FTM range response invalid\n"); + return -EINVAL; + } + + return 0; +} + +static void iwl_mvm_ftm_rtt_smoothing(struct iwl_mvm *mvm, + struct cfg80211_pmsr_result *res) +{ + struct iwl_mvm_smooth_entry *resp; + s64 rtt_avg, rtt = res->ftm.rtt_avg; + u32 undershoot, overshoot; + u8 alpha; + bool found; + + if (!IWL_MVM_FTM_INITIATOR_ENABLE_SMOOTH) + return; + + WARN_ON(rtt < 0); + + if (res->status != NL80211_PMSR_STATUS_SUCCESS) { + IWL_DEBUG_INFO(mvm, + ": %pM: ignore failed measurement. Status=%u\n", + res->addr, res->status); + return; + } + + found = false; + list_for_each_entry(resp, &mvm->ftm_initiator.smooth.resp, list) { + if (!memcmp(res->addr, resp->addr, ETH_ALEN)) { + found = true; + break; + } + } + + if (!found) { + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) + return; + + memcpy(resp->addr, res->addr, ETH_ALEN); + list_add_tail(&resp->list, &mvm->ftm_initiator.smooth.resp); + + resp->rtt_avg = rtt; + + IWL_DEBUG_INFO(mvm, "new: %pM: rtt_avg=%lld\n", + resp->addr, resp->rtt_avg); + goto update_time; + } + + if (res->host_time - resp->host_time > + IWL_MVM_FTM_INITIATOR_SMOOTH_AGE_SEC * 1000000000) { + resp->rtt_avg = rtt; + + IWL_DEBUG_INFO(mvm, "expired: %pM: rtt_avg=%lld\n", + resp->addr, resp->rtt_avg); + goto update_time; + } + + /* Smooth the results based on the tracked RTT average */ + undershoot = IWL_MVM_FTM_INITIATOR_SMOOTH_UNDERSHOOT; + overshoot = IWL_MVM_FTM_INITIATOR_SMOOTH_OVERSHOOT; + alpha = IWL_MVM_FTM_INITIATOR_SMOOTH_ALPHA; + + rtt_avg = (alpha * rtt + (100 - alpha) * resp->rtt_avg) / 100; + + IWL_DEBUG_INFO(mvm, + "%pM: prev rtt_avg=%lld, new rtt_avg=%lld, rtt=%lld\n", + resp->addr, resp->rtt_avg, rtt_avg, rtt); + + /* + * update the responder's average RTT results regardless of + * the under/over shoot logic below + */ + resp->rtt_avg = rtt_avg; + + /* smooth the results */ + if (rtt_avg > rtt && (rtt_avg - rtt) > undershoot) { + res->ftm.rtt_avg = rtt_avg; + + IWL_DEBUG_INFO(mvm, + "undershoot: val=%lld\n", + (rtt_avg - rtt)); + } else if (rtt_avg < rtt && (rtt - rtt_avg) > + overshoot) { + res->ftm.rtt_avg = rtt_avg; + IWL_DEBUG_INFO(mvm, + "overshoot: val=%lld\n", + (rtt - rtt_avg)); + } + +update_time: + resp->host_time = res->host_time; +} + +static void iwl_mvm_debug_range_resp(struct iwl_mvm *mvm, u8 index, + struct cfg80211_pmsr_result *res) +{ + s64 rtt_avg = div_s64(res->ftm.rtt_avg * 100, 6666); + + IWL_DEBUG_INFO(mvm, "entry %d\n", index); + IWL_DEBUG_INFO(mvm, "\tstatus: %d\n", res->status); + IWL_DEBUG_INFO(mvm, "\tBSSID: %pM\n", res->addr); + IWL_DEBUG_INFO(mvm, "\thost time: %llu\n", res->host_time); + IWL_DEBUG_INFO(mvm, "\tburst index: %hhu\n", res->ftm.burst_index); + IWL_DEBUG_INFO(mvm, "\tsuccess num: %u\n", res->ftm.num_ftmr_successes); + IWL_DEBUG_INFO(mvm, "\trssi: %d\n", res->ftm.rssi_avg); + IWL_DEBUG_INFO(mvm, "\trssi spread: %hhu\n", res->ftm.rssi_spread); + IWL_DEBUG_INFO(mvm, "\trtt: %lld\n", res->ftm.rtt_avg); + IWL_DEBUG_INFO(mvm, "\trtt var: %llu\n", res->ftm.rtt_variance); + IWL_DEBUG_INFO(mvm, "\trtt spread: %llu\n", res->ftm.rtt_spread); + IWL_DEBUG_INFO(mvm, "\tdistance: %lld\n", rtt_avg); +} + +static void +iwl_mvm_ftm_pasn_update_pn(struct iwl_mvm *mvm, + struct iwl_tof_range_rsp_ap_entry_ntfy_v6 *fw_ap) +{ + struct iwl_mvm_ftm_pasn_entry *entry; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry(entry, &mvm->ftm_initiator.pasn_list, list) { + if (memcmp(fw_ap->bssid, entry->addr, sizeof(entry->addr))) + continue; + + memcpy(entry->rx_pn, fw_ap->rx_pn, sizeof(entry->rx_pn)); + memcpy(entry->tx_pn, fw_ap->tx_pn, sizeof(entry->tx_pn)); + return; + } +} + +static u8 iwl_mvm_ftm_get_range_resp_ver(struct iwl_mvm *mvm) +{ + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ)) + return 5; + + /* Starting from version 8, the FW advertises the version */ + if (mvm->cmd_ver.range_resp >= 8) + return mvm->cmd_ver.range_resp; + else if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_RTT_ACCURACY)) + return 7; + + /* The first version of the new range request API */ + return 6; +} + +static bool iwl_mvm_ftm_resp_size_validation(u8 ver, unsigned int pkt_len) +{ + switch (ver) { + case 9: + case 8: + return pkt_len == sizeof(struct iwl_tof_range_rsp_ntfy_v8); + case 7: + return pkt_len == sizeof(struct iwl_tof_range_rsp_ntfy_v7); + case 6: + return pkt_len == sizeof(struct iwl_tof_range_rsp_ntfy_v6); + case 5: + return pkt_len == sizeof(struct iwl_tof_range_rsp_ntfy_v5); + default: + WARN_ONCE(1, "FTM: unsupported range response version %u", ver); + return false; + } +} + +void iwl_mvm_ftm_range_resp(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_tof_range_rsp_ntfy_v5 *fw_resp_v5 = (void *)pkt->data; + struct iwl_tof_range_rsp_ntfy_v6 *fw_resp_v6 = (void *)pkt->data; + struct iwl_tof_range_rsp_ntfy_v7 *fw_resp_v7 = (void *)pkt->data; + struct iwl_tof_range_rsp_ntfy_v8 *fw_resp_v8 = (void *)pkt->data; + int i; + bool new_api = fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ); + u8 num_of_aps, last_in_batch; + u8 notif_ver = iwl_mvm_ftm_get_range_resp_ver(mvm); + + lockdep_assert_held(&mvm->mutex); + + if (!mvm->ftm_initiator.req) { + return; + } + + if (unlikely(!iwl_mvm_ftm_resp_size_validation(notif_ver, pkt_len))) + return; + + if (new_api) { + if (iwl_mvm_ftm_range_resp_valid(mvm, fw_resp_v8->request_id, + fw_resp_v8->num_of_aps)) + return; + + num_of_aps = fw_resp_v8->num_of_aps; + last_in_batch = fw_resp_v8->last_report; + } else { + if (iwl_mvm_ftm_range_resp_valid(mvm, fw_resp_v5->request_id, + fw_resp_v5->num_of_aps)) + return; + + num_of_aps = fw_resp_v5->num_of_aps; + last_in_batch = fw_resp_v5->last_in_batch; + } + + IWL_DEBUG_INFO(mvm, "Range response received\n"); + IWL_DEBUG_INFO(mvm, "request id: %lld, num of entries: %hhu\n", + mvm->ftm_initiator.req->cookie, num_of_aps); + + for (i = 0; i < num_of_aps && i < IWL_MVM_TOF_MAX_APS; i++) { + struct cfg80211_pmsr_result result = {}; + struct iwl_tof_range_rsp_ap_entry_ntfy_v6 *fw_ap; + int peer_idx; + + if (new_api) { + if (notif_ver >= 8) { + fw_ap = &fw_resp_v8->ap[i]; + iwl_mvm_ftm_pasn_update_pn(mvm, fw_ap); + } else if (notif_ver == 7) { + fw_ap = (void *)&fw_resp_v7->ap[i]; + } else { + fw_ap = (void *)&fw_resp_v6->ap[i]; + } + + result.final = fw_ap->last_burst; + result.ap_tsf = le32_to_cpu(fw_ap->start_tsf); + result.ap_tsf_valid = 1; + } else { + /* the first part is the same for old and new APIs */ + fw_ap = (void *)&fw_resp_v5->ap[i]; + /* + * FIXME: the firmware needs to report this, we don't + * even know the number of bursts the responder picked + * (if we asked it to) + */ + result.final = 0; + } + + peer_idx = iwl_mvm_ftm_find_peer(mvm->ftm_initiator.req, + fw_ap->bssid); + if (peer_idx < 0) { + IWL_WARN(mvm, + "Unknown address (%pM, target #%d) in FTM response\n", + fw_ap->bssid, i); + continue; + } + + switch (fw_ap->measure_status) { + case IWL_TOF_ENTRY_SUCCESS: + result.status = NL80211_PMSR_STATUS_SUCCESS; + break; + case IWL_TOF_ENTRY_TIMING_MEASURE_TIMEOUT: + result.status = NL80211_PMSR_STATUS_TIMEOUT; + break; + case IWL_TOF_ENTRY_NO_RESPONSE: + result.status = NL80211_PMSR_STATUS_FAILURE; + result.ftm.failure_reason = + NL80211_PMSR_FTM_FAILURE_NO_RESPONSE; + break; + case IWL_TOF_ENTRY_REQUEST_REJECTED: + result.status = NL80211_PMSR_STATUS_FAILURE; + result.ftm.failure_reason = + NL80211_PMSR_FTM_FAILURE_PEER_BUSY; + result.ftm.busy_retry_time = fw_ap->refusal_period; + break; + default: + result.status = NL80211_PMSR_STATUS_FAILURE; + result.ftm.failure_reason = + NL80211_PMSR_FTM_FAILURE_UNSPECIFIED; + break; + } + memcpy(result.addr, fw_ap->bssid, ETH_ALEN); + result.host_time = iwl_mvm_ftm_get_host_time(mvm, + fw_ap->timestamp); + result.type = NL80211_PMSR_TYPE_FTM; + result.ftm.burst_index = mvm->ftm_initiator.responses[peer_idx]; + mvm->ftm_initiator.responses[peer_idx]++; + result.ftm.rssi_avg = fw_ap->rssi; + result.ftm.rssi_avg_valid = 1; + result.ftm.rssi_spread = fw_ap->rssi_spread; + result.ftm.rssi_spread_valid = 1; + result.ftm.rtt_avg = (s32)le32_to_cpu(fw_ap->rtt); + result.ftm.rtt_avg_valid = 1; + result.ftm.rtt_variance = le32_to_cpu(fw_ap->rtt_variance); + result.ftm.rtt_variance_valid = 1; + result.ftm.rtt_spread = le32_to_cpu(fw_ap->rtt_spread); + result.ftm.rtt_spread_valid = 1; + + iwl_mvm_ftm_get_lci_civic(mvm, &result); + + iwl_mvm_ftm_rtt_smoothing(mvm, &result); + + cfg80211_pmsr_report(mvm->ftm_initiator.req_wdev, + mvm->ftm_initiator.req, + &result, GFP_KERNEL); + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_RTT_ACCURACY)) + IWL_DEBUG_INFO(mvm, "RTT confidence: %hhu\n", + fw_ap->rttConfidence); + + iwl_mvm_debug_range_resp(mvm, i, &result); + } + + if (last_in_batch) { + cfg80211_pmsr_complete(mvm->ftm_initiator.req_wdev, + mvm->ftm_initiator.req, + GFP_KERNEL); + iwl_mvm_ftm_reset(mvm); + } +} + +void iwl_mvm_ftm_lc_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + const struct ieee80211_mgmt *mgmt = (void *)pkt->data; + size_t len = iwl_rx_packet_payload_len(pkt); + struct iwl_mvm_loc_entry *entry; + const u8 *ies, *lci, *civic, *msr_ie; + size_t ies_len, lci_len = 0, civic_len = 0; + size_t baselen = IEEE80211_MIN_ACTION_SIZE + + sizeof(mgmt->u.action.u.ftm); + static const u8 rprt_type_lci = IEEE80211_SPCT_MSR_RPRT_TYPE_LCI; + static const u8 rprt_type_civic = IEEE80211_SPCT_MSR_RPRT_TYPE_CIVIC; + + if (len <= baselen) + return; + + lockdep_assert_held(&mvm->mutex); + + ies = mgmt->u.action.u.ftm.variable; + ies_len = len - baselen; + + msr_ie = cfg80211_find_ie_match(WLAN_EID_MEASURE_REPORT, ies, ies_len, + &rprt_type_lci, 1, 4); + if (msr_ie) { + lci = msr_ie + 2; + lci_len = msr_ie[1]; + } + + msr_ie = cfg80211_find_ie_match(WLAN_EID_MEASURE_REPORT, ies, ies_len, + &rprt_type_civic, 1, 4); + if (msr_ie) { + civic = msr_ie + 2; + civic_len = msr_ie[1]; + } + + entry = kmalloc(sizeof(*entry) + lci_len + civic_len, GFP_KERNEL); + if (!entry) + return; + + memcpy(entry->addr, mgmt->bssid, ETH_ALEN); + + entry->lci_len = lci_len; + if (lci_len) + memcpy(entry->buf, lci, lci_len); + + entry->civic_len = civic_len; + if (civic_len) + memcpy(entry->buf + lci_len, civic, civic_len); + + list_add_tail(&entry->list, &mvm->ftm_initiator.loc_list); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/ftm-responder.c b/sys/contrib/dev/iwlwifi/mvm/ftm-responder.c new file mode 100644 index 00000000000..bda6da7d988 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/ftm-responder.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#include +#include +#include "mvm.h" +#include "constants.h" + +struct iwl_mvm_pasn_sta { + struct list_head list; + struct iwl_mvm_int_sta int_sta; + u8 addr[ETH_ALEN]; +}; + +struct iwl_mvm_pasn_hltk_data { + u8 *addr; + u8 cipher; + u8 *hltk; +}; + +static int iwl_mvm_ftm_responder_set_bw_v1(struct cfg80211_chan_def *chandef, + u8 *bw, u8 *ctrl_ch_position) +{ + switch (chandef->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + *bw = IWL_TOF_BW_20_LEGACY; + break; + case NL80211_CHAN_WIDTH_20: + *bw = IWL_TOF_BW_20_HT; + break; + case NL80211_CHAN_WIDTH_40: + *bw = IWL_TOF_BW_40; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + case NL80211_CHAN_WIDTH_80: + *bw = IWL_TOF_BW_80; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +static int iwl_mvm_ftm_responder_set_bw_v2(struct cfg80211_chan_def *chandef, + u8 *format_bw, u8 *ctrl_ch_position, + u8 cmd_ver) +{ + switch (chandef->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + *format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY; + *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_20: + *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; + *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_40: + *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; + *format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + case NL80211_CHAN_WIDTH_80: + *format_bw = IWL_LOCATION_FRAME_FORMAT_VHT; + *format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + case NL80211_CHAN_WIDTH_160: + if (cmd_ver >= 9) { + *format_bw = IWL_LOCATION_FRAME_FORMAT_HE; + *format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + } + fallthrough; + default: + return -ENOTSUPP; + } + + return 0; +} + +static void +iwl_mvm_ftm_responder_set_ndp(struct iwl_mvm *mvm, + struct iwl_tof_responder_config_cmd_v9 *cmd) +{ + /* Up to 2 R2I STS are allowed on the responder */ + u32 r2i_max_sts = IWL_MVM_FTM_R2I_MAX_STS < 2 ? + IWL_MVM_FTM_R2I_MAX_STS : 1; + + cmd->r2i_ndp_params = IWL_MVM_FTM_R2I_MAX_REP | + (r2i_max_sts << IWL_RESPONDER_STS_POS) | + (IWL_MVM_FTM_R2I_MAX_TOTAL_LTF << IWL_RESPONDER_TOTAL_LTF_POS); + cmd->i2r_ndp_params = IWL_MVM_FTM_I2R_MAX_REP | + (IWL_MVM_FTM_I2R_MAX_STS << IWL_RESPONDER_STS_POS) | + (IWL_MVM_FTM_I2R_MAX_TOTAL_LTF << IWL_RESPONDER_TOTAL_LTF_POS); + cmd->cmd_valid_fields |= + cpu_to_le32(IWL_TOF_RESPONDER_CMD_VALID_NDP_PARAMS); +} + +static int +iwl_mvm_ftm_responder_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_chan_def *chandef) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + /* + * The command structure is the same for versions 6, 7 and 8 (only the + * field interpretation is different), so the same struct can be use + * for all cases. + */ + struct iwl_tof_responder_config_cmd_v9 cmd = { + .channel_num = chandef->chan->hw_value, + .cmd_valid_fields = + cpu_to_le32(IWL_TOF_RESPONDER_CMD_VALID_CHAN_INFO | + IWL_TOF_RESPONDER_CMD_VALID_BSSID | + IWL_TOF_RESPONDER_CMD_VALID_STA_ID), + .sta_id = mvmvif->bcast_sta.sta_id, + }; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LOCATION_GROUP, + TOF_RESPONDER_CONFIG_CMD, 6); + int err; + int cmd_size; + + lockdep_assert_held(&mvm->mutex); + + /* Use a default of bss_color=1 for now */ + if (cmd_ver == 9) { + cmd.cmd_valid_fields |= + cpu_to_le32(IWL_TOF_RESPONDER_CMD_VALID_BSS_COLOR | + IWL_TOF_RESPONDER_CMD_VALID_MIN_MAX_TIME_BETWEEN_MSR); + cmd.bss_color = 1; + cmd.min_time_between_msr = + cpu_to_le16(IWL_MVM_FTM_NON_TB_MIN_TIME_BETWEEN_MSR); + cmd.max_time_between_msr = + cpu_to_le16(IWL_MVM_FTM_NON_TB_MAX_TIME_BETWEEN_MSR); + cmd_size = sizeof(struct iwl_tof_responder_config_cmd_v9); + } else { + /* All versions up to version 8 have the same size */ + cmd_size = sizeof(struct iwl_tof_responder_config_cmd_v8); + } + + if (cmd_ver >= 8) + iwl_mvm_ftm_responder_set_ndp(mvm, &cmd); + + if (cmd_ver >= 7) + err = iwl_mvm_ftm_responder_set_bw_v2(chandef, &cmd.format_bw, + &cmd.ctrl_ch_position, + cmd_ver); + else + err = iwl_mvm_ftm_responder_set_bw_v1(chandef, &cmd.format_bw, + &cmd.ctrl_ch_position); + + if (err) { + IWL_ERR(mvm, "Failed to set responder bandwidth\n"); + return err; + } + + memcpy(cmd.bssid, vif->addr, ETH_ALEN); + + return iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(TOF_RESPONDER_CONFIG_CMD, + LOCATION_GROUP, 0), + 0, cmd_size, &cmd); +} + +static int +iwl_mvm_ftm_responder_dyn_cfg_v2(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_ftm_responder_params *params) +{ + struct iwl_tof_responder_dyn_config_cmd_v2 cmd = { + .lci_len = cpu_to_le32(params->lci_len + 2), + .civic_len = cpu_to_le32(params->civicloc_len + 2), + }; + u8 data[IWL_LCI_CIVIC_IE_MAX_SIZE] = {0}; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(TOF_RESPONDER_DYN_CONFIG_CMD, + LOCATION_GROUP, 0), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + .data[1] = &data, + /* .len[1] set later */ + /* may not be able to DMA from stack */ + .dataflags[1] = IWL_HCMD_DFL_DUP, + }; + u32 aligned_lci_len = ALIGN(params->lci_len + 2, 4); + u32 aligned_civicloc_len = ALIGN(params->civicloc_len + 2, 4); + u8 *pos = data; + + lockdep_assert_held(&mvm->mutex); + + if (aligned_lci_len + aligned_civicloc_len > sizeof(data)) { + IWL_ERR(mvm, "LCI/civicloc data too big (%zd + %zd)\n", + params->lci_len, params->civicloc_len); + return -ENOBUFS; + } + + pos[0] = WLAN_EID_MEASURE_REPORT; + pos[1] = params->lci_len; + memcpy(pos + 2, params->lci, params->lci_len); + + pos += aligned_lci_len; + pos[0] = WLAN_EID_MEASURE_REPORT; + pos[1] = params->civicloc_len; + memcpy(pos + 2, params->civicloc, params->civicloc_len); + + hcmd.len[1] = aligned_lci_len + aligned_civicloc_len; + + return iwl_mvm_send_cmd(mvm, &hcmd); +} + +static int +iwl_mvm_ftm_responder_dyn_cfg_v3(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_ftm_responder_params *params, + struct iwl_mvm_pasn_hltk_data *hltk_data) +{ + struct iwl_tof_responder_dyn_config_cmd cmd; + struct iwl_host_cmd hcmd = { + .id = iwl_cmd_id(TOF_RESPONDER_DYN_CONFIG_CMD, + LOCATION_GROUP, 0), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + /* may not be able to DMA from stack */ + .dataflags[0] = IWL_HCMD_DFL_DUP, + }; + + lockdep_assert_held(&mvm->mutex); + + cmd.valid_flags = 0; + + if (params) { + if (params->lci_len + 2 > sizeof(cmd.lci_buf) || + params->civicloc_len + 2 > sizeof(cmd.civic_buf)) { + IWL_ERR(mvm, + "LCI/civic data too big (lci=%zd, civic=%zd)\n", + params->lci_len, params->civicloc_len); + return -ENOBUFS; + } + + cmd.lci_buf[0] = WLAN_EID_MEASURE_REPORT; + cmd.lci_buf[1] = params->lci_len; + memcpy(cmd.lci_buf + 2, params->lci, params->lci_len); + cmd.lci_len = params->lci_len + 2; + + cmd.civic_buf[0] = WLAN_EID_MEASURE_REPORT; + cmd.civic_buf[1] = params->civicloc_len; + memcpy(cmd.civic_buf + 2, params->civicloc, + params->civicloc_len); + cmd.civic_len = params->civicloc_len + 2; + + cmd.valid_flags |= IWL_RESPONDER_DYN_CFG_VALID_LCI | + IWL_RESPONDER_DYN_CFG_VALID_CIVIC; + } + + if (hltk_data) { + if (hltk_data->cipher > IWL_LOCATION_CIPHER_GCMP_256) { + IWL_ERR(mvm, "invalid cipher: %u\n", + hltk_data->cipher); + return -EINVAL; + } + + cmd.cipher = hltk_data->cipher; + memcpy(cmd.addr, hltk_data->addr, sizeof(cmd.addr)); + memcpy(cmd.hltk_buf, hltk_data->hltk, sizeof(cmd.hltk_buf)); + cmd.valid_flags |= IWL_RESPONDER_DYN_CFG_VALID_PASN_STA; + } + + return iwl_mvm_send_cmd(mvm, &hcmd); +} + +static int +iwl_mvm_ftm_responder_dyn_cfg_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_ftm_responder_params *params) +{ + int ret; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LOCATION_GROUP, + TOF_RESPONDER_DYN_CONFIG_CMD, 2); + + switch (cmd_ver) { + case 2: + ret = iwl_mvm_ftm_responder_dyn_cfg_v2(mvm, vif, + params); + break; + case 3: + ret = iwl_mvm_ftm_responder_dyn_cfg_v3(mvm, vif, + params, NULL); + break; + default: + IWL_ERR(mvm, "Unsupported DYN_CONFIG_CMD version %u\n", + cmd_ver); + ret = -ENOTSUPP; + } + + return ret; +} + +static void iwl_mvm_resp_del_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_pasn_sta *sta) +{ + list_del(&sta->list); + iwl_mvm_rm_sta_id(mvm, vif, sta->int_sta.sta_id); + iwl_mvm_dealloc_int_sta(mvm, &sta->int_sta); + kfree(sta); +} + +int iwl_mvm_ftm_respoder_add_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 *addr, u32 cipher, u8 *tk, u32 tk_len, + u8 *hltk, u32 hltk_len) +{ + int ret; + struct iwl_mvm_pasn_sta *sta = NULL; + struct iwl_mvm_pasn_hltk_data hltk_data = { + .addr = addr, + .hltk = hltk, + }; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LOCATION_GROUP, + TOF_RESPONDER_DYN_CONFIG_CMD, 2); + + lockdep_assert_held(&mvm->mutex); + + if (cmd_ver < 3) { + IWL_ERR(mvm, "Adding PASN station not supported by FW\n"); + return -ENOTSUPP; + } + + hltk_data.cipher = iwl_mvm_cipher_to_location_cipher(cipher); + if (hltk_data.cipher == IWL_LOCATION_CIPHER_INVALID) { + IWL_ERR(mvm, "invalid cipher: %u\n", cipher); + return -EINVAL; + } + + if (tk && tk_len) { + sta = kzalloc(sizeof(*sta), GFP_KERNEL); + if (!sta) + return -ENOBUFS; + + ret = iwl_mvm_add_pasn_sta(mvm, vif, &sta->int_sta, addr, + cipher, tk, tk_len); + if (ret) { + kfree(sta); + return ret; + } + + memcpy(sta->addr, addr, ETH_ALEN); + list_add_tail(&sta->list, &mvm->resp_pasn_list); + } + + ret = iwl_mvm_ftm_responder_dyn_cfg_v3(mvm, vif, NULL, &hltk_data); + if (ret && sta) + iwl_mvm_resp_del_pasn_sta(mvm, vif, sta); + + return ret; +} + +int iwl_mvm_ftm_resp_remove_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, u8 *addr) +{ + struct iwl_mvm_pasn_sta *sta, *prev; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry_safe(sta, prev, &mvm->resp_pasn_list, list) { + if (!memcmp(sta->addr, addr, ETH_ALEN)) { + iwl_mvm_resp_del_pasn_sta(mvm, vif, sta); + return 0; + } + } + + IWL_ERR(mvm, "FTM: PASN station %pM not found\n", addr); + return -EINVAL; +} + +int iwl_mvm_ftm_start_responder(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_ftm_responder_params *params; + struct ieee80211_chanctx_conf ctx, *pctx; + u16 *phy_ctxt_id; + struct iwl_mvm_phy_ctxt *phy_ctxt; + int ret; + + params = vif->bss_conf.ftmr_params; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(!vif->bss_conf.ftm_responder)) + return -EINVAL; + + if (vif->p2p || vif->type != NL80211_IFTYPE_AP || + !mvmvif->ap_ibss_active) { + IWL_ERR(mvm, "Cannot start responder, not in AP mode\n"); + return -EIO; + } + + rcu_read_lock(); + pctx = rcu_dereference(vif->chanctx_conf); + /* Copy the ctx to unlock the rcu and send the phy ctxt. We don't care + * about changes in the ctx after releasing the lock because the driver + * is still protected by the mutex. */ + ctx = *pctx; + phy_ctxt_id = (u16 *)pctx->drv_priv; + rcu_read_unlock(); + + phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id]; + ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &ctx.def, + ctx.rx_chains_static, + ctx.rx_chains_dynamic); + if (ret) + return ret; + + ret = iwl_mvm_ftm_responder_cmd(mvm, vif, &ctx.def); + if (ret) + return ret; + + if (params) + ret = iwl_mvm_ftm_responder_dyn_cfg_cmd(mvm, vif, params); + + return ret; +} + +void iwl_mvm_ftm_responder_clear(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_pasn_sta *sta, *prev; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry_safe(sta, prev, &mvm->resp_pasn_list, list) + iwl_mvm_resp_del_pasn_sta(mvm, vif, sta); +} + +void iwl_mvm_ftm_restart_responder(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + if (!vif->bss_conf.ftm_responder) + return; + + iwl_mvm_ftm_responder_clear(mvm, vif); + iwl_mvm_ftm_start_responder(mvm, vif); +} + +void iwl_mvm_ftm_responder_stats(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_ftm_responder_stats *resp = (void *)pkt->data; + struct cfg80211_ftm_responder_stats *stats = &mvm->ftm_resp_stats; + u32 flags = le32_to_cpu(resp->flags); + + if (resp->success_ftm == resp->ftm_per_burst) + stats->success_num++; + else if (resp->success_ftm >= 2) + stats->partial_num++; + else + stats->failed_num++; + + if ((flags & FTM_RESP_STAT_ASAP_REQ) && + (flags & FTM_RESP_STAT_ASAP_RESP)) + stats->asap_num++; + + if (flags & FTM_RESP_STAT_NON_ASAP_RESP) + stats->non_asap_num++; + + stats->total_duration_ms += le32_to_cpu(resp->duration) / USEC_PER_MSEC; + + if (flags & FTM_RESP_STAT_TRIGGER_UNKNOWN) + stats->unknown_triggers_num++; + + if (flags & FTM_RESP_STAT_DUP) + stats->reschedule_requests_num++; + + if (flags & FTM_RESP_STAT_NON_ASAP_OUT_WIN) + stats->out_of_window_triggers_num++; +} diff --git a/sys/contrib/dev/iwlwifi/mvm/fw-api.h b/sys/contrib/dev/iwlwifi/mvm/fw-api.h new file mode 100644 index 00000000000..73a82f07dc5 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/fw-api.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018, 2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __fw_api_h__ +#define __fw_api_h__ + +#include "fw/api/tdls.h" +#include "fw/api/mac-cfg.h" +#include "fw/api/offload.h" +#include "fw/api/context.h" +#include "fw/api/time-event.h" +#include "fw/api/datapath.h" +#include "fw/api/phy.h" +#include "fw/api/config.h" +#include "fw/api/soc.h" +#include "fw/api/alive.h" +#include "fw/api/binding.h" +#include "fw/api/cmdhdr.h" +#include "fw/api/coex.h" +#include "fw/api/commands.h" +#include "fw/api/d3.h" +#include "fw/api/filter.h" +#include "fw/api/led.h" +#include "fw/api/mac.h" +#include "fw/api/nvm-reg.h" +#include "fw/api/phy-ctxt.h" +#include "fw/api/power.h" +#include "fw/api/rs.h" +#include "fw/api/rx.h" +#include "fw/api/scan.h" +#include "fw/api/sf.h" +#include "fw/api/sta.h" +#include "fw/api/stats.h" +#include "fw/api/location.h" +#include "fw/api/tx.h" +#include "fw/api/rfi.h" + +#endif /* __fw_api_h__ */ diff --git a/sys/contrib/dev/iwlwifi/mvm/fw.c b/sys/contrib/dev/iwlwifi/mvm/fw.c new file mode 100644 index 00000000000..1e6425dc736 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/fw.c @@ -0,0 +1,1739 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include +#include + +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "fw/img.h" +#include "iwl-debug.h" +#include "iwl-csr.h" /* for iwl_mvm_rx_card_state_notif */ +#include "iwl-io.h" /* for iwl_mvm_rx_card_state_notif */ +#include "iwl-prph.h" +#include "fw/acpi.h" +#include "fw/pnvm.h" + +#include "mvm.h" +#include "fw/dbg.h" +#include "iwl-phy-db.h" +#include "iwl-modparams.h" +#include "iwl-nvm-parse.h" + +#define MVM_UCODE_ALIVE_TIMEOUT (HZ) +#define MVM_UCODE_CALIB_TIMEOUT (2 * HZ) + +#define UCODE_VALID_OK cpu_to_le32(0x1) + +#define IWL_PPAG_MASK 3 +#define IWL_PPAG_ETSI_MASK BIT(0) + +struct iwl_mvm_alive_data { + bool valid; + u32 scd_base_addr; +}; + +static int iwl_send_tx_ant_cfg(struct iwl_mvm *mvm, u8 valid_tx_ant) +{ + struct iwl_tx_ant_cfg_cmd tx_ant_cmd = { + .valid = cpu_to_le32(valid_tx_ant), + }; + + IWL_DEBUG_FW(mvm, "select valid tx ant: %u\n", valid_tx_ant); + return iwl_mvm_send_cmd_pdu(mvm, TX_ANT_CONFIGURATION_CMD, 0, + sizeof(tx_ant_cmd), &tx_ant_cmd); +} + +static int iwl_send_rss_cfg_cmd(struct iwl_mvm *mvm) +{ + int i; + struct iwl_rss_config_cmd cmd = { + .flags = cpu_to_le32(IWL_RSS_ENABLE), + .hash_mask = BIT(IWL_RSS_HASH_TYPE_IPV4_TCP) | + BIT(IWL_RSS_HASH_TYPE_IPV4_UDP) | + BIT(IWL_RSS_HASH_TYPE_IPV4_PAYLOAD) | + BIT(IWL_RSS_HASH_TYPE_IPV6_TCP) | + BIT(IWL_RSS_HASH_TYPE_IPV6_UDP) | + BIT(IWL_RSS_HASH_TYPE_IPV6_PAYLOAD), + }; + + if (mvm->trans->num_rx_queues == 1) + return 0; + + /* Do not direct RSS traffic to Q 0 which is our fallback queue */ + for (i = 0; i < ARRAY_SIZE(cmd.indirection_table); i++) + cmd.indirection_table[i] = + 1 + (i % (mvm->trans->num_rx_queues - 1)); + netdev_rss_key_fill(cmd.secret_key, sizeof(cmd.secret_key)); + + return iwl_mvm_send_cmd_pdu(mvm, RSS_CONFIG_CMD, 0, sizeof(cmd), &cmd); +} + +static int iwl_mvm_send_dqa_cmd(struct iwl_mvm *mvm) +{ + struct iwl_dqa_enable_cmd dqa_cmd = { + .cmd_queue = cpu_to_le32(IWL_MVM_DQA_CMD_QUEUE), + }; + u32 cmd_id = iwl_cmd_id(DQA_ENABLE_CMD, DATA_PATH_GROUP, 0); + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, sizeof(dqa_cmd), &dqa_cmd); + if (ret) + IWL_ERR(mvm, "Failed to send DQA enabling command: %d\n", ret); + else + IWL_DEBUG_FW(mvm, "Working in DQA mode\n"); + + return ret; +} + +void iwl_mvm_mfu_assert_dump_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mfu_assert_dump_notif *mfu_dump_notif = (void *)pkt->data; + __le32 *dump_data = mfu_dump_notif->data; + int n_words = le32_to_cpu(mfu_dump_notif->data_size) / sizeof(__le32); + int i; + + if (mfu_dump_notif->index_num == 0) + IWL_INFO(mvm, "MFUART assert id 0x%x occurred\n", + le32_to_cpu(mfu_dump_notif->assert_id)); + + for (i = 0; i < n_words; i++) + IWL_DEBUG_INFO(mvm, + "MFUART assert dump, dword %u: 0x%08x\n", + le16_to_cpu(mfu_dump_notif->index_num) * + n_words + i, + le32_to_cpu(dump_data[i])); +} + +static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_mvm_alive_data *alive_data = data; + struct iwl_umac_alive *umac; + struct iwl_lmac_alive *lmac1; + struct iwl_lmac_alive *lmac2 = NULL; + u16 status; + u32 lmac_error_event_table, umac_error_table; + + /* + * For v5 and above, we can check the version, for older + * versions we need to check the size. + */ + if (iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, + UCODE_ALIVE_NTFY, 0) == 5) { + struct iwl_alive_ntf_v5 *palive; + + if (pkt_len < sizeof(*palive)) + return false; + + palive = (void *)pkt->data; + umac = &palive->umac_data; + lmac1 = &palive->lmac_data[0]; + lmac2 = &palive->lmac_data[1]; + status = le16_to_cpu(palive->status); + + mvm->trans->sku_id[0] = le32_to_cpu(palive->sku_id.data[0]); + mvm->trans->sku_id[1] = le32_to_cpu(palive->sku_id.data[1]); + mvm->trans->sku_id[2] = le32_to_cpu(palive->sku_id.data[2]); + + IWL_DEBUG_FW(mvm, "Got sku_id: 0x0%x 0x0%x 0x0%x\n", + mvm->trans->sku_id[0], + mvm->trans->sku_id[1], + mvm->trans->sku_id[2]); + } else if (iwl_rx_packet_payload_len(pkt) == sizeof(struct iwl_alive_ntf_v4)) { + struct iwl_alive_ntf_v4 *palive; + + if (pkt_len < sizeof(*palive)) + return false; + + palive = (void *)pkt->data; + umac = &palive->umac_data; + lmac1 = &palive->lmac_data[0]; + lmac2 = &palive->lmac_data[1]; + status = le16_to_cpu(palive->status); + } else if (iwl_rx_packet_payload_len(pkt) == + sizeof(struct iwl_alive_ntf_v3)) { + struct iwl_alive_ntf_v3 *palive3; + + if (pkt_len < sizeof(*palive3)) + return false; + + palive3 = (void *)pkt->data; + umac = &palive3->umac_data; + lmac1 = &palive3->lmac_data; + status = le16_to_cpu(palive3->status); + } else { + WARN(1, "unsupported alive notification (size %d)\n", + iwl_rx_packet_payload_len(pkt)); + /* get timeout later */ + return false; + } + + lmac_error_event_table = + le32_to_cpu(lmac1->dbg_ptrs.error_event_table_ptr); + iwl_fw_lmac1_set_alive_err_table(mvm->trans, lmac_error_event_table); + + if (lmac2) + mvm->trans->dbg.lmac_error_event_table[1] = + le32_to_cpu(lmac2->dbg_ptrs.error_event_table_ptr); + + umac_error_table = le32_to_cpu(umac->dbg_ptrs.error_info_addr) & + ~FW_ADDR_CACHE_CONTROL; + + if (umac_error_table) { + if (umac_error_table >= + mvm->trans->cfg->min_umac_error_event_table) { + iwl_fw_umac_set_alive_err_table(mvm->trans, + umac_error_table); + } else { + IWL_ERR(mvm, + "Not valid error log pointer 0x%08X for %s uCode\n", + umac_error_table, + (mvm->fwrt.cur_fw_img == IWL_UCODE_INIT) ? + "Init" : "RT"); + } + } + + alive_data->scd_base_addr = le32_to_cpu(lmac1->dbg_ptrs.scd_base_ptr); + alive_data->valid = status == IWL_ALIVE_STATUS_OK; + + IWL_DEBUG_FW(mvm, + "Alive ucode status 0x%04x revision 0x%01X 0x%01X\n", + status, lmac1->ver_type, lmac1->ver_subtype); + + if (lmac2) + IWL_DEBUG_FW(mvm, "Alive ucode CDB\n"); + + IWL_DEBUG_FW(mvm, + "UMAC version: Major - 0x%x, Minor - 0x%x\n", + le32_to_cpu(umac->umac_major), + le32_to_cpu(umac->umac_minor)); + + iwl_fwrt_update_fw_versions(&mvm->fwrt, lmac1, umac); + + return true; +} + +static bool iwl_wait_init_complete(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + WARN_ON(pkt->hdr.cmd != INIT_COMPLETE_NOTIF); + + return true; +} + +static bool iwl_wait_phy_db_entry(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_phy_db *phy_db = data; + + if (pkt->hdr.cmd != CALIB_RES_NOTIF_PHY_DB) { + WARN_ON(pkt->hdr.cmd != INIT_COMPLETE_NOTIF); + return true; + } + + WARN_ON(iwl_phy_db_set_section(phy_db, pkt)); + + return false; +} + +static int iwl_mvm_load_ucode_wait_alive(struct iwl_mvm *mvm, + enum iwl_ucode_type ucode_type) +{ + struct iwl_notification_wait alive_wait; + struct iwl_mvm_alive_data alive_data = {}; + const struct fw_img *fw; + int ret; + enum iwl_ucode_type old_type = mvm->fwrt.cur_fw_img; + static const u16 alive_cmd[] = { UCODE_ALIVE_NTFY }; + bool run_in_rfkill = + ucode_type == IWL_UCODE_INIT || iwl_mvm_has_unified_ucode(mvm); + + if (ucode_type == IWL_UCODE_REGULAR && + iwl_fw_dbg_conf_usniffer(mvm->fw, FW_DBG_START_FROM_ALIVE) && + !(fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_USNIFFER_UNIFIED))) + fw = iwl_get_ucode_image(mvm->fw, IWL_UCODE_REGULAR_USNIFFER); + else + fw = iwl_get_ucode_image(mvm->fw, ucode_type); + if (WARN_ON(!fw)) + return -EINVAL; + iwl_fw_set_current_image(&mvm->fwrt, ucode_type); + clear_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); + + iwl_init_notification_wait(&mvm->notif_wait, &alive_wait, + alive_cmd, ARRAY_SIZE(alive_cmd), + iwl_alive_fn, &alive_data); + + /* + * We want to load the INIT firmware even in RFKILL + * For the unified firmware case, the ucode_type is not + * INIT, but we still need to run it. + */ + ret = iwl_trans_start_fw(mvm->trans, fw, run_in_rfkill); + if (ret) { + iwl_fw_set_current_image(&mvm->fwrt, old_type); + iwl_remove_notification(&mvm->notif_wait, &alive_wait); + return ret; + } + + /* + * Some things may run in the background now, but we + * just wait for the ALIVE notification here. + */ + ret = iwl_wait_notification(&mvm->notif_wait, &alive_wait, + MVM_UCODE_ALIVE_TIMEOUT); + if (ret) { + struct iwl_trans *trans = mvm->trans; + + /* SecBoot info */ + if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_22000) { + IWL_ERR(mvm, + "SecBoot CPU1 Status: 0x%x, CPU2 Status: 0x%x\n", + iwl_read_umac_prph(trans, UMAG_SB_CPU_1_STATUS), + iwl_read_umac_prph(trans, + UMAG_SB_CPU_2_STATUS)); + } else if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_8000) { + IWL_ERR(mvm, + "SecBoot CPU1 Status: 0x%x, CPU2 Status: 0x%x\n", + iwl_read_prph(trans, SB_CPU_1_STATUS), + iwl_read_prph(trans, SB_CPU_2_STATUS)); + } + + /* LMAC/UMAC PC info */ + if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_9000) { + IWL_ERR(mvm, "UMAC PC: 0x%x\n", + iwl_read_umac_prph(trans, + UREG_UMAC_CURRENT_PC)); + IWL_ERR(mvm, "LMAC PC: 0x%x\n", + iwl_read_umac_prph(trans, + UREG_LMAC1_CURRENT_PC)); + if (iwl_mvm_is_cdb_supported(mvm)) + IWL_ERR(mvm, "LMAC2 PC: 0x%x\n", + iwl_read_umac_prph(trans, + UREG_LMAC2_CURRENT_PC)); + } + + if (ret == -ETIMEDOUT) + iwl_fw_dbg_error_collect(&mvm->fwrt, + FW_DBG_TRIGGER_ALIVE_TIMEOUT); + + iwl_fw_set_current_image(&mvm->fwrt, old_type); + return ret; + } + + if (!alive_data.valid) { + IWL_ERR(mvm, "Loaded ucode is not valid!\n"); + iwl_fw_set_current_image(&mvm->fwrt, old_type); + return -EIO; + } + + ret = iwl_pnvm_load(mvm->trans, &mvm->notif_wait); + if (ret) { + IWL_ERR(mvm, "Timeout waiting for PNVM load!\n"); + iwl_fw_set_current_image(&mvm->fwrt, old_type); + return ret; + } + + iwl_trans_fw_alive(mvm->trans, alive_data.scd_base_addr); + + /* + * Note: all the queues are enabled as part of the interface + * initialization, but in firmware restart scenarios they + * could be stopped, so wake them up. In firmware restart, + * mac80211 will have the queues stopped as well until the + * reconfiguration completes. During normal startup, they + * will be empty. + */ + + memset(&mvm->queue_info, 0, sizeof(mvm->queue_info)); + /* + * Set a 'fake' TID for the command queue, since we use the + * hweight() of the tid_bitmap as a refcount now. Not that + * we ever even consider the command queue as one we might + * want to reuse, but be safe nevertheless. + */ + mvm->queue_info[IWL_MVM_DQA_CMD_QUEUE].tid_bitmap = + BIT(IWL_MAX_TID_COUNT + 2); + + set_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); +#ifdef CONFIG_IWLWIFI_DEBUGFS + iwl_fw_set_dbg_rec_on(&mvm->fwrt); +#endif + + /* + * All the BSSes in the BSS table include the GP2 in the system + * at the beacon Rx time, this is of course no longer relevant + * since we are resetting the firmware. + * Purge all the BSS table. + */ + cfg80211_bss_flush(mvm->hw->wiphy); + + return 0; +} + +static int iwl_run_unified_mvm_ucode(struct iwl_mvm *mvm) +{ + struct iwl_notification_wait init_wait; + struct iwl_nvm_access_complete_cmd nvm_complete = {}; + struct iwl_init_extended_cfg_cmd init_cfg = { + .init_flags = cpu_to_le32(BIT(IWL_INIT_NVM)), + }; + static const u16 init_complete[] = { + INIT_COMPLETE_NOTIF, + }; + int ret; + + if (mvm->trans->cfg->tx_with_siso_diversity) + init_cfg.init_flags |= cpu_to_le32(BIT(IWL_INIT_PHY)); + + lockdep_assert_held(&mvm->mutex); + + mvm->rfkill_safe_init_done = false; + + iwl_init_notification_wait(&mvm->notif_wait, + &init_wait, + init_complete, + ARRAY_SIZE(init_complete), + iwl_wait_init_complete, + NULL); + + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_EARLY, NULL); + + /* Will also start the device */ + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_REGULAR); + if (ret) { + IWL_ERR(mvm, "Failed to start RT ucode: %d\n", ret); + goto error; + } + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_AFTER_ALIVE, + NULL); + + /* Send init config command to mark that we are sending NVM access + * commands + */ + ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(SYSTEM_GROUP, + INIT_EXTENDED_CFG_CMD), + CMD_SEND_IN_RFKILL, + sizeof(init_cfg), &init_cfg); + if (ret) { + IWL_ERR(mvm, "Failed to run init config command: %d\n", + ret); + goto error; + } + + /* Load NVM to NIC if needed */ + if (mvm->nvm_file_name) { + ret = iwl_read_external_nvm(mvm->trans, mvm->nvm_file_name, + mvm->nvm_sections); + if (ret) + goto error; + ret = iwl_mvm_load_nvm_to_nic(mvm); + if (ret) + goto error; + } + + if (IWL_MVM_PARSE_NVM && !mvm->nvm_data) { + ret = iwl_nvm_init(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to read NVM: %d\n", ret); + goto error; + } + } + + ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(REGULATORY_AND_NVM_GROUP, + NVM_ACCESS_COMPLETE), + CMD_SEND_IN_RFKILL, + sizeof(nvm_complete), &nvm_complete); + if (ret) { + IWL_ERR(mvm, "Failed to run complete NVM access: %d\n", + ret); + goto error; + } + + /* We wait for the INIT complete notification */ + ret = iwl_wait_notification(&mvm->notif_wait, &init_wait, + MVM_UCODE_ALIVE_TIMEOUT); + if (ret) + return ret; + + /* Read the NVM only at driver load time, no need to do this twice */ + if (!IWL_MVM_PARSE_NVM && !mvm->nvm_data) { + mvm->nvm_data = iwl_get_nvm(mvm->trans, mvm->fw); + if (IS_ERR(mvm->nvm_data)) { + ret = PTR_ERR(mvm->nvm_data); + mvm->nvm_data = NULL; + IWL_ERR(mvm, "Failed to read NVM: %d\n", ret); + return ret; + } + } + + mvm->rfkill_safe_init_done = true; + + return 0; + +error: + iwl_remove_notification(&mvm->notif_wait, &init_wait); + return ret; +} + +#ifdef CONFIG_ACPI +static void iwl_mvm_phy_filter_init(struct iwl_mvm *mvm, + struct iwl_phy_specific_cfg *phy_filters) +{ + /* + * TODO: read specific phy config from BIOS + * ACPI table for this feature has not been defined yet, + * so for now we use hardcoded values. + */ + + if (IWL_MVM_PHY_FILTER_CHAIN_A) { + phy_filters->filter_cfg_chain_a = + cpu_to_le32(IWL_MVM_PHY_FILTER_CHAIN_A); + } + if (IWL_MVM_PHY_FILTER_CHAIN_B) { + phy_filters->filter_cfg_chain_b = + cpu_to_le32(IWL_MVM_PHY_FILTER_CHAIN_B); + } + if (IWL_MVM_PHY_FILTER_CHAIN_C) { + phy_filters->filter_cfg_chain_c = + cpu_to_le32(IWL_MVM_PHY_FILTER_CHAIN_C); + } + if (IWL_MVM_PHY_FILTER_CHAIN_D) { + phy_filters->filter_cfg_chain_d = + cpu_to_le32(IWL_MVM_PHY_FILTER_CHAIN_D); + } +} + +#else /* CONFIG_ACPI */ + +static void iwl_mvm_phy_filter_init(struct iwl_mvm *mvm, + struct iwl_phy_specific_cfg *phy_filters) +{ +} +#endif /* CONFIG_ACPI */ + +static int iwl_send_phy_cfg_cmd(struct iwl_mvm *mvm) +{ + struct iwl_phy_cfg_cmd_v3 phy_cfg_cmd; + enum iwl_ucode_type ucode_type = mvm->fwrt.cur_fw_img; + struct iwl_phy_specific_cfg phy_filters = {}; + u8 cmd_ver; + size_t cmd_size; + + if (iwl_mvm_has_unified_ucode(mvm) && + !mvm->trans->cfg->tx_with_siso_diversity) + return 0; + + if (mvm->trans->cfg->tx_with_siso_diversity) { + /* + * TODO: currently we don't set the antenna but letting the NIC + * to decide which antenna to use. This should come from BIOS. + */ + phy_cfg_cmd.phy_cfg = + cpu_to_le32(FW_PHY_CFG_CHAIN_SAD_ENABLED); + } + + /* Set parameters */ + phy_cfg_cmd.phy_cfg = cpu_to_le32(iwl_mvm_get_phy_config(mvm)); + + /* set flags extra PHY configuration flags from the device's cfg */ + phy_cfg_cmd.phy_cfg |= + cpu_to_le32(mvm->trans->trans_cfg->extra_phy_cfg_flags); + + phy_cfg_cmd.calib_control.event_trigger = + mvm->fw->default_calib[ucode_type].event_trigger; + phy_cfg_cmd.calib_control.flow_trigger = + mvm->fw->default_calib[ucode_type].flow_trigger; + + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, IWL_ALWAYS_LONG_GROUP, + PHY_CONFIGURATION_CMD, + IWL_FW_CMD_VER_UNKNOWN); + if (cmd_ver == 3) { + iwl_mvm_phy_filter_init(mvm, &phy_filters); + memcpy(&phy_cfg_cmd.phy_specific_cfg, &phy_filters, + sizeof(struct iwl_phy_specific_cfg)); + } + + IWL_DEBUG_INFO(mvm, "Sending Phy CFG command: 0x%x\n", + phy_cfg_cmd.phy_cfg); + cmd_size = (cmd_ver == 3) ? sizeof(struct iwl_phy_cfg_cmd_v3) : + sizeof(struct iwl_phy_cfg_cmd_v1); + return iwl_mvm_send_cmd_pdu(mvm, PHY_CONFIGURATION_CMD, 0, + cmd_size, &phy_cfg_cmd); +} + +int iwl_run_init_mvm_ucode(struct iwl_mvm *mvm) +{ + struct iwl_notification_wait calib_wait; + static const u16 init_complete[] = { + INIT_COMPLETE_NOTIF, + CALIB_RES_NOTIF_PHY_DB + }; + int ret; + + if (iwl_mvm_has_unified_ucode(mvm)) + return iwl_run_unified_mvm_ucode(mvm); + + lockdep_assert_held(&mvm->mutex); + + mvm->rfkill_safe_init_done = false; + + iwl_init_notification_wait(&mvm->notif_wait, + &calib_wait, + init_complete, + ARRAY_SIZE(init_complete), + iwl_wait_phy_db_entry, + mvm->phy_db); + + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_EARLY, NULL); + + /* Will also start the device */ + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_INIT); + if (ret) { + IWL_ERR(mvm, "Failed to start INIT ucode: %d\n", ret); + goto remove_notif; + } + + if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) { + ret = iwl_mvm_send_bt_init_conf(mvm); + if (ret) + goto remove_notif; + } + + /* Read the NVM only at driver load time, no need to do this twice */ + if (!mvm->nvm_data) { + ret = iwl_nvm_init(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to read NVM: %d\n", ret); + goto remove_notif; + } + } + + /* In case we read the NVM from external file, load it to the NIC */ + if (mvm->nvm_file_name) { + ret = iwl_mvm_load_nvm_to_nic(mvm); + if (ret) + goto remove_notif; + } + + WARN_ONCE(mvm->nvm_data->nvm_version < mvm->trans->cfg->nvm_ver, + "Too old NVM version (0x%0x, required = 0x%0x)", + mvm->nvm_data->nvm_version, mvm->trans->cfg->nvm_ver); + + /* + * abort after reading the nvm in case RF Kill is on, we will complete + * the init seq later when RF kill will switch to off + */ + if (iwl_mvm_is_radio_hw_killed(mvm)) { + IWL_DEBUG_RF_KILL(mvm, + "jump over all phy activities due to RF kill\n"); + goto remove_notif; + } + + mvm->rfkill_safe_init_done = true; + + /* Send TX valid antennas before triggering calibrations */ + ret = iwl_send_tx_ant_cfg(mvm, iwl_mvm_get_valid_tx_ant(mvm)); + if (ret) + goto remove_notif; + + ret = iwl_send_phy_cfg_cmd(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to run INIT calibrations: %d\n", + ret); + goto remove_notif; + } + + /* + * Some things may run in the background now, but we + * just wait for the calibration complete notification. + */ + ret = iwl_wait_notification(&mvm->notif_wait, &calib_wait, + MVM_UCODE_CALIB_TIMEOUT); + if (!ret) + goto out; + + if (iwl_mvm_is_radio_hw_killed(mvm)) { + IWL_DEBUG_RF_KILL(mvm, "RFKILL while calibrating.\n"); + ret = 0; + } else { + IWL_ERR(mvm, "Failed to run INIT calibrations: %d\n", + ret); + } + + goto out; + +remove_notif: + iwl_remove_notification(&mvm->notif_wait, &calib_wait); +out: + mvm->rfkill_safe_init_done = false; + if (iwlmvm_mod_params.init_dbg && !mvm->nvm_data) { + /* we want to debug INIT and we have no NVM - fake */ + mvm->nvm_data = kzalloc(sizeof(struct iwl_nvm_data) + + sizeof(struct ieee80211_channel) + + sizeof(struct ieee80211_rate), + GFP_KERNEL); + if (!mvm->nvm_data) + return -ENOMEM; + mvm->nvm_data->bands[0].channels = mvm->nvm_data->channels; + mvm->nvm_data->bands[0].n_channels = 1; + mvm->nvm_data->bands[0].n_bitrates = 1; + mvm->nvm_data->bands[0].bitrates = + (void *)(mvm->nvm_data->channels + 1); + mvm->nvm_data->bands[0].bitrates->hw_value = 10; + } + + return ret; +} + +static int iwl_mvm_config_ltr(struct iwl_mvm *mvm) +{ + struct iwl_ltr_config_cmd cmd = { + .flags = cpu_to_le32(LTR_CFG_FLAG_FEATURE_ENABLE), + }; + + if (!mvm->trans->ltr_enabled) + return 0; + + return iwl_mvm_send_cmd_pdu(mvm, LTR_CONFIG, 0, + sizeof(cmd), &cmd); +} + +#ifdef CONFIG_ACPI +int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, int prof_a, int prof_b) +{ + struct iwl_dev_tx_power_cmd cmd = { + .common.set_mode = cpu_to_le32(IWL_TX_POWER_MODE_SET_CHAINS), + }; + __le16 *per_chain; + int ret; + u16 len = 0; + u32 n_subbands; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + REDUCE_TX_POWER_CMD, + IWL_FW_CMD_VER_UNKNOWN); + + if (cmd_ver == 6) { + len = sizeof(cmd.v6); + n_subbands = IWL_NUM_SUB_BANDS_V2; + per_chain = cmd.v6.per_chain[0][0]; + } else if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_REDUCE_TX_POWER)) { + len = sizeof(cmd.v5); + n_subbands = IWL_NUM_SUB_BANDS_V1; + per_chain = cmd.v5.per_chain[0][0]; + } else if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TX_POWER_ACK)) { + len = sizeof(cmd.v4); + n_subbands = IWL_NUM_SUB_BANDS_V1; + per_chain = cmd.v4.per_chain[0][0]; + } else { + len = sizeof(cmd.v3); + n_subbands = IWL_NUM_SUB_BANDS_V1; + per_chain = cmd.v3.per_chain[0][0]; + } + + /* all structs have the same common part, add it */ + len += sizeof(cmd.common); + + ret = iwl_sar_select_profile(&mvm->fwrt, per_chain, + IWL_NUM_CHAIN_TABLES, + n_subbands, prof_a, prof_b); + + /* return on error or if the profile is disabled (positive number) */ + if (ret) + return ret; + + IWL_DEBUG_RADIO(mvm, "Sending REDUCE_TX_POWER_CMD per chain\n"); + return iwl_mvm_send_cmd_pdu(mvm, REDUCE_TX_POWER_CMD, 0, len, &cmd); +} + +int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm) +{ + union iwl_geo_tx_power_profiles_cmd geo_tx_cmd; + struct iwl_geo_tx_power_profiles_resp *resp; + u16 len; + int ret; + struct iwl_host_cmd cmd; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, PHY_OPS_GROUP, + PER_CHAIN_LIMIT_OFFSET_CMD, + IWL_FW_CMD_VER_UNKNOWN); + + /* the ops field is at the same spot for all versions, so set in v1 */ + geo_tx_cmd.v1.ops = + cpu_to_le32(IWL_PER_CHAIN_OFFSET_GET_CURRENT_TABLE); + + if (cmd_ver == 5) + len = sizeof(geo_tx_cmd.v5); + else if (cmd_ver == 4) + len = sizeof(geo_tx_cmd.v4); + else if (cmd_ver == 3) + len = sizeof(geo_tx_cmd.v3); + else if (fw_has_api(&mvm->fwrt.fw->ucode_capa, + IWL_UCODE_TLV_API_SAR_TABLE_VER)) + len = sizeof(geo_tx_cmd.v2); + else + len = sizeof(geo_tx_cmd.v1); + + if (!iwl_sar_geo_support(&mvm->fwrt)) + return -EOPNOTSUPP; + + cmd = (struct iwl_host_cmd){ + .id = WIDE_ID(PHY_OPS_GROUP, PER_CHAIN_LIMIT_OFFSET_CMD), + .len = { len, }, + .flags = CMD_WANT_SKB, + .data = { &geo_tx_cmd }, + }; + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) { + IWL_ERR(mvm, "Failed to get geographic profile info %d\n", ret); + return ret; + } + + resp = (void *)cmd.resp_pkt->data; + ret = le32_to_cpu(resp->profile_idx); + + if (WARN_ON(ret > ACPI_NUM_GEO_PROFILES_REV3)) + ret = -EIO; + + iwl_free_resp(&cmd); + return ret; +} + +static int iwl_mvm_sar_geo_init(struct iwl_mvm *mvm) +{ + union iwl_geo_tx_power_profiles_cmd cmd; + u16 len; + u32 n_bands; + u32 n_profiles; + int ret; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, PHY_OPS_GROUP, + PER_CHAIN_LIMIT_OFFSET_CMD, + IWL_FW_CMD_VER_UNKNOWN); + + BUILD_BUG_ON(offsetof(struct iwl_geo_tx_power_profiles_cmd_v1, ops) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v2, ops) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v2, ops) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v3, ops) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v3, ops) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v4, ops) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v4, ops) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v5, ops)); + + /* the ops field is at the same spot for all versions, so set in v1 */ + cmd.v1.ops = cpu_to_le32(IWL_PER_CHAIN_OFFSET_SET_TABLES); + + if (cmd_ver == 5) { + len = sizeof(cmd.v5); + n_bands = ARRAY_SIZE(cmd.v5.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES_REV3; + } else if (cmd_ver == 4) { + len = sizeof(cmd.v4); + n_bands = ARRAY_SIZE(cmd.v4.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES_REV3; + } else if (cmd_ver == 3) { + len = sizeof(cmd.v3); + n_bands = ARRAY_SIZE(cmd.v3.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES; + } else if (fw_has_api(&mvm->fwrt.fw->ucode_capa, + IWL_UCODE_TLV_API_SAR_TABLE_VER)) { + len = sizeof(cmd.v2); + n_bands = ARRAY_SIZE(cmd.v2.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES; + } else { + len = sizeof(cmd.v1); + n_bands = ARRAY_SIZE(cmd.v1.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES; + } + + BUILD_BUG_ON(offsetof(struct iwl_geo_tx_power_profiles_cmd_v1, table) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v2, table) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v2, table) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v3, table) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v3, table) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v4, table) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v4, table) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v5, table)); + /* the table is at the same position for all versions, so set use v1 */ + ret = iwl_sar_geo_init(&mvm->fwrt, &cmd.v1.table[0][0], + n_bands, n_profiles); + + /* + * It is a valid scenario to not support SAR, or miss wgds table, + * but in that case there is no need to send the command. + */ + if (ret) + return 0; + + /* + * Set the revision on versions that contain it. + * This must be done after calling iwl_sar_geo_init(). + */ + if (cmd_ver == 5) + cmd.v5.table_revision = cpu_to_le32(mvm->fwrt.geo_rev); + else if (cmd_ver == 4) + cmd.v4.table_revision = cpu_to_le32(mvm->fwrt.geo_rev); + else if (cmd_ver == 3) + cmd.v3.table_revision = cpu_to_le32(mvm->fwrt.geo_rev); + else if (fw_has_api(&mvm->fwrt.fw->ucode_capa, + IWL_UCODE_TLV_API_SAR_TABLE_VER)) + cmd.v2.table_revision = cpu_to_le32(mvm->fwrt.geo_rev); + + return iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(PHY_OPS_GROUP, + PER_CHAIN_LIMIT_OFFSET_CMD), + 0, len, &cmd); +} + +static int iwl_mvm_get_ppag_table(struct iwl_mvm *mvm) +{ + union acpi_object *wifi_pkg, *data, *flags; + int i, j, ret, tbl_rev, num_sub_bands; + int idx = 2; + s8 *gain; + + /* + * The 'flags' field is the same in v1 and in v2 so we can just + * use v1 to access it. + */ + mvm->fwrt.ppag_table.v1.flags = cpu_to_le32(0); + + data = iwl_acpi_get_object(mvm->dev, ACPI_PPAG_METHOD); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* try to read ppag table rev 2 or 1 (both have the same data size) */ + wifi_pkg = iwl_acpi_get_wifi_pkg(mvm->dev, data, + ACPI_PPAG_WIFI_DATA_SIZE_V2, &tbl_rev); + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev == 1 || tbl_rev == 2) { + num_sub_bands = IWL_NUM_SUB_BANDS_V2; + gain = mvm->fwrt.ppag_table.v2.gain[0]; + mvm->fwrt.ppag_ver = tbl_rev; + IWL_DEBUG_RADIO(mvm, + "Reading PPAG table v2 (tbl_rev=%d)\n", + tbl_rev); + goto read_table; + } else { + ret = -EINVAL; + goto out_free; + } + } + + /* try to read ppag table revision 0 */ + wifi_pkg = iwl_acpi_get_wifi_pkg(mvm->dev, data, + ACPI_PPAG_WIFI_DATA_SIZE_V1, &tbl_rev); + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev != 0) { + ret = -EINVAL; + goto out_free; + } + num_sub_bands = IWL_NUM_SUB_BANDS_V1; + gain = mvm->fwrt.ppag_table.v1.gain[0]; + mvm->fwrt.ppag_ver = 0; + IWL_DEBUG_RADIO(mvm, "Reading PPAG table v1 (tbl_rev=0)\n"); + goto read_table; + } + ret = PTR_ERR(wifi_pkg); + goto out_free; + +read_table: + flags = &wifi_pkg->package.elements[1]; + + if (flags->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto out_free; + } + + mvm->fwrt.ppag_table.v1.flags = cpu_to_le32(flags->integer.value & + IWL_PPAG_MASK); + + if (!mvm->fwrt.ppag_table.v1.flags) { + ret = 0; + goto out_free; + } + + /* + * read, verify gain values and save them into the PPAG table. + * first sub-band (j=0) corresponds to Low-Band (2.4GHz), and the + * following sub-bands to High-Band (5GHz). + */ + for (i = 0; i < IWL_NUM_CHAIN_LIMITS; i++) { + for (j = 0; j < num_sub_bands; j++) { + union acpi_object *ent; + + ent = &wifi_pkg->package.elements[idx++]; + if (ent->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto out_free; + } + + gain[i * num_sub_bands + j] = ent->integer.value; + + if ((j == 0 && + (gain[i * num_sub_bands + j] > ACPI_PPAG_MAX_LB || + gain[i * num_sub_bands + j] < ACPI_PPAG_MIN_LB)) || + (j != 0 && + (gain[i * num_sub_bands + j] > ACPI_PPAG_MAX_HB || + gain[i * num_sub_bands + j] < ACPI_PPAG_MIN_HB))) { + mvm->fwrt.ppag_table.v1.flags = cpu_to_le32(0); + ret = -EINVAL; + goto out_free; + } + } + } + + ret = 0; +out_free: + kfree(data); + return ret; +} + +int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm) +{ + u8 cmd_ver; + int i, j, ret, num_sub_bands, cmd_size; + s8 *gain; + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SET_PPAG)) { + IWL_DEBUG_RADIO(mvm, + "PPAG capability not supported by FW, command not sent.\n"); + return 0; + } + if (!mvm->fwrt.ppag_table.v1.flags) { + IWL_DEBUG_RADIO(mvm, "PPAG not enabled, command not sent.\n"); + return 0; + } + + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, PHY_OPS_GROUP, + PER_PLATFORM_ANT_GAIN_CMD, + IWL_FW_CMD_VER_UNKNOWN); + if (cmd_ver == 1) { + num_sub_bands = IWL_NUM_SUB_BANDS_V1; + gain = mvm->fwrt.ppag_table.v1.gain[0]; + cmd_size = sizeof(mvm->fwrt.ppag_table.v1); + if (mvm->fwrt.ppag_ver == 1 || mvm->fwrt.ppag_ver == 2) { + IWL_DEBUG_RADIO(mvm, + "PPAG table rev is %d but FW supports v1, sending truncated table\n", + mvm->fwrt.ppag_ver); + mvm->fwrt.ppag_table.v1.flags &= + cpu_to_le32(IWL_PPAG_ETSI_MASK); + } + } else if (cmd_ver == 2 || cmd_ver == 3) { + num_sub_bands = IWL_NUM_SUB_BANDS_V2; + gain = mvm->fwrt.ppag_table.v2.gain[0]; + cmd_size = sizeof(mvm->fwrt.ppag_table.v2); + if (mvm->fwrt.ppag_ver == 0) { + IWL_DEBUG_RADIO(mvm, + "PPAG table is v1 but FW supports v2, sending padded table\n"); + } else if (cmd_ver == 2 && mvm->fwrt.ppag_ver == 2) { + IWL_DEBUG_RADIO(mvm, + "PPAG table is v3 but FW supports v2, sending partial bitmap.\n"); + mvm->fwrt.ppag_table.v1.flags &= + cpu_to_le32(IWL_PPAG_ETSI_MASK); + } + } else { + IWL_DEBUG_RADIO(mvm, "Unsupported PPAG command version\n"); + return 0; + } + + for (i = 0; i < IWL_NUM_CHAIN_LIMITS; i++) { + for (j = 0; j < num_sub_bands; j++) { + IWL_DEBUG_RADIO(mvm, + "PPAG table: chain[%d] band[%d]: gain = %d\n", + i, j, gain[i * num_sub_bands + j]); + } + } + IWL_DEBUG_RADIO(mvm, "Sending PER_PLATFORM_ANT_GAIN_CMD\n"); + ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP, + PER_PLATFORM_ANT_GAIN_CMD), + 0, cmd_size, &mvm->fwrt.ppag_table); + if (ret < 0) + IWL_ERR(mvm, "failed to send PER_PLATFORM_ANT_GAIN_CMD (%d)\n", + ret); + + return ret; +} + +static const struct dmi_system_id dmi_ppag_approved_list[] = { + { .ident = "HP", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + }, + }, + { .ident = "SAMSUNG", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"), + }, + }, + { .ident = "MSFT", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + }, + }, + { .ident = "ASUS", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTek COMPUTER INC."), + }, + }, + {} +}; + +static int iwl_mvm_ppag_init(struct iwl_mvm *mvm) +{ + /* no need to read the table, done in INIT stage */ + if (!dmi_check_system(dmi_ppag_approved_list)) { + IWL_DEBUG_RADIO(mvm, + "System vendor '%s' is not in the approved list, disabling PPAG.\n", + dmi_get_system_info(DMI_SYS_VENDOR)); + mvm->fwrt.ppag_table.v1.flags = cpu_to_le32(0); + return 0; + } + + return iwl_mvm_ppag_send_cmd(mvm); +} + +static void iwl_mvm_tas_init(struct iwl_mvm *mvm) +{ + int ret; + struct iwl_tas_config_cmd cmd = {}; + int list_size; + + BUILD_BUG_ON(ARRAY_SIZE(cmd.block_list_array) < + APCI_WTAS_BLACK_LIST_MAX); + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_TAS_CFG)) { + IWL_DEBUG_RADIO(mvm, "TAS not enabled in FW\n"); + return; + } + + ret = iwl_acpi_get_tas(&mvm->fwrt, cmd.block_list_array, &list_size); + if (ret < 0) { + IWL_DEBUG_RADIO(mvm, + "TAS table invalid or unavailable. (%d)\n", + ret); + return; + } + + if (list_size < 0) + return; + + /* list size if TAS enabled can only be non-negative */ + cmd.block_list_size = cpu_to_le32((u32)list_size); + + ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(REGULATORY_AND_NVM_GROUP, + TAS_CONFIG), + 0, sizeof(cmd), &cmd); + if (ret < 0) + IWL_DEBUG_RADIO(mvm, "failed to send TAS_CONFIG (%d)\n", ret); +} + +static u8 iwl_mvm_eval_dsm_rfi(struct iwl_mvm *mvm) +{ + u8 value; + int ret = iwl_acpi_get_dsm_u8(mvm->fwrt.dev, 0, DSM_RFI_FUNC_ENABLE, + &iwl_rfi_guid, &value); + + if (ret < 0) { + IWL_DEBUG_RADIO(mvm, "Failed to get DSM RFI, ret=%d\n", ret); + + } else if (value >= DSM_VALUE_RFI_MAX) { + IWL_DEBUG_RADIO(mvm, "DSM RFI got invalid value, ret=%d\n", + value); + + } else if (value == DSM_VALUE_RFI_ENABLE) { + IWL_DEBUG_RADIO(mvm, "DSM RFI is evaluated to enable\n"); + return DSM_VALUE_RFI_ENABLE; + } + + IWL_DEBUG_RADIO(mvm, "DSM RFI is disabled\n"); + + /* default behaviour is disabled */ + return DSM_VALUE_RFI_DISABLE; +} + +static void iwl_mvm_lari_cfg(struct iwl_mvm *mvm) +{ + int ret; + u32 value; + struct iwl_lari_config_change_cmd_v5 cmd = {}; + + cmd.config_bitmap = iwl_acpi_get_lari_config_bitmap(&mvm->fwrt); + + ret = iwl_acpi_get_dsm_u32(mvm->fwrt.dev, 0, DSM_FUNC_11AX_ENABLEMENT, + &iwl_guid, &value); + if (!ret) + cmd.oem_11ax_allow_bitmap = cpu_to_le32(value); + + ret = iwl_acpi_get_dsm_u32(mvm->fwrt.dev, 0, + DSM_FUNC_ENABLE_UNII4_CHAN, + &iwl_guid, &value); + if (!ret) + cmd.oem_unii4_allow_bitmap = cpu_to_le32(value); + + ret = iwl_acpi_get_dsm_u32(mvm->fwrt.dev, 0, + DSM_FUNC_ACTIVATE_CHANNEL, + &iwl_guid, &value); + if (!ret) + cmd.chan_state_active_bitmap = cpu_to_le32(value); + + ret = iwl_acpi_get_dsm_u32(mvm->fwrt.dev, 0, + DSM_FUNC_ENABLE_6E, + &iwl_guid, &value); + if (!ret) + cmd.oem_uhb_allow_bitmap = cpu_to_le32(value); + + if (cmd.config_bitmap || + cmd.oem_uhb_allow_bitmap || + cmd.oem_11ax_allow_bitmap || + cmd.oem_unii4_allow_bitmap || + cmd.chan_state_active_bitmap) { + size_t cmd_size; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, + REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_CHANGE, 1); + if (cmd_ver == 5) + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v5); + else if (cmd_ver == 4) + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v4); + else if (cmd_ver == 3) + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v3); + else if (cmd_ver == 2) + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v2); + else + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v1); + + IWL_DEBUG_RADIO(mvm, + "sending LARI_CONFIG_CHANGE, config_bitmap=0x%x, oem_11ax_allow_bitmap=0x%x\n", + le32_to_cpu(cmd.config_bitmap), + le32_to_cpu(cmd.oem_11ax_allow_bitmap)); + IWL_DEBUG_RADIO(mvm, + "sending LARI_CONFIG_CHANGE, oem_unii4_allow_bitmap=0x%x, chan_state_active_bitmap=0x%x, cmd_ver=%d\n", + le32_to_cpu(cmd.oem_unii4_allow_bitmap), + le32_to_cpu(cmd.chan_state_active_bitmap), + cmd_ver); + IWL_DEBUG_RADIO(mvm, + "sending LARI_CONFIG_CHANGE, oem_uhb_allow_bitmap=0x%x\n", + le32_to_cpu(cmd.oem_uhb_allow_bitmap)); + ret = iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_CHANGE), + 0, cmd_size, &cmd); + if (ret < 0) + IWL_DEBUG_RADIO(mvm, + "Failed to send LARI_CONFIG_CHANGE (%d)\n", + ret); + } +} + +void iwl_mvm_get_acpi_tables(struct iwl_mvm *mvm) +{ + int ret; + + /* read PPAG table */ + ret = iwl_mvm_get_ppag_table(mvm); + if (ret < 0) { + IWL_DEBUG_RADIO(mvm, + "PPAG BIOS table invalid or unavailable. (%d)\n", + ret); + } + + /* read SAR tables */ + ret = iwl_sar_get_wrds_table(&mvm->fwrt); + if (ret < 0) { + IWL_DEBUG_RADIO(mvm, + "WRDS SAR BIOS table invalid or unavailable. (%d)\n", + ret); + /* + * If not available, don't fail and don't bother with EWRD and + * WGDS */ + + if (!iwl_sar_get_wgds_table(&mvm->fwrt)) { + /* + * If basic SAR is not available, we check for WGDS, + * which should *not* be available either. If it is + * available, issue an error, because we can't use SAR + * Geo without basic SAR. + */ + IWL_ERR(mvm, "BIOS contains WGDS but no WRDS\n"); + } + + } else { + ret = iwl_sar_get_ewrd_table(&mvm->fwrt); + /* if EWRD is not available, we can still use + * WRDS, so don't fail */ + if (ret < 0) + IWL_DEBUG_RADIO(mvm, + "EWRD SAR BIOS table invalid or unavailable. (%d)\n", + ret); + + /* read geo SAR table */ + if (iwl_sar_geo_support(&mvm->fwrt)) { + ret = iwl_sar_get_wgds_table(&mvm->fwrt); + if (ret < 0) + IWL_DEBUG_RADIO(mvm, + "Geo SAR BIOS table invalid or unavailable. (%d)\n", + ret); + /* we don't fail if the table is not available */ + } + } +} +#else /* CONFIG_ACPI */ + +inline int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, + int prof_a, int prof_b) +{ + return 1; +} + +inline int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm) +{ + return -ENOENT; +} + +static int iwl_mvm_sar_geo_init(struct iwl_mvm *mvm) +{ + return 0; +} + +int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm) +{ + return -ENOENT; +} + +static int iwl_mvm_ppag_init(struct iwl_mvm *mvm) +{ + return 0; +} + +static void iwl_mvm_tas_init(struct iwl_mvm *mvm) +{ +} + +static void iwl_mvm_lari_cfg(struct iwl_mvm *mvm) +{ +} + +static u8 iwl_mvm_eval_dsm_rfi(struct iwl_mvm *mvm) +{ + return DSM_VALUE_RFI_DISABLE; +} + +void iwl_mvm_get_acpi_tables(struct iwl_mvm *mvm) +{ +} +#endif /* CONFIG_ACPI */ + +void iwl_mvm_send_recovery_cmd(struct iwl_mvm *mvm, u32 flags) +{ + u32 error_log_size = mvm->fw->ucode_capa.error_log_size; + int ret; + u32 resp; + + struct iwl_fw_error_recovery_cmd recovery_cmd = { + .flags = cpu_to_le32(flags), + .buf_size = 0, + }; + struct iwl_host_cmd host_cmd = { + .id = WIDE_ID(SYSTEM_GROUP, FW_ERROR_RECOVERY_CMD), + .flags = CMD_WANT_SKB, + .data = {&recovery_cmd, }, + .len = {sizeof(recovery_cmd), }, + }; + + /* no error log was defined in TLV */ + if (!error_log_size) + return; + + if (flags & ERROR_RECOVERY_UPDATE_DB) { + /* no buf was allocated while HW reset */ + if (!mvm->error_recovery_buf) + return; + + host_cmd.data[1] = mvm->error_recovery_buf; + host_cmd.len[1] = error_log_size; + host_cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY; + recovery_cmd.buf_size = cpu_to_le32(error_log_size); + } + + ret = iwl_mvm_send_cmd(mvm, &host_cmd); + kfree(mvm->error_recovery_buf); + mvm->error_recovery_buf = NULL; + + if (ret) { + IWL_ERR(mvm, "Failed to send recovery cmd %d\n", ret); + return; + } + + /* skb respond is only relevant in ERROR_RECOVERY_UPDATE_DB */ + if (flags & ERROR_RECOVERY_UPDATE_DB) { + resp = le32_to_cpu(*(__le32 *)host_cmd.resp_pkt->data); + if (resp) + IWL_ERR(mvm, + "Failed to send recovery cmd blob was invalid %d\n", + resp); + } +} + +static int iwl_mvm_sar_init(struct iwl_mvm *mvm) +{ + return iwl_mvm_sar_select_profile(mvm, 1, 1); +} + +static int iwl_mvm_load_rt_fw(struct iwl_mvm *mvm) +{ + int ret; + + if (iwl_mvm_has_unified_ucode(mvm)) + return iwl_run_unified_mvm_ucode(mvm); + + WARN_ON(!mvm->nvm_data); + ret = iwl_run_init_mvm_ucode(mvm); + + if (ret) { + IWL_ERR(mvm, "Failed to run INIT ucode: %d\n", ret); + + if (iwlmvm_mod_params.init_dbg) + return 0; + return ret; + } + + iwl_fw_dbg_stop_sync(&mvm->fwrt); + iwl_trans_stop_device(mvm->trans); + ret = iwl_trans_start_hw(mvm->trans); + if (ret) + return ret; + + mvm->rfkill_safe_init_done = false; + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_REGULAR); + if (ret) + return ret; + + mvm->rfkill_safe_init_done = true; + + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_AFTER_ALIVE, + NULL); + + return iwl_init_paging(&mvm->fwrt, mvm->fwrt.cur_fw_img); +} + +int iwl_mvm_up(struct iwl_mvm *mvm) +{ + int ret, i; + struct ieee80211_channel *chan; + struct cfg80211_chan_def chandef; + struct ieee80211_supported_band *sband = NULL; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_trans_start_hw(mvm->trans); + if (ret) + return ret; + + ret = iwl_mvm_load_rt_fw(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to start RT ucode: %d\n", ret); + if (ret != -ERFKILL) + iwl_fw_dbg_error_collect(&mvm->fwrt, + FW_DBG_TRIGGER_DRIVER); + goto error; + } + + iwl_get_shared_mem_conf(&mvm->fwrt); + + ret = iwl_mvm_sf_update(mvm, NULL, false); + if (ret) + IWL_ERR(mvm, "Failed to initialize Smart Fifo\n"); + + if (!iwl_trans_dbg_ini_valid(mvm->trans)) { + mvm->fwrt.dump.conf = FW_DBG_INVALID; + /* if we have a destination, assume EARLY START */ + if (mvm->fw->dbg.dest_tlv) + mvm->fwrt.dump.conf = FW_DBG_START_FROM_ALIVE; + iwl_fw_start_dbg_conf(&mvm->fwrt, FW_DBG_START_FROM_ALIVE); + } + + ret = iwl_send_tx_ant_cfg(mvm, iwl_mvm_get_valid_tx_ant(mvm)); + if (ret) + goto error; + + if (!iwl_mvm_has_unified_ucode(mvm)) { + /* Send phy db control command and then phy db calibration */ + ret = iwl_send_phy_db_data(mvm->phy_db); + if (ret) + goto error; + } + + ret = iwl_send_phy_cfg_cmd(mvm); + if (ret) + goto error; + + ret = iwl_mvm_send_bt_init_conf(mvm); + if (ret) + goto error; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SOC_LATENCY_SUPPORT)) { + ret = iwl_set_soc_latency(&mvm->fwrt); + if (ret) + goto error; + } + + /* Init RSS configuration */ + ret = iwl_configure_rxq(&mvm->fwrt); + if (ret) + goto error; + + if (iwl_mvm_has_new_rx_api(mvm)) { + ret = iwl_send_rss_cfg_cmd(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to configure RSS queues: %d\n", + ret); + goto error; + } + } + + /* init the fw <-> mac80211 STA mapping */ + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[i], NULL); + + mvm->tdls_cs.peer.sta_id = IWL_MVM_INVALID_STA; + + /* reset quota debouncing buffer - 0xff will yield invalid data */ + memset(&mvm->last_quota_cmd, 0xff, sizeof(mvm->last_quota_cmd)); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_DQA_SUPPORT)) { + ret = iwl_mvm_send_dqa_cmd(mvm); + if (ret) + goto error; + } + + /* + * Add auxiliary station for scanning. + * Newer versions of this command implies that the fw uses + * internal aux station for all aux activities that don't + * requires a dedicated data queue. + */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + ADD_STA, + 0) < 12) { + /* + * In old version the aux station uses mac id like other + * station and not lmac id + */ + ret = iwl_mvm_add_aux_sta(mvm, MAC_INDEX_AUX); + if (ret) + goto error; + } + + /* Add all the PHY contexts */ + i = 0; + while (!sband && i < NUM_NL80211_BANDS) + sband = mvm->hw->wiphy->bands[i++]; + + if (WARN_ON_ONCE(!sband)) + goto error; + + chan = &sband->channels[0]; + + cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT); + for (i = 0; i < NUM_PHY_CTX; i++) { + /* + * The channel used here isn't relevant as it's + * going to be overwritten in the other flows. + * For now use the first channel we have. + */ + ret = iwl_mvm_phy_ctxt_add(mvm, &mvm->phy_ctxts[i], + &chandef, 1, 1); + if (ret) + goto error; + } + + if (iwl_mvm_is_tt_in_fw(mvm)) { + /* in order to give the responsibility of ct-kill and + * TX backoff to FW we need to send empty temperature reporting + * cmd during init time + */ + iwl_mvm_send_temp_report_ths_cmd(mvm); + } else { + /* Initialize tx backoffs to the minimal possible */ + iwl_mvm_tt_tx_backoff(mvm, 0); + } + +#ifdef CONFIG_THERMAL + /* TODO: read the budget from BIOS / Platform NVM */ + + /* + * In case there is no budget from BIOS / Platform NVM the default + * budget should be 2000mW (cooling state 0). + */ + if (iwl_mvm_is_ctdp_supported(mvm)) { + ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START, + mvm->cooling_dev.cur_state); + if (ret) + goto error; + } +#endif + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SET_LTR_GEN2)) + WARN_ON(iwl_mvm_config_ltr(mvm)); + + ret = iwl_mvm_power_update_device(mvm); + if (ret) + goto error; + + iwl_mvm_lari_cfg(mvm); + /* + * RTNL is not taken during Ct-kill, but we don't need to scan/Tx + * anyway, so don't init MCC. + */ + if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) { + ret = iwl_mvm_init_mcc(mvm); + if (ret) + goto error; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + mvm->scan_type = IWL_SCAN_TYPE_NOT_SET; + mvm->hb_scan_type = IWL_SCAN_TYPE_NOT_SET; + ret = iwl_mvm_config_scan(mvm); + if (ret) + goto error; + } + + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + iwl_mvm_send_recovery_cmd(mvm, ERROR_RECOVERY_UPDATE_DB); + + if (iwl_acpi_get_eckv(mvm->dev, &mvm->ext_clock_valid)) + IWL_DEBUG_INFO(mvm, "ECKV table doesn't exist in BIOS\n"); + + ret = iwl_mvm_ppag_init(mvm); + if (ret) + goto error; + + ret = iwl_mvm_sar_init(mvm); + if (ret == 0) + ret = iwl_mvm_sar_geo_init(mvm); + else if (ret < 0) + goto error; + + iwl_mvm_tas_init(mvm); + iwl_mvm_leds_sync(mvm); + + iwl_mvm_ftm_initiator_smooth_config(mvm); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_RFIM_SUPPORT)) { + if (iwl_mvm_eval_dsm_rfi(mvm) == DSM_VALUE_RFI_ENABLE) + iwl_rfi_send_config_cmd(mvm, NULL); + } + + IWL_DEBUG_INFO(mvm, "RT uCode started.\n"); + return 0; + error: + if (!iwlmvm_mod_params.init_dbg || !ret) + iwl_mvm_stop_device(mvm); + return ret; +} + +int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm) +{ + int ret, i; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_trans_start_hw(mvm->trans); + if (ret) + return ret; + + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_WOWLAN); + if (ret) { + IWL_ERR(mvm, "Failed to start WoWLAN firmware: %d\n", ret); + goto error; + } + + ret = iwl_send_tx_ant_cfg(mvm, iwl_mvm_get_valid_tx_ant(mvm)); + if (ret) + goto error; + + /* Send phy db control command and then phy db calibration*/ + ret = iwl_send_phy_db_data(mvm->phy_db); + if (ret) + goto error; + + ret = iwl_send_phy_cfg_cmd(mvm); + if (ret) + goto error; + + /* init the fw <-> mac80211 STA mapping */ + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[i], NULL); + + if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + ADD_STA, + 0) < 12) { + /* + * Add auxiliary station for scanning. + * Newer versions of this command implies that the fw uses + * internal aux station for all aux activities that don't + * requires a dedicated data queue. + * In old version the aux station uses mac id like other + * station and not lmac id + */ + ret = iwl_mvm_add_aux_sta(mvm, MAC_INDEX_AUX); + if (ret) + goto error; + } + + return 0; + error: + iwl_mvm_stop_device(mvm); + return ret; +} + +void iwl_mvm_rx_card_state_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_card_state_notif *card_state_notif = (void *)pkt->data; + u32 flags = le32_to_cpu(card_state_notif->flags); + + IWL_DEBUG_RF_KILL(mvm, "Card state received: HW:%s SW:%s CT:%s\n", + (flags & HW_CARD_DISABLED) ? "Kill" : "On", + (flags & SW_CARD_DISABLED) ? "Kill" : "On", + (flags & CT_KILL_CARD_DISABLED) ? + "Reached" : "Not reached"); +} + +void iwl_mvm_rx_mfuart_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mfuart_load_notif *mfuart_notif = (void *)pkt->data; + + IWL_DEBUG_INFO(mvm, + "MFUART: installed ver: 0x%08x, external ver: 0x%08x, status: 0x%08x, duration: 0x%08x\n", + le32_to_cpu(mfuart_notif->installed_ver), + le32_to_cpu(mfuart_notif->external_ver), + le32_to_cpu(mfuart_notif->status), + le32_to_cpu(mfuart_notif->duration)); + + if (iwl_rx_packet_payload_len(pkt) == sizeof(*mfuart_notif)) + IWL_DEBUG_INFO(mvm, + "MFUART: image size: 0x%08x\n", + le32_to_cpu(mfuart_notif->image_size)); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/led.c b/sys/contrib/dev/iwlwifi/mvm/led.c new file mode 100644 index 00000000000..1ea7c44250d --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/led.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2019 Intel Corporation + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#include +#include "iwl-io.h" +#include "iwl-csr.h" +#include "mvm.h" + +static void iwl_mvm_send_led_fw_cmd(struct iwl_mvm *mvm, bool on) +{ + struct iwl_led_cmd led_cmd = { + .status = cpu_to_le32(on), + }; + struct iwl_host_cmd cmd = { + .id = WIDE_ID(LONG_GROUP, LEDS_CMD), + .len = { sizeof(led_cmd), }, + .data = { &led_cmd, }, + .flags = CMD_ASYNC, + }; + int err; + + if (!iwl_mvm_firmware_running(mvm)) + return; + + err = iwl_mvm_send_cmd(mvm, &cmd); + + if (err) + IWL_WARN(mvm, "LED command failed: %d\n", err); +} + +static void iwl_mvm_led_set(struct iwl_mvm *mvm, bool on) +{ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LED_CMD_SUPPORT)) { + iwl_mvm_send_led_fw_cmd(mvm, on); + return; + } + + iwl_write32(mvm->trans, CSR_LED_REG, + on ? CSR_LED_REG_TURN_ON : CSR_LED_REG_TURN_OFF); +} + +static void iwl_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct iwl_mvm *mvm = container_of(led_cdev, struct iwl_mvm, led); + + iwl_mvm_led_set(mvm, brightness > 0); +} + +int iwl_mvm_leds_init(struct iwl_mvm *mvm) +{ + int mode = iwlwifi_mod_params.led_mode; + int ret; + + switch (mode) { + case IWL_LED_BLINK: + IWL_ERR(mvm, "Blink led mode not supported, used default\n"); + fallthrough; + case IWL_LED_DEFAULT: + case IWL_LED_RF_STATE: + mode = IWL_LED_RF_STATE; + break; + case IWL_LED_DISABLE: + IWL_INFO(mvm, "Led disabled\n"); + return 0; + default: + return -EINVAL; + } + + mvm->led.name = kasprintf(GFP_KERNEL, "%s-led", + wiphy_name(mvm->hw->wiphy)); + if (!mvm->led.name) + return -ENOMEM; + + mvm->led.brightness_set = iwl_led_brightness_set; + mvm->led.max_brightness = 1; + + if (mode == IWL_LED_RF_STATE) + mvm->led.default_trigger = + ieee80211_get_radio_led_name(mvm->hw); + + ret = led_classdev_register(mvm->trans->dev, &mvm->led); + if (ret) { + kfree(mvm->led.name); + IWL_INFO(mvm, "Failed to enable led\n"); + return ret; + } + + mvm->init_status |= IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE; + return 0; +} + +void iwl_mvm_leds_sync(struct iwl_mvm *mvm) +{ + if (!(mvm->init_status & IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE)) + return; + + /* + * if we control through the register, we're doing it + * even when the firmware isn't up, so no need to sync + */ + if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) + return; + + iwl_mvm_led_set(mvm, mvm->led.brightness > 0); +} + +void iwl_mvm_leds_exit(struct iwl_mvm *mvm) +{ + if (!(mvm->init_status & IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE)) + return; + + led_classdev_unregister(&mvm->led); + kfree(mvm->led.name); + mvm->init_status &= ~IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE; +} diff --git a/sys/contrib/dev/iwlwifi/mvm/mac-ctxt.c b/sys/contrib/dev/iwlwifi/mvm/mac-ctxt.c new file mode 100644 index 00000000000..fd7d4abfb45 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/mac-ctxt.c @@ -0,0 +1,1639 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include +#include +#include +#include "iwl-io.h" +#include "iwl-prph.h" +#include "fw-api.h" +#include "mvm.h" +#include "time-event.h" + +const u8 iwl_mvm_ac_to_tx_fifo[] = { + IWL_MVM_TX_FIFO_VO, + IWL_MVM_TX_FIFO_VI, + IWL_MVM_TX_FIFO_BE, + IWL_MVM_TX_FIFO_BK, +}; + +const u8 iwl_mvm_ac_to_gen2_tx_fifo[] = { + IWL_GEN2_EDCA_TX_FIFO_VO, + IWL_GEN2_EDCA_TX_FIFO_VI, + IWL_GEN2_EDCA_TX_FIFO_BE, + IWL_GEN2_EDCA_TX_FIFO_BK, + IWL_GEN2_TRIG_TX_FIFO_VO, + IWL_GEN2_TRIG_TX_FIFO_VI, + IWL_GEN2_TRIG_TX_FIFO_BE, + IWL_GEN2_TRIG_TX_FIFO_BK, +}; + +struct iwl_mvm_mac_iface_iterator_data { + struct iwl_mvm *mvm; + struct ieee80211_vif *vif; + unsigned long available_mac_ids[BITS_TO_LONGS(NUM_MAC_INDEX_DRIVER)]; + unsigned long available_tsf_ids[BITS_TO_LONGS(NUM_TSF_IDS)]; + enum iwl_tsf_id preferred_tsf; + bool found_vif; +}; + +static void iwl_mvm_mac_tsf_id_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_mac_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 min_bi; + + /* Skip the interface for which we are trying to assign a tsf_id */ + if (vif == data->vif) + return; + + /* + * The TSF is a hardware/firmware resource, there are 4 and + * the driver should assign and free them as needed. However, + * there are cases where 2 MACs should share the same TSF ID + * for the purpose of clock sync, an optimization to avoid + * clock drift causing overlapping TBTTs/DTIMs for a GO and + * client in the system. + * + * The firmware will decide according to the MAC type which + * will be the leader and follower. Clients that need to sync + * with a remote station will be the leader, and an AP or GO + * will be the follower. + * + * Depending on the new interface type it can be following + * or become the leader of an existing interface. + */ + switch (data->vif->type) { + case NL80211_IFTYPE_STATION: + /* + * The new interface is a client, so if the one we're iterating + * is an AP, and the beacon interval of the AP is a multiple or + * divisor of the beacon interval of the client, the same TSF + * should be used to avoid drift between the new client and + * existing AP. The existing AP will get drift updates from the + * new client context in this case. + */ + if (vif->type != NL80211_IFTYPE_AP || + data->preferred_tsf != NUM_TSF_IDS || + !test_bit(mvmvif->tsf_id, data->available_tsf_ids)) + break; + + min_bi = min(data->vif->bss_conf.beacon_int, + vif->bss_conf.beacon_int); + + if (!min_bi) + break; + + if ((data->vif->bss_conf.beacon_int - + vif->bss_conf.beacon_int) % min_bi == 0) { + data->preferred_tsf = mvmvif->tsf_id; + return; + } + break; + + case NL80211_IFTYPE_AP: + /* + * The new interface is AP/GO, so if its beacon interval is a + * multiple or a divisor of the beacon interval of an existing + * interface, it should get drift updates from an existing + * client or use the same TSF as an existing GO. There's no + * drift between TSFs internally but if they used different + * TSFs then a new client MAC could update one of them and + * cause drift that way. + */ + if ((vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_STATION) || + data->preferred_tsf != NUM_TSF_IDS || + !test_bit(mvmvif->tsf_id, data->available_tsf_ids)) + break; + + min_bi = min(data->vif->bss_conf.beacon_int, + vif->bss_conf.beacon_int); + + if (!min_bi) + break; + + if ((data->vif->bss_conf.beacon_int - + vif->bss_conf.beacon_int) % min_bi == 0) { + data->preferred_tsf = mvmvif->tsf_id; + return; + } + break; + default: + /* + * For all other interface types there's no need to + * take drift into account. Either they're exclusive + * like IBSS and monitor, or we don't care much about + * their TSF (like P2P Device), but we won't be able + * to share the TSF resource. + */ + break; + } + + /* + * Unless we exited above, we can't share the TSF resource + * that the virtual interface we're iterating over is using + * with the new one, so clear the available bit and if this + * was the preferred one, reset that as well. + */ + __clear_bit(mvmvif->tsf_id, data->available_tsf_ids); + + if (data->preferred_tsf == mvmvif->tsf_id) + data->preferred_tsf = NUM_TSF_IDS; +} + +static void iwl_mvm_mac_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_mac_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* Iterator may already find the interface being added -- skip it */ + if (vif == data->vif) { + data->found_vif = true; + return; + } + + /* Mark MAC IDs as used by clearing the available bit, and + * (below) mark TSFs as used if their existing use is not + * compatible with the new interface type. + * No locking or atomic bit operations are needed since the + * data is on the stack of the caller function. + */ + __clear_bit(mvmvif->id, data->available_mac_ids); + + /* find a suitable tsf_id */ + iwl_mvm_mac_tsf_id_iter(_data, mac, vif); +} + +void iwl_mvm_mac_ctxt_recalc_tsf_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_mac_iface_iterator_data data = { + .mvm = mvm, + .vif = vif, + .available_tsf_ids = { (1 << NUM_TSF_IDS) - 1 }, + /* no preference yet */ + .preferred_tsf = NUM_TSF_IDS, + }; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_mac_tsf_id_iter, &data); + + if (data.preferred_tsf != NUM_TSF_IDS) + mvmvif->tsf_id = data.preferred_tsf; + else if (!test_bit(mvmvif->tsf_id, data.available_tsf_ids)) + mvmvif->tsf_id = find_first_bit(data.available_tsf_ids, + NUM_TSF_IDS); +} + +int iwl_mvm_mac_ctxt_init(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_mac_iface_iterator_data data = { + .mvm = mvm, + .vif = vif, + .available_mac_ids = { (1 << NUM_MAC_INDEX_DRIVER) - 1 }, + .available_tsf_ids = { (1 << NUM_TSF_IDS) - 1 }, + /* no preference yet */ + .preferred_tsf = NUM_TSF_IDS, + .found_vif = false, + }; + int ret, i; + + lockdep_assert_held(&mvm->mutex); + + /* + * Allocate a MAC ID and a TSF for this MAC, along with the queues + * and other resources. + */ + + /* + * Before the iterator, we start with all MAC IDs and TSFs available. + * + * During iteration, all MAC IDs are cleared that are in use by other + * virtual interfaces, and all TSF IDs are cleared that can't be used + * by this new virtual interface because they're used by an interface + * that can't share it with the new one. + * At the same time, we check if there's a preferred TSF in the case + * that we should share it with another interface. + */ + + /* Currently, MAC ID 0 should be used only for the managed/IBSS vif */ + switch (vif->type) { + case NL80211_IFTYPE_ADHOC: + break; + case NL80211_IFTYPE_STATION: + if (!vif->p2p) + break; + fallthrough; + default: + __clear_bit(0, data.available_mac_ids); + } + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_mac_iface_iterator, &data); + + /* + * In the case we're getting here during resume, it's similar to + * firmware restart, and with RESUME_ALL the iterator will find + * the vif being added already. + * We don't want to reassign any IDs in either case since doing + * so would probably assign different IDs (as interfaces aren't + * necessarily added in the same order), but the old IDs were + * preserved anyway, so skip ID assignment for both resume and + * recovery. + */ + if (data.found_vif) + return 0; + + /* Therefore, in recovery, we can't get here */ + if (WARN_ON_ONCE(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))) + return -EBUSY; + + mvmvif->id = find_first_bit(data.available_mac_ids, + NUM_MAC_INDEX_DRIVER); + if (mvmvif->id == NUM_MAC_INDEX_DRIVER) { + IWL_ERR(mvm, "Failed to init MAC context - no free ID!\n"); + ret = -EIO; + goto exit_fail; + } + + if (data.preferred_tsf != NUM_TSF_IDS) + mvmvif->tsf_id = data.preferred_tsf; + else + mvmvif->tsf_id = find_first_bit(data.available_tsf_ids, + NUM_TSF_IDS); + if (mvmvif->tsf_id == NUM_TSF_IDS) { + IWL_ERR(mvm, "Failed to init MAC context - no free TSF!\n"); + ret = -EIO; + goto exit_fail; + } + + mvmvif->color = 0; + + INIT_LIST_HEAD(&mvmvif->time_event_data.list); + mvmvif->time_event_data.id = TE_MAX; + + /* No need to allocate data queues to P2P Device MAC and NAN.*/ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) + return 0; + + /* Allocate the CAB queue for softAP and GO interfaces */ + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { + /* + * For TVQM this will be overwritten later with the FW assigned + * queue value (when queue is enabled). + */ + mvmvif->cab_queue = IWL_MVM_DQA_GCAST_QUEUE; + } + + mvmvif->bcast_sta.sta_id = IWL_MVM_INVALID_STA; + mvmvif->mcast_sta.sta_id = IWL_MVM_INVALID_STA; + mvmvif->ap_sta_id = IWL_MVM_INVALID_STA; + + for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++) + mvmvif->smps_requests[i] = IEEE80211_SMPS_AUTOMATIC; + + return 0; + +exit_fail: + memset(mvmvif, 0, sizeof(struct iwl_mvm_vif)); + return ret; +} + +static void iwl_mvm_ack_rates(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum nl80211_band band, + u8 *cck_rates, u8 *ofdm_rates) +{ + struct ieee80211_supported_band *sband; + unsigned long basic = vif->bss_conf.basic_rates; + int lowest_present_ofdm = 100; + int lowest_present_cck = 100; + u8 cck = 0; + u8 ofdm = 0; + int i; + + sband = mvm->hw->wiphy->bands[band]; + + for_each_set_bit(i, &basic, BITS_PER_LONG) { + int hw = sband->bitrates[i].hw_value; + if (hw >= IWL_FIRST_OFDM_RATE) { + ofdm |= BIT(hw - IWL_FIRST_OFDM_RATE); + if (lowest_present_ofdm > hw) + lowest_present_ofdm = hw; + } else { + BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + + cck |= BIT(hw); + if (lowest_present_cck > hw) + lowest_present_cck = hw; + } + } + + /* + * Now we've got the basic rates as bitmaps in the ofdm and cck + * variables. This isn't sufficient though, as there might not + * be all the right rates in the bitmap. E.g. if the only basic + * rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps + * and 6 Mbps because the 802.11-2007 standard says in 9.6: + * + * [...] a STA responding to a received frame shall transmit + * its Control Response frame [...] at the highest rate in the + * BSSBasicRateSet parameter that is less than or equal to the + * rate of the immediately previous frame in the frame exchange + * sequence ([...]) and that is of the same modulation class + * ([...]) as the received frame. If no rate contained in the + * BSSBasicRateSet parameter meets these conditions, then the + * control frame sent in response to a received frame shall be + * transmitted at the highest mandatory rate of the PHY that is + * less than or equal to the rate of the received frame, and + * that is of the same modulation class as the received frame. + * + * As a consequence, we need to add all mandatory rates that are + * lower than all of the basic rates to these bitmaps. + */ + + if (IWL_RATE_24M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE; + if (IWL_RATE_12M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE; + /* 6M already there or needed so always add */ + ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE; + + /* + * CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP. + * Note, however: + * - if no CCK rates are basic, it must be ERP since there must + * be some basic rates at all, so they're OFDM => ERP PHY + * (or we're in 5 GHz, and the cck bitmap will never be used) + * - if 11M is a basic rate, it must be ERP as well, so add 5.5M + * - if 5.5M is basic, 1M and 2M are mandatory + * - if 2M is basic, 1M is mandatory + * - if 1M is basic, that's the only valid ACK rate. + * As a consequence, it's not as complicated as it sounds, just add + * any lower rates to the ACK rate bitmap. + */ + if (IWL_RATE_11M_INDEX < lowest_present_cck) + cck |= IWL_RATE_BIT_MSK(11) >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_5M_INDEX < lowest_present_cck) + cck |= IWL_RATE_BIT_MSK(5) >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_2M_INDEX < lowest_present_cck) + cck |= IWL_RATE_BIT_MSK(2) >> IWL_FIRST_CCK_RATE; + /* 1M already there or needed so always add */ + cck |= IWL_RATE_BIT_MSK(1) >> IWL_FIRST_CCK_RATE; + + *cck_rates = cck; + *ofdm_rates = ofdm; +} + +static void iwl_mvm_mac_ctxt_set_ht_flags(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_ctx_cmd *cmd) +{ + /* for both sta and ap, ht_operation_mode hold the protection_mode */ + u8 protection_mode = vif->bss_conf.ht_operation_mode & + IEEE80211_HT_OP_MODE_PROTECTION; + /* The fw does not distinguish between ht and fat */ + u32 ht_flag = MAC_PROT_FLG_HT_PROT | MAC_PROT_FLG_FAT_PROT; + + IWL_DEBUG_RATE(mvm, "protection mode set to %d\n", protection_mode); + /* + * See section 9.23.3.1 of IEEE 80211-2012. + * Nongreenfield HT STAs Present is not supported. + */ + switch (protection_mode) { + case IEEE80211_HT_OP_MODE_PROTECTION_NONE: + break; + case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER: + case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED: + cmd->protection_flags |= cpu_to_le32(ht_flag); + break; + case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ: + /* Protect when channel wider than 20MHz */ + if (vif->bss_conf.chandef.width > NL80211_CHAN_WIDTH_20) + cmd->protection_flags |= cpu_to_le32(ht_flag); + break; + default: + IWL_ERR(mvm, "Illegal protection mode %d\n", + protection_mode); + break; + } +} + +static void iwl_mvm_mac_ctxt_cmd_common(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_ctx_cmd *cmd, + const u8 *bssid_override, + u32 action) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_chanctx_conf *chanctx; + bool ht_enabled = !!(vif->bss_conf.ht_operation_mode & + IEEE80211_HT_OP_MODE_PROTECTION); + u8 cck_ack_rates, ofdm_ack_rates; + const u8 *bssid = bssid_override ?: vif->bss_conf.bssid; + int i; + + cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + cmd->action = cpu_to_le32(action); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (vif->p2p) + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_P2P_STA); + else + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_BSS_STA); + break; + case NL80211_IFTYPE_AP: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_GO); + break; + case NL80211_IFTYPE_MONITOR: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_LISTENER); + break; + case NL80211_IFTYPE_P2P_DEVICE: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_P2P_DEVICE); + break; + case NL80211_IFTYPE_ADHOC: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_IBSS); + break; + default: + WARN_ON_ONCE(1); + } + + cmd->tsf_id = cpu_to_le32(mvmvif->tsf_id); + + memcpy(cmd->node_addr, vif->addr, ETH_ALEN); + + if (bssid) + memcpy(cmd->bssid_addr, bssid, ETH_ALEN); + else + eth_broadcast_addr(cmd->bssid_addr); + + rcu_read_lock(); + chanctx = rcu_dereference(vif->chanctx_conf); + iwl_mvm_ack_rates(mvm, vif, chanctx ? chanctx->def.chan->band + : NL80211_BAND_2GHZ, + &cck_ack_rates, &ofdm_ack_rates); + rcu_read_unlock(); + + cmd->cck_rates = cpu_to_le32((u32)cck_ack_rates); + cmd->ofdm_rates = cpu_to_le32((u32)ofdm_ack_rates); + + cmd->cck_short_preamble = + cpu_to_le32(vif->bss_conf.use_short_preamble ? + MAC_FLG_SHORT_PREAMBLE : 0); + cmd->short_slot = + cpu_to_le32(vif->bss_conf.use_short_slot ? + MAC_FLG_SHORT_SLOT : 0); + + cmd->filter_flags = 0; + + for (i = 0; i < IEEE80211_NUM_ACS; i++) { + u8 txf = iwl_mvm_mac_ac_to_tx_fifo(mvm, i); + u8 ucode_ac = iwl_mvm_mac80211_ac_to_ucode_ac(i); + + cmd->ac[ucode_ac].cw_min = + cpu_to_le16(mvmvif->queue_params[i].cw_min); + cmd->ac[ucode_ac].cw_max = + cpu_to_le16(mvmvif->queue_params[i].cw_max); + cmd->ac[ucode_ac].edca_txop = + cpu_to_le16(mvmvif->queue_params[i].txop * 32); + cmd->ac[ucode_ac].aifsn = mvmvif->queue_params[i].aifs; + cmd->ac[ucode_ac].fifos_mask = BIT(txf); + } + + if (vif->bss_conf.qos) + cmd->qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA); + + if (vif->bss_conf.use_cts_prot) + cmd->protection_flags |= cpu_to_le32(MAC_PROT_FLG_TGG_PROTECT); + + IWL_DEBUG_RATE(mvm, "use_cts_prot %d, ht_operation_mode %d\n", + vif->bss_conf.use_cts_prot, + vif->bss_conf.ht_operation_mode); + if (vif->bss_conf.chandef.width != NL80211_CHAN_WIDTH_20_NOHT) + cmd->qos_flags |= cpu_to_le32(MAC_QOS_FLG_TGN); + if (ht_enabled) + iwl_mvm_mac_ctxt_set_ht_flags(mvm, vif, cmd); +} + +static int iwl_mvm_mac_ctxt_send_cmd(struct iwl_mvm *mvm, + struct iwl_mac_ctx_cmd *cmd) +{ + int ret = iwl_mvm_send_cmd_pdu(mvm, MAC_CONTEXT_CMD, 0, + sizeof(*cmd), cmd); + if (ret) + IWL_ERR(mvm, "Failed to send MAC context (action:%d): %d\n", + le32_to_cpu(cmd->action), ret); + return ret; +} + +static int iwl_mvm_mac_ctxt_cmd_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action, bool force_assoc_off, + const u8 *bssid_override) +{ + struct iwl_mac_ctx_cmd cmd = {}; + struct iwl_mac_data_sta *ctxt_sta; + + WARN_ON(vif->type != NL80211_IFTYPE_STATION); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, bssid_override, action); + + if (vif->p2p) { + struct ieee80211_p2p_noa_attr *noa = + &vif->bss_conf.p2p_noa_attr; + + cmd.p2p_sta.ctwin = cpu_to_le32(noa->oppps_ctwindow & + IEEE80211_P2P_OPPPS_CTWINDOW_MASK); + ctxt_sta = &cmd.p2p_sta.sta; + } else { + ctxt_sta = &cmd.sta; + } + + /* We need the dtim_period to set the MAC as associated */ + if (vif->bss_conf.assoc && vif->bss_conf.dtim_period && + !force_assoc_off) { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u8 ap_sta_id = mvmvif->ap_sta_id; + u32 dtim_offs; + + /* + * The DTIM count counts down, so when it is N that means N + * more beacon intervals happen until the DTIM TBTT. Therefore + * add this to the current time. If that ends up being in the + * future, the firmware will handle it. + * + * Also note that the system_timestamp (which we get here as + * "sync_device_ts") and TSF timestamp aren't at exactly the + * same offset in the frame -- the TSF is at the first symbol + * of the TSF, the system timestamp is at signal acquisition + * time. This means there's an offset between them of at most + * a few hundred microseconds (24 * 8 bits + PLCP time gives + * 384us in the longest case), this is currently not relevant + * as the firmware wakes up around 2ms before the TBTT. + */ + dtim_offs = vif->bss_conf.sync_dtim_count * + vif->bss_conf.beacon_int; + /* convert TU to usecs */ + dtim_offs *= 1024; + + ctxt_sta->dtim_tsf = + cpu_to_le64(vif->bss_conf.sync_tsf + dtim_offs); + ctxt_sta->dtim_time = + cpu_to_le32(vif->bss_conf.sync_device_ts + dtim_offs); + ctxt_sta->assoc_beacon_arrive_time = + cpu_to_le32(vif->bss_conf.sync_device_ts); + + IWL_DEBUG_INFO(mvm, "DTIM TBTT is 0x%llx/0x%x, offset %d\n", + le64_to_cpu(ctxt_sta->dtim_tsf), + le32_to_cpu(ctxt_sta->dtim_time), + dtim_offs); + + ctxt_sta->is_assoc = cpu_to_le32(1); + + if (!mvmvif->authorized && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_COEX_HIGH_PRIO)) + ctxt_sta->data_policy |= + cpu_to_le32(COEX_HIGH_PRIORITY_ENABLE); + + /* + * allow multicast data frames only as long as the station is + * authorized, i.e., GTK keys are already installed (if needed) + */ + if (ap_sta_id < mvm->fw->ucode_capa.num_stations) { + struct ieee80211_sta *sta; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[ap_sta_id]); + if (!IS_ERR_OR_NULL(sta)) { + struct iwl_mvm_sta *mvmsta = + iwl_mvm_sta_from_mac80211(sta); + + if (mvmsta->sta_state == + IEEE80211_STA_AUTHORIZED) + cmd.filter_flags |= + cpu_to_le32(MAC_FILTER_ACCEPT_GRP); + } + + rcu_read_unlock(); + } + } else { + ctxt_sta->is_assoc = cpu_to_le32(0); + + /* Allow beacons to pass through as long as we are not + * associated, or we do not have dtim period information. + */ + cmd.filter_flags |= cpu_to_le32(MAC_FILTER_IN_BEACON); + } + + ctxt_sta->bi = cpu_to_le32(vif->bss_conf.beacon_int); + ctxt_sta->dtim_interval = cpu_to_le32(vif->bss_conf.beacon_int * + vif->bss_conf.dtim_period); + + ctxt_sta->listen_interval = cpu_to_le32(mvm->hw->conf.listen_interval); + ctxt_sta->assoc_id = cpu_to_le32(vif->bss_conf.aid); + + if (vif->probe_req_reg && vif->bss_conf.assoc && vif->p2p) + cmd.filter_flags |= cpu_to_le32(MAC_FILTER_IN_PROBE_REQUEST); + + if (vif->bss_conf.he_support && !iwlwifi_mod_params.disable_11ax) { + cmd.filter_flags |= cpu_to_le32(MAC_FILTER_IN_11AX); + if (vif->bss_conf.twt_requester && IWL_MVM_USE_TWT) + ctxt_sta->data_policy |= cpu_to_le32(TWT_SUPPORTED); + if (vif->bss_conf.twt_protected) + ctxt_sta->data_policy |= + cpu_to_le32(PROTECTED_TWT_SUPPORTED); + if (vif->bss_conf.twt_broadcast) + ctxt_sta->data_policy |= + cpu_to_le32(BROADCAST_TWT_SUPPORTED); + } + + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_listener(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + u32 tfd_queue_msk = BIT(mvm->snif_queue); + int ret; + + WARN_ON(vif->type != NL80211_IFTYPE_MONITOR); + + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + cmd.filter_flags = cpu_to_le32(MAC_FILTER_IN_PROMISC | + MAC_FILTER_IN_CONTROL_AND_MGMT | + MAC_FILTER_IN_BEACON | + MAC_FILTER_IN_PROBE_REQUEST | + MAC_FILTER_IN_CRC32 | + MAC_FILTER_ACCEPT_GRP); + ieee80211_hw_set(mvm->hw, RX_INCLUDES_FCS); + + /* Allocate sniffer station */ + ret = iwl_mvm_allocate_int_sta(mvm, &mvm->snif_sta, tfd_queue_msk, + vif->type, IWL_STA_GENERAL_PURPOSE); + if (ret) + return ret; + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_ibss(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_ctx_cmd cmd = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_ADHOC); + + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + cmd.filter_flags = cpu_to_le32(MAC_FILTER_IN_BEACON | + MAC_FILTER_IN_PROBE_REQUEST | + MAC_FILTER_ACCEPT_GRP); + + /* cmd.ibss.beacon_time/cmd.ibss.beacon_tsf are curently ignored */ + cmd.ibss.bi = cpu_to_le32(vif->bss_conf.beacon_int); + + /* TODO: Assumes that the beacon id == mac context id */ + cmd.ibss.beacon_template = cpu_to_le32(mvmvif->id); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +struct iwl_mvm_go_iterator_data { + bool go_active; +}; + +static void iwl_mvm_go_iterator(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + struct iwl_mvm_go_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type == NL80211_IFTYPE_AP && vif->p2p && + mvmvif->ap_ibss_active) + data->go_active = true; +} + +static int iwl_mvm_mac_ctxt_cmd_p2p_device(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + struct iwl_mvm_go_iterator_data data = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_P2P_DEVICE); + + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + /* Override the filter flags to accept only probe requests */ + cmd.filter_flags = cpu_to_le32(MAC_FILTER_IN_PROBE_REQUEST); + + /* + * This flag should be set to true when the P2P Device is + * discoverable and there is at least another active P2P GO. Settings + * this flag will allow the P2P Device to be discoverable on other + * channels in addition to its listen channel. + * Note that this flag should not be set in other cases as it opens the + * Rx filters on all MAC and increases the number of interrupts. + */ + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_go_iterator, &data); + + cmd.p2p_dev.is_disc_extended = cpu_to_le32(data.go_active ? 1 : 0); + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +void iwl_mvm_mac_ctxt_set_tim(struct iwl_mvm *mvm, + __le32 *tim_index, __le32 *tim_size, + u8 *beacon, u32 frame_size) +{ + u32 tim_idx; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)beacon; + + /* The index is relative to frame start but we start looking at the + * variable-length part of the beacon. */ + tim_idx = mgmt->u.beacon.variable - beacon; + + /* Parse variable-length elements of beacon to find WLAN_EID_TIM */ + while ((tim_idx < (frame_size - 2)) && + (beacon[tim_idx] != WLAN_EID_TIM)) + tim_idx += beacon[tim_idx+1] + 2; + + /* If TIM field was found, set variables */ + if ((tim_idx < (frame_size - 1)) && (beacon[tim_idx] == WLAN_EID_TIM)) { + *tim_index = cpu_to_le32(tim_idx); + *tim_size = cpu_to_le32((u32)beacon[tim_idx + 1]); + } else { + IWL_WARN(mvm, "Unable to find TIM Element in beacon\n"); + } +} + +static u32 iwl_mvm_find_ie_offset(u8 *beacon, u8 eid, u32 frame_size) +{ + struct ieee80211_mgmt *mgmt = (void *)beacon; + const u8 *ie; + + if (WARN_ON_ONCE(frame_size <= (mgmt->u.beacon.variable - beacon))) + return 0; + + frame_size -= mgmt->u.beacon.variable - beacon; + + ie = cfg80211_find_ie(eid, mgmt->u.beacon.variable, frame_size); + if (!ie) + return 0; + + return ie - beacon; +} + +u8 iwl_mvm_mac_ctxt_get_lowest_rate(struct ieee80211_tx_info *info, + struct ieee80211_vif *vif) +{ + u8 rate; + if (info->band == NL80211_BAND_2GHZ && !vif->p2p) + rate = IWL_FIRST_CCK_RATE; + else + rate = IWL_FIRST_OFDM_RATE; + + return rate; +} + +u16 iwl_mvm_mac_ctxt_get_beacon_flags(const struct iwl_fw *fw, u8 rate_idx) +{ + u16 flags = iwl_mvm_mac80211_idx_to_hwrate(fw, rate_idx); + bool is_new_rate = iwl_fw_lookup_cmd_ver(fw, + LONG_GROUP, + BEACON_TEMPLATE_CMD, + 0) > 10; + + if (rate_idx <= IWL_FIRST_CCK_RATE) + flags |= is_new_rate ? IWL_MAC_BEACON_CCK + : IWL_MAC_BEACON_CCK_V1; + + return flags; +} + +static void iwl_mvm_mac_ctxt_set_tx(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon, + struct iwl_tx_cmd *tx) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_tx_info *info; + u8 rate; + u32 tx_flags; + + info = IEEE80211_SKB_CB(beacon); + + /* Set up TX command fields */ + tx->len = cpu_to_le16((u16)beacon->len); + tx->sta_id = mvmvif->bcast_sta.sta_id; + tx->life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); + tx_flags = TX_CMD_FLG_SEQ_CTL | TX_CMD_FLG_TSF; + tx_flags |= + iwl_mvm_bt_coex_tx_prio(mvm, (void *)beacon->data, info, 0) << + TX_CMD_FLG_BT_PRIO_POS; + tx->tx_flags = cpu_to_le32(tx_flags); + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION)) + iwl_mvm_toggle_tx_ant(mvm, &mvm->mgmt_last_antenna_idx); + + tx->rate_n_flags = + cpu_to_le32(BIT(mvm->mgmt_last_antenna_idx) << + RATE_MCS_ANT_POS); + + rate = iwl_mvm_mac_ctxt_get_lowest_rate(info, vif); + + tx->rate_n_flags |= + cpu_to_le32(iwl_mvm_mac80211_idx_to_hwrate(mvm->fw, rate)); + if (rate == IWL_FIRST_CCK_RATE) + tx->rate_n_flags |= cpu_to_le32(RATE_MCS_CCK_MSK_V1); + +} + +int iwl_mvm_mac_ctxt_send_beacon_cmd(struct iwl_mvm *mvm, + struct sk_buff *beacon, + void *data, int len) +{ + struct iwl_host_cmd cmd = { + .id = BEACON_TEMPLATE_CMD, + .flags = CMD_ASYNC, + }; + + cmd.len[0] = len; + cmd.data[0] = data; + cmd.dataflags[0] = 0; + cmd.len[1] = beacon->len; + cmd.data[1] = beacon->data; + cmd.dataflags[1] = IWL_HCMD_DFL_DUP; + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_send_beacon_v6(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_beacon_cmd_v6 beacon_cmd = {}; + + iwl_mvm_mac_ctxt_set_tx(mvm, vif, beacon, &beacon_cmd.tx); + + beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id); + + if (vif->type == NL80211_IFTYPE_AP) + iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd.tim_idx, + &beacon_cmd.tim_size, + beacon->data, beacon->len); + + return iwl_mvm_mac_ctxt_send_beacon_cmd(mvm, beacon, &beacon_cmd, + sizeof(beacon_cmd)); +} + +static int iwl_mvm_mac_ctxt_send_beacon_v7(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_beacon_cmd_v7 beacon_cmd = {}; + + iwl_mvm_mac_ctxt_set_tx(mvm, vif, beacon, &beacon_cmd.tx); + + beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id); + + if (vif->type == NL80211_IFTYPE_AP) + iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd.tim_idx, + &beacon_cmd.tim_size, + beacon->data, beacon->len); + + beacon_cmd.csa_offset = + cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data, + WLAN_EID_CHANNEL_SWITCH, + beacon->len)); + beacon_cmd.ecsa_offset = + cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data, + WLAN_EID_EXT_CHANSWITCH_ANN, + beacon->len)); + + return iwl_mvm_mac_ctxt_send_beacon_cmd(mvm, beacon, &beacon_cmd, + sizeof(beacon_cmd)); +} + +static int iwl_mvm_mac_ctxt_send_beacon_v9(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(beacon); + struct iwl_mac_beacon_cmd beacon_cmd = {}; + u8 rate = iwl_mvm_mac_ctxt_get_lowest_rate(info, vif); + u16 flags; + struct ieee80211_chanctx_conf *ctx; + int channel; + flags = iwl_mvm_mac_ctxt_get_beacon_flags(mvm->fw, rate); + + /* Enable FILS on PSC channels only */ + rcu_read_lock(); + ctx = rcu_dereference(vif->chanctx_conf); + channel = ieee80211_frequency_to_channel(ctx->def.chan->center_freq); + WARN_ON(channel == 0); + if (cfg80211_channel_is_psc(ctx->def.chan) && + !IWL_MVM_DISABLE_AP_FILS) { + flags |= iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + BEACON_TEMPLATE_CMD, + 0) > 10 ? + IWL_MAC_BEACON_FILS : + IWL_MAC_BEACON_FILS_V1; + beacon_cmd.short_ssid = + cpu_to_le32(~crc32_le(~0, vif->bss_conf.ssid, + vif->bss_conf.ssid_len)); + } + rcu_read_unlock(); + + beacon_cmd.flags = cpu_to_le16(flags); + beacon_cmd.byte_cnt = cpu_to_le16((u16)beacon->len); + beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id); + + if (vif->type == NL80211_IFTYPE_AP) + iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd.tim_idx, + &beacon_cmd.tim_size, + beacon->data, beacon->len); + + beacon_cmd.csa_offset = + cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data, + WLAN_EID_CHANNEL_SWITCH, + beacon->len)); + beacon_cmd.ecsa_offset = + cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data, + WLAN_EID_EXT_CHANSWITCH_ANN, + beacon->len)); + + return iwl_mvm_mac_ctxt_send_beacon_cmd(mvm, beacon, &beacon_cmd, + sizeof(beacon_cmd)); +} + +int iwl_mvm_mac_ctxt_send_beacon(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon) +{ + if (WARN_ON(!beacon)) + return -EINVAL; + + if (IWL_MVM_NON_TRANSMITTING_AP) + return 0; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CSA_AND_TBTT_OFFLOAD)) + return iwl_mvm_mac_ctxt_send_beacon_v6(mvm, vif, beacon); + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_NEW_BEACON_TEMPLATE)) + return iwl_mvm_mac_ctxt_send_beacon_v9(mvm, vif, beacon); + + return iwl_mvm_mac_ctxt_send_beacon_v7(mvm, vif, beacon); +} + +/* The beacon template for the AP/GO/IBSS has changed and needs update */ +int iwl_mvm_mac_ctxt_beacon_changed(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct sk_buff *beacon; + int ret; + + WARN_ON(vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_ADHOC); + + beacon = ieee80211_beacon_get_template(mvm->hw, vif, NULL); + if (!beacon) + return -ENOMEM; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvm->beacon_inject_active) { + dev_kfree_skb(beacon); + return -EBUSY; + } +#endif + + ret = iwl_mvm_mac_ctxt_send_beacon(mvm, vif, beacon); + dev_kfree_skb(beacon); + return ret; +} + +struct iwl_mvm_mac_ap_iterator_data { + struct iwl_mvm *mvm; + struct ieee80211_vif *vif; + u32 beacon_device_ts; + u16 beacon_int; +}; + +/* Find the beacon_device_ts and beacon_int for a managed interface */ +static void iwl_mvm_mac_ap_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_mac_ap_iterator_data *data = _data; + + if (vif->type != NL80211_IFTYPE_STATION || !vif->bss_conf.assoc) + return; + + /* Station client has higher priority over P2P client*/ + if (vif->p2p && data->beacon_device_ts) + return; + + data->beacon_device_ts = vif->bss_conf.sync_device_ts; + data->beacon_int = vif->bss_conf.beacon_int; +} + +/* + * Fill the specific data for mac context of type AP of P2P GO + */ +static void iwl_mvm_mac_ctxt_cmd_fill_ap(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_ctx_cmd *cmd, + struct iwl_mac_data_ap *ctxt_ap, + bool add) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_mac_ap_iterator_data data = { + .mvm = mvm, + .vif = vif, + .beacon_device_ts = 0 + }; + + /* in AP mode, the MCAST FIFO takes the EDCA params from VO */ + cmd->ac[IWL_MVM_TX_FIFO_VO].fifos_mask |= BIT(IWL_MVM_TX_FIFO_MCAST); + + /* + * in AP mode, pass probe requests and beacons from other APs + * (needed for ht protection); when there're no any associated + * station don't ask FW to pass beacons to prevent unnecessary + * wake-ups. + */ + cmd->filter_flags |= cpu_to_le32(MAC_FILTER_IN_PROBE_REQUEST); + if (mvmvif->ap_assoc_sta_count || !mvm->drop_bcn_ap_mode) { + cmd->filter_flags |= cpu_to_le32(MAC_FILTER_IN_BEACON); + IWL_DEBUG_HC(mvm, "Asking FW to pass beacons\n"); + } else { + IWL_DEBUG_HC(mvm, "No need to receive beacons\n"); + } + + ctxt_ap->bi = cpu_to_le32(vif->bss_conf.beacon_int); + ctxt_ap->dtim_interval = cpu_to_le32(vif->bss_conf.beacon_int * + vif->bss_conf.dtim_period); + + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_STA_TYPE)) + ctxt_ap->mcast_qid = cpu_to_le32(mvmvif->cab_queue); + + /* + * Only set the beacon time when the MAC is being added, when we + * just modify the MAC then we should keep the time -- the firmware + * can otherwise have a "jumping" TBTT. + */ + if (add) { + /* + * If there is a station/P2P client interface which is + * associated, set the AP's TBTT far enough from the station's + * TBTT. Otherwise, set it to the current system time + */ + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_mac_ap_iterator, &data); + + if (data.beacon_device_ts) { + u32 rand = (prandom_u32() % (64 - 36)) + 36; + mvmvif->ap_beacon_time = data.beacon_device_ts + + ieee80211_tu_to_usec(data.beacon_int * rand / + 100); + } else { + mvmvif->ap_beacon_time = iwl_mvm_get_systime(mvm); + } + } + + ctxt_ap->beacon_time = cpu_to_le32(mvmvif->ap_beacon_time); + ctxt_ap->beacon_tsf = 0; /* unused */ + + /* TODO: Assume that the beacon id == mac context id */ + ctxt_ap->beacon_template = cpu_to_le32(mvmvif->id); +} + +static int iwl_mvm_mac_ctxt_cmd_ap(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_AP || vif->p2p); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + /* Fill the data specific for ap mode */ + iwl_mvm_mac_ctxt_cmd_fill_ap(mvm, vif, &cmd, &cmd.ap, + action == FW_CTXT_ACTION_ADD); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_go(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + struct ieee80211_p2p_noa_attr *noa = &vif->bss_conf.p2p_noa_attr; + + WARN_ON(vif->type != NL80211_IFTYPE_AP || !vif->p2p); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + /* Fill the data specific for GO mode */ + iwl_mvm_mac_ctxt_cmd_fill_ap(mvm, vif, &cmd, &cmd.go.ap, + action == FW_CTXT_ACTION_ADD); + + cmd.go.ctwin = cpu_to_le32(noa->oppps_ctwindow & + IEEE80211_P2P_OPPPS_CTWINDOW_MASK); + cmd.go.opp_ps_enabled = + cpu_to_le32(!!(noa->oppps_ctwindow & + IEEE80211_P2P_OPPPS_ENABLE_BIT)); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctx_send(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + u32 action, bool force_assoc_off, + const u8 *bssid_override) +{ + switch (vif->type) { + case NL80211_IFTYPE_STATION: + return iwl_mvm_mac_ctxt_cmd_sta(mvm, vif, action, + force_assoc_off, + bssid_override); + case NL80211_IFTYPE_AP: + if (!vif->p2p) + return iwl_mvm_mac_ctxt_cmd_ap(mvm, vif, action); + else + return iwl_mvm_mac_ctxt_cmd_go(mvm, vif, action); + case NL80211_IFTYPE_MONITOR: + return iwl_mvm_mac_ctxt_cmd_listener(mvm, vif, action); + case NL80211_IFTYPE_P2P_DEVICE: + return iwl_mvm_mac_ctxt_cmd_p2p_device(mvm, vif, action); + case NL80211_IFTYPE_ADHOC: + return iwl_mvm_mac_ctxt_cmd_ibss(mvm, vif, action); + default: + break; + } + + return -EOPNOTSUPP; +} + +int iwl_mvm_mac_ctxt_add(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + if (WARN_ONCE(mvmvif->uploaded, "Adding active MAC %pM/%d\n", + vif->addr, ieee80211_vif_type_p2p(vif))) + return -EIO; + + ret = iwl_mvm_mac_ctx_send(mvm, vif, FW_CTXT_ACTION_ADD, + true, NULL); + if (ret) + return ret; + + /* will only do anything at resume from D3 time */ + iwl_mvm_set_last_nonqos_seq(mvm, vif); + + mvmvif->uploaded = true; + return 0; +} + +int iwl_mvm_mac_ctxt_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool force_assoc_off, const u8 *bssid_override) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (WARN_ONCE(!mvmvif->uploaded, "Changing inactive MAC %pM/%d\n", + vif->addr, ieee80211_vif_type_p2p(vif))) + return -EIO; + + return iwl_mvm_mac_ctx_send(mvm, vif, FW_CTXT_ACTION_MODIFY, + force_assoc_off, bssid_override); +} + +int iwl_mvm_mac_ctxt_remove(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_ctx_cmd cmd; + int ret; + + if (WARN_ONCE(!mvmvif->uploaded, "Removing inactive MAC %pM/%d\n", + vif->addr, ieee80211_vif_type_p2p(vif))) + return -EIO; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); + + ret = iwl_mvm_send_cmd_pdu(mvm, MAC_CONTEXT_CMD, 0, + sizeof(cmd), &cmd); + if (ret) { + IWL_ERR(mvm, "Failed to remove MAC context: %d\n", ret); + return ret; + } + + mvmvif->uploaded = false; + + if (vif->type == NL80211_IFTYPE_MONITOR) { + __clear_bit(IEEE80211_HW_RX_INCLUDES_FCS, mvm->hw->flags); + iwl_mvm_dealloc_snif_sta(mvm); + } + + return 0; +} + +static void iwl_mvm_csa_count_down(struct iwl_mvm *mvm, + struct ieee80211_vif *csa_vif, u32 gp2, + bool tx_success) +{ + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(csa_vif); + + /* Don't start to countdown from a failed beacon */ + if (!tx_success && !mvmvif->csa_countdown) + return; + + mvmvif->csa_countdown = true; + + if (!ieee80211_beacon_cntdwn_is_complete(csa_vif)) { + int c = ieee80211_beacon_update_cntdwn(csa_vif); + + iwl_mvm_mac_ctxt_beacon_changed(mvm, csa_vif); + if (csa_vif->p2p && + !iwl_mvm_te_scheduled(&mvmvif->time_event_data) && gp2 && + tx_success) { + u32 rel_time = (c + 1) * + csa_vif->bss_conf.beacon_int - + IWL_MVM_CHANNEL_SWITCH_TIME_GO; + u32 apply_time = gp2 + rel_time * 1024; + + iwl_mvm_schedule_csa_period(mvm, csa_vif, + IWL_MVM_CHANNEL_SWITCH_TIME_GO - + IWL_MVM_CHANNEL_SWITCH_MARGIN, + apply_time); + } + } else if (!iwl_mvm_te_scheduled(&mvmvif->time_event_data)) { + /* we don't have CSA NoA scheduled yet, switch now */ + ieee80211_csa_finish(csa_vif); + RCU_INIT_POINTER(mvm->csa_vif, NULL); + } +} + +void iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_extended_beacon_notif *beacon = (void *)pkt->data; + struct iwl_extended_beacon_notif_v5 *beacon_v5 = (void *)pkt->data; + struct ieee80211_vif *csa_vif; + struct ieee80211_vif *tx_blocked_vif; + struct agg_tx_status *agg_status; + u16 status; + + lockdep_assert_held(&mvm->mutex); + + mvm->ap_last_beacon_gp2 = le32_to_cpu(beacon->gp2); + + if (!iwl_mvm_is_short_beacon_notif_supported(mvm)) { + struct iwl_mvm_tx_resp *beacon_notify_hdr = + &beacon_v5->beacon_notify_hdr; + + if (unlikely(pkt_len < sizeof(*beacon_v5))) + return; + + mvm->ibss_manager = beacon_v5->ibss_mgr_status != 0; + agg_status = iwl_mvm_get_agg_status(mvm, beacon_notify_hdr); + status = le16_to_cpu(agg_status->status) & TX_STATUS_MSK; + IWL_DEBUG_RX(mvm, + "beacon status %#x retries:%d tsf:0x%016llX gp2:0x%X rate:%d\n", + status, beacon_notify_hdr->failure_frame, + le64_to_cpu(beacon->tsf), + mvm->ap_last_beacon_gp2, + le32_to_cpu(beacon_notify_hdr->initial_rate)); + } else { + if (unlikely(pkt_len < sizeof(*beacon))) + return; + + mvm->ibss_manager = beacon->ibss_mgr_status != 0; + status = le32_to_cpu(beacon->status) & TX_STATUS_MSK; + IWL_DEBUG_RX(mvm, + "beacon status %#x tsf:0x%016llX gp2:0x%X\n", + status, le64_to_cpu(beacon->tsf), + mvm->ap_last_beacon_gp2); + } + + csa_vif = rcu_dereference_protected(mvm->csa_vif, + lockdep_is_held(&mvm->mutex)); + if (unlikely(csa_vif && csa_vif->csa_active)) + iwl_mvm_csa_count_down(mvm, csa_vif, mvm->ap_last_beacon_gp2, + (status == TX_STATUS_SUCCESS)); + + tx_blocked_vif = rcu_dereference_protected(mvm->csa_tx_blocked_vif, + lockdep_is_held(&mvm->mutex)); + if (unlikely(tx_blocked_vif)) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(tx_blocked_vif); + + /* + * The channel switch is started and we have blocked the + * stations. If this is the first beacon (the timeout wasn't + * set), set the unblock timeout, otherwise countdown + */ + if (!mvm->csa_tx_block_bcn_timeout) + mvm->csa_tx_block_bcn_timeout = + IWL_MVM_CS_UNBLOCK_TX_TIMEOUT; + else + mvm->csa_tx_block_bcn_timeout--; + + /* Check if the timeout is expired, and unblock tx */ + if (mvm->csa_tx_block_bcn_timeout == 0) { + iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, false); + RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); + } + } +} + +void iwl_mvm_rx_missed_beacons_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_missed_beacons_notif *mb = (void *)pkt->data; + struct iwl_fw_dbg_trigger_missed_bcon *bcon_trig; + struct iwl_fw_dbg_trigger_tlv *trigger; + u32 stop_trig_missed_bcon, stop_trig_missed_bcon_since_rx; + u32 rx_missed_bcon, rx_missed_bcon_since_rx; + struct ieee80211_vif *vif; + u32 id = le32_to_cpu(mb->mac_id); + union iwl_dbg_tlv_tp_data tp_data = { .fw_pkt = pkt }; + + IWL_DEBUG_INFO(mvm, + "missed bcn mac_id=%u, consecutive=%u (%u, %u, %u)\n", + le32_to_cpu(mb->mac_id), + le32_to_cpu(mb->consec_missed_beacons), + le32_to_cpu(mb->consec_missed_beacons_since_last_rx), + le32_to_cpu(mb->num_recvd_beacons), + le32_to_cpu(mb->num_expected_beacons)); + + rcu_read_lock(); + + vif = iwl_mvm_rcu_dereference_vif_id(mvm, id, true); + if (!vif) + goto out; + + rx_missed_bcon = le32_to_cpu(mb->consec_missed_beacons); + rx_missed_bcon_since_rx = + le32_to_cpu(mb->consec_missed_beacons_since_last_rx); + /* + * TODO: the threshold should be adjusted based on latency conditions, + * and/or in case of a CS flow on one of the other AP vifs. + */ + if (rx_missed_bcon > IWL_MVM_MISSED_BEACONS_THRESHOLD_LONG) + iwl_mvm_connection_loss(mvm, vif, "missed beacons"); + else if (rx_missed_bcon_since_rx > IWL_MVM_MISSED_BEACONS_THRESHOLD) + ieee80211_beacon_loss(vif); + + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_MISSED_BEACONS, &tp_data); + + trigger = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_MISSED_BEACONS); + if (!trigger) + goto out; + + bcon_trig = (void *)trigger->data; + stop_trig_missed_bcon = le32_to_cpu(bcon_trig->stop_consec_missed_bcon); + stop_trig_missed_bcon_since_rx = + le32_to_cpu(bcon_trig->stop_consec_missed_bcon_since_rx); + + /* TODO: implement start trigger */ + + if (rx_missed_bcon_since_rx >= stop_trig_missed_bcon_since_rx || + rx_missed_bcon >= stop_trig_missed_bcon) + iwl_fw_dbg_collect_trig(&mvm->fwrt, trigger, NULL); + +out: + rcu_read_unlock(); +} + +void iwl_mvm_rx_stored_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_stored_beacon_notif_common *sb = (void *)pkt->data; + struct ieee80211_rx_status rx_status; + struct sk_buff *skb; + u8 *data; + u32 size = le32_to_cpu(sb->byte_count); + int ver = iwl_fw_lookup_cmd_ver(mvm->fw, PROT_OFFLOAD_GROUP, + STORED_BEACON_NTF, 0); + + if (size == 0) + return; + + /* handle per-version differences */ + if (ver <= 2) { + struct iwl_stored_beacon_notif_v2 *sb_v2 = (void *)pkt->data; + + if (pkt_len < struct_size(sb_v2, data, size)) + return; + + data = sb_v2->data; + } else { + struct iwl_stored_beacon_notif_v3 *sb_v3 = (void *)pkt->data; + + if (pkt_len < struct_size(sb_v3, data, size)) + return; + + data = sb_v3->data; + } + + skb = alloc_skb(size, GFP_ATOMIC); + if (!skb) { + IWL_ERR(mvm, "alloc_skb failed\n"); + return; + } + + /* update rx_status according to the notification's metadata */ + memset(&rx_status, 0, sizeof(rx_status)); + rx_status.mactime = le64_to_cpu(sb->tsf); + /* TSF as indicated by the firmware is at INA time */ + rx_status.flag |= RX_FLAG_MACTIME_PLCP_START; + rx_status.device_timestamp = le32_to_cpu(sb->system_time); + rx_status.band = + (sb->band & cpu_to_le16(RX_RES_PHY_FLAGS_BAND_24)) ? + NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + rx_status.freq = + ieee80211_channel_to_frequency(le16_to_cpu(sb->channel), + rx_status.band); + + /* copy the data */ + skb_put_data(skb, data, size); + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + + /* pass it as regular rx to mac80211 */ + ieee80211_rx_napi(mvm->hw, NULL, skb, NULL); +} + +void iwl_mvm_probe_resp_data_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_probe_resp_data_notif *notif = (void *)pkt->data; + struct iwl_probe_resp_data *old_data, *new_data; + u32 id = le32_to_cpu(notif->mac_id); + struct ieee80211_vif *vif; + struct iwl_mvm_vif *mvmvif; + + IWL_DEBUG_INFO(mvm, "Probe response data notif: noa %d, csa %d\n", + notif->noa_active, notif->csa_counter); + + vif = iwl_mvm_rcu_dereference_vif_id(mvm, id, false); + if (!vif) + return; + + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + new_data = kzalloc(sizeof(*new_data), GFP_KERNEL); + if (!new_data) + return; + + memcpy(&new_data->notif, notif, sizeof(new_data->notif)); + + /* noa_attr contains 1 reserved byte, need to substruct it */ + new_data->noa_len = sizeof(struct ieee80211_vendor_ie) + + sizeof(new_data->notif.noa_attr) - 1; + + /* + * If it's a one time NoA, only one descriptor is needed, + * adjust the length according to len_low. + */ + if (new_data->notif.noa_attr.len_low == + sizeof(struct ieee80211_p2p_noa_desc) + 2) + new_data->noa_len -= sizeof(struct ieee80211_p2p_noa_desc); + + old_data = rcu_dereference_protected(mvmvif->probe_resp_data, + lockdep_is_held(&mvmvif->mvm->mutex)); + rcu_assign_pointer(mvmvif->probe_resp_data, new_data); + + if (old_data) + kfree_rcu(old_data, rcu_head); + + if (notif->csa_counter != IWL_PROBE_RESP_DATA_NO_CSA && + notif->csa_counter >= 1) + ieee80211_beacon_set_cntdwn(vif, notif->csa_counter); +} + +void iwl_mvm_channel_switch_start_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_channel_switch_start_notif *notif = (void *)pkt->data; + struct ieee80211_vif *csa_vif, *vif; + struct iwl_mvm_vif *mvmvif; + u32 id_n_color, csa_id, mac_id; + + id_n_color = le32_to_cpu(notif->id_and_color); + mac_id = id_n_color & FW_CTXT_ID_MSK; + + if (WARN_ON_ONCE(mac_id >= NUM_MAC_INDEX_DRIVER)) + return; + + rcu_read_lock(); + vif = rcu_dereference(mvm->vif_id_to_mac[mac_id]); + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + switch (vif->type) { + case NL80211_IFTYPE_AP: + csa_vif = rcu_dereference(mvm->csa_vif); + if (WARN_ON(!csa_vif || !csa_vif->csa_active || + csa_vif != vif)) + goto out_unlock; + + csa_id = FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color); + if (WARN(csa_id != id_n_color, + "channel switch noa notification on unexpected vif (csa_vif=%d, notif=%d)", + csa_id, id_n_color)) + goto out_unlock; + + IWL_DEBUG_INFO(mvm, "Channel Switch Started Notification\n"); + + schedule_delayed_work(&mvm->cs_tx_unblock_dwork, + msecs_to_jiffies(IWL_MVM_CS_UNBLOCK_TX_TIMEOUT * + csa_vif->bss_conf.beacon_int)); + + ieee80211_csa_finish(csa_vif); + + rcu_read_unlock(); + + RCU_INIT_POINTER(mvm->csa_vif, NULL); + return; + case NL80211_IFTYPE_STATION: + iwl_mvm_csa_client_absent(mvm, vif); + cancel_delayed_work(&mvmvif->csa_work); + ieee80211_chswitch_done(vif, true); + break; + default: + /* should never happen */ + WARN_ON_ONCE(1); + break; + } +out_unlock: + rcu_read_unlock(); +} + +void iwl_mvm_rx_missed_vap_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_missed_vap_notif *mb = (void *)pkt->data; + struct ieee80211_vif *vif; + u32 id = le32_to_cpu(mb->mac_id); + + IWL_DEBUG_INFO(mvm, + "missed_vap notify mac_id=%u, num_beacon_intervals_elapsed=%u, profile_periodicity=%u\n", + le32_to_cpu(mb->mac_id), + mb->num_beacon_intervals_elapsed, + mb->profile_periodicity); + + rcu_read_lock(); + + vif = iwl_mvm_rcu_dereference_vif_id(mvm, id, true); + if (vif) + iwl_mvm_connection_loss(mvm, vif, "missed vap beacon"); + + rcu_read_unlock(); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/mac80211.c b/sys/contrib/dev/iwlwifi/mvm/mac80211.c new file mode 100644 index 00000000000..8aa92920798 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/mac80211.c @@ -0,0 +1,5474 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__FreeBSD__) +#include +#endif +#include +#include +#include +#if defined(__FreeBSD__) +#include +#endif + +#include "iwl-op-mode.h" +#include "iwl-io.h" +#include "mvm.h" +#include "sta.h" +#include "time-event.h" +#include "iwl-eeprom-parse.h" +#include "iwl-phy-db.h" +#ifdef CONFIG_NL80211_TESTMODE +#include "testmode.h" +#endif +#include "fw/error-dump.h" +#include "iwl-prph.h" +#include "iwl-nvm-parse.h" + +static const struct ieee80211_iface_limit iwl_mvm_limits[] = { + { + .max = 1, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_DEVICE), + }, +}; + +static const struct ieee80211_iface_combination iwl_mvm_iface_combinations[] = { + { + .num_different_channels = 2, + .max_interfaces = 3, + .limits = iwl_mvm_limits, + .n_limits = ARRAY_SIZE(iwl_mvm_limits), + }, +}; + +#ifdef CONFIG_IWLWIFI_BCAST_FILTERING +/* + * Use the reserved field to indicate magic values. + * these values will only be used internally by the driver, + * and won't make it to the fw (reserved will be 0). + * BC_FILTER_MAGIC_IP - configure the val of this attribute to + * be the vif's ip address. in case there is not a single + * ip address (0, or more than 1), this attribute will + * be skipped. + * BC_FILTER_MAGIC_MAC - set the val of this attribute to + * the LSB bytes of the vif's mac address + */ +enum { + BC_FILTER_MAGIC_NONE = 0, + BC_FILTER_MAGIC_IP, + BC_FILTER_MAGIC_MAC, +}; + +static const struct iwl_fw_bcast_filter iwl_mvm_default_bcast_filters[] = { + { + /* arp */ + .discard = 0, + .frame_type = BCAST_FILTER_FRAME_TYPE_ALL, + .attrs = { + { + /* frame type - arp, hw type - ethernet */ + .offset_type = + BCAST_FILTER_OFFSET_PAYLOAD_START, + .offset = sizeof(rfc1042_header), + .val = cpu_to_be32(0x08060001), + .mask = cpu_to_be32(0xffffffff), + }, + { + /* arp dest ip */ + .offset_type = + BCAST_FILTER_OFFSET_PAYLOAD_START, + .offset = sizeof(rfc1042_header) + 2 + + sizeof(struct arphdr) + + ETH_ALEN + sizeof(__be32) + + ETH_ALEN, + .mask = cpu_to_be32(0xffffffff), + /* mark it as special field */ + .reserved1 = cpu_to_le16(BC_FILTER_MAGIC_IP), + }, + }, + }, + { + /* dhcp offer bcast */ + .discard = 0, + .frame_type = BCAST_FILTER_FRAME_TYPE_IPV4, + .attrs = { + { + /* udp dest port - 68 (bootp client)*/ + .offset_type = BCAST_FILTER_OFFSET_IP_END, + .offset = offsetof(struct udphdr, dest), + .val = cpu_to_be32(0x00440000), + .mask = cpu_to_be32(0xffff0000), + }, + { + /* dhcp - lsb bytes of client hw address */ + .offset_type = BCAST_FILTER_OFFSET_IP_END, + .offset = 38, + .mask = cpu_to_be32(0xffffffff), + /* mark it as special field */ + .reserved1 = cpu_to_le16(BC_FILTER_MAGIC_MAC), + }, + }, + }, + /* last filter must be empty */ + {}, +}; +#endif + +static const struct cfg80211_pmsr_capabilities iwl_mvm_pmsr_capa = { + .max_peers = IWL_MVM_TOF_MAX_APS, + .report_ap_tsf = 1, + .randomize_mac_addr = 1, + + .ftm = { + .supported = 1, + .asap = 1, + .non_asap = 1, + .request_lci = 1, + .request_civicloc = 1, + .trigger_based = 1, + .non_trigger_based = 1, + .max_bursts_exponent = -1, /* all supported */ + .max_ftms_per_burst = 0, /* no limits */ + .bandwidths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80) | + BIT(NL80211_CHAN_WIDTH_160), + .preambles = BIT(NL80211_PREAMBLE_LEGACY) | + BIT(NL80211_PREAMBLE_HT) | + BIT(NL80211_PREAMBLE_VHT) | + BIT(NL80211_PREAMBLE_HE), + }, +}; + +static int __iwl_mvm_mac_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key); + +static void iwl_mvm_reset_phy_ctxts(struct iwl_mvm *mvm) +{ + int i; + + memset(mvm->phy_ctxts, 0, sizeof(mvm->phy_ctxts)); + for (i = 0; i < NUM_PHY_CTX; i++) { + mvm->phy_ctxts[i].id = i; + mvm->phy_ctxts[i].ref = 0; + } +} + +struct ieee80211_regdomain *iwl_mvm_get_regdomain(struct wiphy *wiphy, + const char *alpha2, + enum iwl_mcc_source src_id, + bool *changed) +{ + struct ieee80211_regdomain *regd = NULL; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mcc_update_resp *resp; + u8 resp_ver; + + IWL_DEBUG_LAR(mvm, "Getting regdomain data for %s from FW\n", alpha2); + + lockdep_assert_held(&mvm->mutex); + + resp = iwl_mvm_update_mcc(mvm, alpha2, src_id); + if (IS_ERR_OR_NULL(resp)) { + IWL_DEBUG_LAR(mvm, "Could not get update from FW %d\n", + PTR_ERR_OR_ZERO(resp)); + goto out; + } + + if (changed) { + u32 status = le32_to_cpu(resp->status); + + *changed = (status == MCC_RESP_NEW_CHAN_PROFILE || + status == MCC_RESP_ILLEGAL); + } + resp_ver = iwl_fw_lookup_notif_ver(mvm->fw, IWL_ALWAYS_LONG_GROUP, + MCC_UPDATE_CMD, 0); + IWL_DEBUG_LAR(mvm, "MCC update response version: %d\n", resp_ver); + + regd = iwl_parse_nvm_mcc_info(mvm->trans->dev, mvm->cfg, + __le32_to_cpu(resp->n_channels), + resp->channels, + __le16_to_cpu(resp->mcc), + __le16_to_cpu(resp->geo_info), + __le16_to_cpu(resp->cap), resp_ver); + /* Store the return source id */ + src_id = resp->source_id; + kfree(resp); + if (IS_ERR_OR_NULL(regd)) { + IWL_DEBUG_LAR(mvm, "Could not get parse update from FW %d\n", + PTR_ERR_OR_ZERO(regd)); + goto out; + } + + IWL_DEBUG_LAR(mvm, "setting alpha2 from FW to %s (0x%x, 0x%x) src=%d\n", + regd->alpha2, regd->alpha2[0], regd->alpha2[1], src_id); + mvm->lar_regdom_set = true; + mvm->mcc_src = src_id; + +out: + return regd; +} + +void iwl_mvm_update_changed_regdom(struct iwl_mvm *mvm) +{ + bool changed; + struct ieee80211_regdomain *regd; + + if (!iwl_mvm_is_lar_supported(mvm)) + return; + + regd = iwl_mvm_get_current_regdomain(mvm, &changed); + if (!IS_ERR_OR_NULL(regd)) { + /* only update the regulatory core if changed */ + if (changed) + regulatory_set_wiphy_regd(mvm->hw->wiphy, regd); + + kfree(regd); + } +} + +struct ieee80211_regdomain *iwl_mvm_get_current_regdomain(struct iwl_mvm *mvm, + bool *changed) +{ + return iwl_mvm_get_regdomain(mvm->hw->wiphy, "ZZ", + iwl_mvm_is_wifi_mcc_supported(mvm) ? + MCC_SOURCE_GET_CURRENT : + MCC_SOURCE_OLD_FW, changed); +} + +int iwl_mvm_init_fw_regd(struct iwl_mvm *mvm) +{ + enum iwl_mcc_source used_src; + struct ieee80211_regdomain *regd; + int ret; + bool changed; + const struct ieee80211_regdomain *r = + wiphy_dereference(mvm->hw->wiphy, mvm->hw->wiphy->regd); + + if (!r) + return -ENOENT; + + /* save the last source in case we overwrite it below */ + used_src = mvm->mcc_src; + if (iwl_mvm_is_wifi_mcc_supported(mvm)) { + /* Notify the firmware we support wifi location updates */ + regd = iwl_mvm_get_current_regdomain(mvm, NULL); + if (!IS_ERR_OR_NULL(regd)) + kfree(regd); + } + + /* Now set our last stored MCC and source */ + regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, r->alpha2, used_src, + &changed); + if (IS_ERR_OR_NULL(regd)) + return -EIO; + + /* update cfg80211 if the regdomain was changed */ + if (changed) + ret = regulatory_set_wiphy_regd_sync(mvm->hw->wiphy, regd); + else + ret = 0; + + kfree(regd); + return ret; +} + +static const u8 he_if_types_ext_capa_sta[] = { + [0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING, + [2] = WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT, + [7] = WLAN_EXT_CAPA8_OPMODE_NOTIF, + [9] = WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT, +}; + +static const struct wiphy_iftype_ext_capab he_iftypes_ext_capa[] = { + { + .iftype = NL80211_IFTYPE_STATION, + .extended_capabilities = he_if_types_ext_capa_sta, + .extended_capabilities_mask = he_if_types_ext_capa_sta, + .extended_capabilities_len = sizeof(he_if_types_ext_capa_sta), + }, +}; + +static int +iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + *tx_ant = iwl_mvm_get_valid_tx_ant(mvm); + *rx_ant = iwl_mvm_get_valid_rx_ant(mvm); + return 0; +} + +int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm) +{ + struct ieee80211_hw *hw = mvm->hw; + int num_mac, ret, i; + static const u32 mvm_ciphers[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + }; +#ifdef CONFIG_PM_SLEEP + bool unified = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); +#endif + + /* Tell mac80211 our characteristics */ + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, SPECTRUM_MGMT); + ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); + ieee80211_hw_set(hw, WANT_MONITOR_VIF); + ieee80211_hw_set(hw, SUPPORTS_PS); + ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, TIMING_BEACON_ONLY); + ieee80211_hw_set(hw, CONNECTION_MONITOR); + ieee80211_hw_set(hw, CHANCTX_STA_CSA); + ieee80211_hw_set(hw, SUPPORT_FAST_XMIT); + ieee80211_hw_set(hw, SUPPORTS_CLONED_SKBS); + ieee80211_hw_set(hw, SUPPORTS_AMSDU_IN_AMPDU); + ieee80211_hw_set(hw, NEEDS_UNIQUE_STA_ADDR); + ieee80211_hw_set(hw, DEAUTH_NEED_MGD_TX_PREP); + ieee80211_hw_set(hw, SUPPORTS_VHT_EXT_NSS_BW); + ieee80211_hw_set(hw, BUFF_MMPDU_TXQ); + ieee80211_hw_set(hw, STA_MMPDU_TXQ); + /* + * On older devices, enabling TX A-MSDU occasionally leads to + * something getting messed up, the command read from the FIFO + * gets out of sync and isn't a TX command, so that we have an + * assert EDC. + * + * It's not clear where the bug is, but since we didn't used to + * support A-MSDU until moving the mac80211 iTXQs, just leave it + * for older devices. We also don't see this issue on any newer + * devices. + */ + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_9000) + ieee80211_hw_set(hw, TX_AMSDU); + ieee80211_hw_set(hw, TX_FRAG_LIST); + + if (iwl_mvm_has_tlc_offload(mvm)) { + ieee80211_hw_set(hw, TX_AMPDU_SETUP_IN_HW); + ieee80211_hw_set(hw, HAS_RATE_CONTROL); + } + + if (iwl_mvm_has_new_rx_api(mvm)) + ieee80211_hw_set(hw, SUPPORTS_REORDERING_BUFFER); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_STA_PM_NOTIF)) { + ieee80211_hw_set(hw, AP_LINK_PS); + } else if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) { + /* + * we absolutely need this for the new TX API since that comes + * with many more queues than the current code can deal with + * for station powersave + */ + return -EINVAL; + } + + if (mvm->trans->num_rx_queues > 1) + ieee80211_hw_set(hw, USES_RSS); + + if (mvm->trans->max_skb_frags) + hw->netdev_features = NETIF_F_HIGHDMA | NETIF_F_SG; + + hw->queues = IEEE80211_NUM_ACS; + hw->offchannel_tx_hw_queue = IWL_MVM_OFFCHANNEL_QUEUE; + hw->radiotap_mcs_details |= IEEE80211_RADIOTAP_MCS_HAVE_FEC | + IEEE80211_RADIOTAP_MCS_HAVE_STBC; + hw->radiotap_vht_details |= IEEE80211_RADIOTAP_VHT_KNOWN_STBC | + IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED; + + hw->radiotap_timestamp.units_pos = + IEEE80211_RADIOTAP_TIMESTAMP_UNIT_US | + IEEE80211_RADIOTAP_TIMESTAMP_SPOS_PLCP_SIG_ACQ; + /* this is the case for CCK frames, it's better (only 8) for OFDM */ + hw->radiotap_timestamp.accuracy = 22; + + if (!iwl_mvm_has_tlc_offload(mvm)) + hw->rate_control_algorithm = RS_NAME; + + hw->uapsd_queues = IWL_MVM_UAPSD_QUEUES; + hw->uapsd_max_sp_len = IWL_UAPSD_MAX_SP; + hw->max_tx_fragments = mvm->trans->max_skb_frags; + + BUILD_BUG_ON(ARRAY_SIZE(mvm->ciphers) < ARRAY_SIZE(mvm_ciphers) + 6); + memcpy(mvm->ciphers, mvm_ciphers, sizeof(mvm_ciphers)); + hw->wiphy->n_cipher_suites = ARRAY_SIZE(mvm_ciphers); + hw->wiphy->cipher_suites = mvm->ciphers; + + if (iwl_mvm_has_new_rx_api(mvm)) { + mvm->ciphers[hw->wiphy->n_cipher_suites] = + WLAN_CIPHER_SUITE_GCMP; + hw->wiphy->n_cipher_suites++; + mvm->ciphers[hw->wiphy->n_cipher_suites] = + WLAN_CIPHER_SUITE_GCMP_256; + hw->wiphy->n_cipher_suites++; + } + + if (iwlwifi_mod_params.swcrypto) + IWL_ERR(mvm, + "iwlmvm doesn't allow to disable HW crypto, check swcrypto module parameter\n"); + if (!iwlwifi_mod_params.bt_coex_active) + IWL_ERR(mvm, + "iwlmvm doesn't allow to disable BT Coex, check bt_coex_active module parameter\n"); + + ieee80211_hw_set(hw, MFP_CAPABLE); + mvm->ciphers[hw->wiphy->n_cipher_suites] = WLAN_CIPHER_SUITE_AES_CMAC; + hw->wiphy->n_cipher_suites++; + if (iwl_mvm_has_new_rx_api(mvm)) { + mvm->ciphers[hw->wiphy->n_cipher_suites] = + WLAN_CIPHER_SUITE_BIP_GMAC_128; + hw->wiphy->n_cipher_suites++; + mvm->ciphers[hw->wiphy->n_cipher_suites] = + WLAN_CIPHER_SUITE_BIP_GMAC_256; + hw->wiphy->n_cipher_suites++; + } + + /* currently FW API supports only one optional cipher scheme */ + if (mvm->fw->cs[0].cipher) { + const struct iwl_fw_cipher_scheme *fwcs = &mvm->fw->cs[0]; + struct ieee80211_cipher_scheme *cs = &mvm->cs[0]; + + mvm->hw->n_cipher_schemes = 1; + + cs->cipher = le32_to_cpu(fwcs->cipher); + cs->iftype = BIT(NL80211_IFTYPE_STATION); + cs->hdr_len = fwcs->hdr_len; + cs->pn_len = fwcs->pn_len; + cs->pn_off = fwcs->pn_off; + cs->key_idx_off = fwcs->key_idx_off; + cs->key_idx_mask = fwcs->key_idx_mask; + cs->key_idx_shift = fwcs->key_idx_shift; + cs->mic_len = fwcs->mic_len; + + mvm->hw->cipher_schemes = mvm->cs; + mvm->ciphers[hw->wiphy->n_cipher_suites] = cs->cipher; + hw->wiphy->n_cipher_suites++; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_FTM_CALIBRATED)) { + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER); + hw->wiphy->pmsr_capa = &iwl_mvm_pmsr_capa; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BIGTK_SUPPORT)) + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT); + + ieee80211_hw_set(hw, SINGLE_SCAN_ON_ALL_BANDS); + hw->wiphy->features |= + NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_ND_RANDOM_MAC_ADDR; + + hw->sta_data_size = sizeof(struct iwl_mvm_sta); + hw->vif_data_size = sizeof(struct iwl_mvm_vif); + hw->chanctx_data_size = sizeof(u16); + hw->txq_data_size = sizeof(struct iwl_mvm_txq); + + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_DEVICE) | + BIT(NL80211_IFTYPE_ADHOC); + + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_VHT_IBSS); + + /* The new Tx API does not allow to pass the key or keyid of a MPDU to + * the hw, preventing us to control which key(id) to use per MPDU. + * Till that's fixed we can't use Extended Key ID for the newer cards. + */ + if (!iwl_mvm_has_new_tx_api(mvm)) + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_EXT_KEY_ID); + hw->wiphy->features |= NL80211_FEATURE_HT_IBSS; + + hw->wiphy->regulatory_flags |= REGULATORY_ENABLE_RELAX_NO_IR; + if (iwl_mvm_is_lar_supported(mvm)) + hw->wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED; + else + hw->wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG | + REGULATORY_DISABLE_BEACON_HINTS; + + hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; + hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH; + hw->wiphy->flags |= WIPHY_FLAG_SPLIT_SCAN_6GHZ; + + hw->wiphy->iface_combinations = iwl_mvm_iface_combinations; + hw->wiphy->n_iface_combinations = + ARRAY_SIZE(iwl_mvm_iface_combinations); + + hw->wiphy->max_remain_on_channel_duration = 10000; + hw->max_listen_interval = IWL_CONN_MAX_LISTEN_INTERVAL; + + /* Extract MAC address */ + memcpy(mvm->addresses[0].addr, mvm->nvm_data->hw_addr, ETH_ALEN); + hw->wiphy->addresses = mvm->addresses; + hw->wiphy->n_addresses = 1; + + /* Extract additional MAC addresses if available */ + num_mac = (mvm->nvm_data->n_hw_addrs > 1) ? + min(IWL_MVM_MAX_ADDRESSES, mvm->nvm_data->n_hw_addrs) : 1; + + for (i = 1; i < num_mac; i++) { + memcpy(mvm->addresses[i].addr, mvm->addresses[i-1].addr, + ETH_ALEN); + mvm->addresses[i].addr[5]++; + hw->wiphy->n_addresses++; + } + + iwl_mvm_reset_phy_ctxts(mvm); + + hw->wiphy->max_scan_ie_len = iwl_mvm_max_scan_ie_len(mvm); + + hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX; + + BUILD_BUG_ON(IWL_MVM_SCAN_STOPPING_MASK & IWL_MVM_SCAN_MASK); + BUILD_BUG_ON(IWL_MVM_MAX_UMAC_SCANS > HWEIGHT32(IWL_MVM_SCAN_MASK) || + IWL_MVM_MAX_LMAC_SCANS > HWEIGHT32(IWL_MVM_SCAN_MASK)); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) + mvm->max_scans = IWL_MVM_MAX_UMAC_SCANS; + else + mvm->max_scans = IWL_MVM_MAX_LMAC_SCANS; + + if (mvm->nvm_data->bands[NL80211_BAND_2GHZ].n_channels) + hw->wiphy->bands[NL80211_BAND_2GHZ] = + &mvm->nvm_data->bands[NL80211_BAND_2GHZ]; + if (mvm->nvm_data->bands[NL80211_BAND_5GHZ].n_channels) { + hw->wiphy->bands[NL80211_BAND_5GHZ] = + &mvm->nvm_data->bands[NL80211_BAND_5GHZ]; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BEAMFORMER) && + fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_LQ_SS_PARAMS)) + hw->wiphy->bands[NL80211_BAND_5GHZ]->vht_cap.cap |= + IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; + } + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_PSC_CHAN_SUPPORT) && + mvm->nvm_data->bands[NL80211_BAND_6GHZ].n_channels) + hw->wiphy->bands[NL80211_BAND_6GHZ] = + &mvm->nvm_data->bands[NL80211_BAND_6GHZ]; + + hw->wiphy->hw_version = mvm->trans->hw_id; + + if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) + hw->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + else + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + hw->wiphy->max_sched_scan_reqs = 1; + hw->wiphy->max_sched_scan_ssids = PROBE_OPTION_MAX; + hw->wiphy->max_match_sets = iwl_umac_scan_get_max_profiles(mvm->fw); + /* we create the 802.11 header and zero length SSID IE. */ + hw->wiphy->max_sched_scan_ie_len = + SCAN_OFFLOAD_PROBE_REQ_SIZE - 24 - 2; + hw->wiphy->max_sched_scan_plans = IWL_MAX_SCHED_SCAN_PLANS; + hw->wiphy->max_sched_scan_plan_interval = U16_MAX; + + /* + * the firmware uses u8 for num of iterations, but 0xff is saved for + * infinite loop, so the maximum number of iterations is actually 254. + */ + hw->wiphy->max_sched_scan_plan_iterations = 254; + + hw->wiphy->features |= NL80211_FEATURE_P2P_GO_CTWIN | + NL80211_FEATURE_LOW_PRIORITY_SCAN | + NL80211_FEATURE_P2P_GO_OPPPS | + NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | + NL80211_FEATURE_DYNAMIC_SMPS | + NL80211_FEATURE_STATIC_SMPS | + NL80211_FEATURE_SUPPORTS_WMM_ADMISSION; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT)) + hw->wiphy->features |= NL80211_FEATURE_TX_POWER_INSERTION; + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT)) + hw->wiphy->features |= NL80211_FEATURE_QUIET; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT)) + hw->wiphy->features |= + NL80211_FEATURE_DS_PARAM_SET_IE_IN_PROBES; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT)) + hw->wiphy->features |= NL80211_FEATURE_WFA_TPC_IE_IN_PROBES; + + if (iwl_fw_lookup_cmd_ver(mvm->fw, IWL_ALWAYS_LONG_GROUP, + WOWLAN_KEK_KCK_MATERIAL, + IWL_FW_CMD_VER_UNKNOWN) == 3) + hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_EXT_KEK_KCK; + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_SCAN_TSF_REPORT)) { + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_SCAN_START_TIME); + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_BSS_PARENT_TSF); + } + + if (iwl_mvm_is_oce_supported(mvm)) { + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_ACCEPT_BCAST_PROBE_RESP); + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_FILS_MAX_CHANNEL_TIME); + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_OCE_PROBE_REQ_DEFERRAL_SUPPRESSION); + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_OCE_PROBE_REQ_HIGH_TX_RATE); + } + + if (mvm->nvm_data->sku_cap_11ax_enable && + !iwlwifi_mod_params.disable_11ax) { + hw->wiphy->iftype_ext_capab = he_iftypes_ext_capa; + hw->wiphy->num_iftype_ext_capab = + ARRAY_SIZE(he_iftypes_ext_capa); + + ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); + ieee80211_hw_set(hw, SUPPORTS_ONLY_HE_MULTI_BSSID); + } + + mvm->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; + +#ifdef CONFIG_PM_SLEEP + if ((unified || mvm->fw->img[IWL_UCODE_WOWLAN].num_sec) && + mvm->trans->ops->d3_suspend && + mvm->trans->ops->d3_resume && + device_can_wakeup(mvm->trans->dev)) { + mvm->wowlan.flags |= WIPHY_WOWLAN_MAGIC_PKT | + WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_EAP_IDENTITY_REQ | + WIPHY_WOWLAN_RFKILL_RELEASE | + WIPHY_WOWLAN_NET_DETECT; + mvm->wowlan.flags |= WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_GTK_REKEY_FAILURE | + WIPHY_WOWLAN_4WAY_HANDSHAKE; + + mvm->wowlan.n_patterns = IWL_WOWLAN_MAX_PATTERNS; + mvm->wowlan.pattern_min_len = IWL_WOWLAN_MIN_PATTERN_LEN; + mvm->wowlan.pattern_max_len = IWL_WOWLAN_MAX_PATTERN_LEN; + mvm->wowlan.max_nd_match_sets = + iwl_umac_scan_get_max_profiles(mvm->fw); + hw->wiphy->wowlan = &mvm->wowlan; + } +#endif + +#ifdef CONFIG_IWLWIFI_BCAST_FILTERING + /* assign default bcast filtering configuration */ + mvm->bcast_filters = iwl_mvm_default_bcast_filters; +#endif + + ret = iwl_mvm_leds_init(mvm); + if (ret) + return ret; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TDLS_SUPPORT)) { + IWL_DEBUG_TDLS(mvm, "TDLS supported\n"); + hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; + ieee80211_hw_set(hw, TDLS_WIDER_BW); + } + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH)) { + IWL_DEBUG_TDLS(mvm, "TDLS channel switch supported\n"); + hw->wiphy->features |= NL80211_FEATURE_TDLS_CHANNEL_SWITCH; + } + + hw->netdev_features |= mvm->cfg->features; + if (!iwl_mvm_is_csum_supported(mvm)) + hw->netdev_features &= ~(IWL_TX_CSUM_NETIF_FLAGS | + NETIF_F_RXCSUM); + + if (mvm->cfg->vht_mu_mimo_supported) + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_PROTECTED_TWT)) + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_PROTECTED_TWT); + + hw->wiphy->available_antennas_tx = iwl_mvm_get_valid_tx_ant(mvm); + hw->wiphy->available_antennas_rx = iwl_mvm_get_valid_rx_ant(mvm); + + ret = ieee80211_register_hw(mvm->hw); + if (ret) { + iwl_mvm_leds_exit(mvm); + } + + return ret; +} + +static void iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_sta *sta) +{ + if (likely(sta)) { + if (likely(iwl_mvm_tx_skb_sta(mvm, skb, sta) == 0)) + return; + } else { + if (likely(iwl_mvm_tx_skb_non_sta(mvm, skb) == 0)) + return; + } + + ieee80211_free_txskb(mvm->hw, skb); +} + +static void iwl_mvm_mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct ieee80211_sta *sta = control->sta; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (void *)skb->data; + bool offchannel = IEEE80211_SKB_CB(skb)->flags & + IEEE80211_TX_CTL_TX_OFFCHAN; + + if (iwl_mvm_is_radio_killed(mvm)) { + IWL_DEBUG_DROP(mvm, "Dropping - RF/CT KILL\n"); + goto drop; + } + + if (offchannel && + !test_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status) && + !test_bit(IWL_MVM_STATUS_ROC_AUX_RUNNING, &mvm->status)) + goto drop; + + /* + * bufferable MMPDUs or MMPDUs on STA interfaces come via TXQs + * so we treat the others as broadcast + */ + if (ieee80211_is_mgmt(hdr->frame_control)) + sta = NULL; + + /* If there is no sta, and it's not offchannel - send through AP */ + if (!sta && info->control.vif->type == NL80211_IFTYPE_STATION && + !offchannel) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(info->control.vif); + u8 ap_sta_id = READ_ONCE(mvmvif->ap_sta_id); + + if (ap_sta_id < mvm->fw->ucode_capa.num_stations) { + /* mac80211 holds rcu read lock */ + sta = rcu_dereference(mvm->fw_id_to_mac_id[ap_sta_id]); + if (IS_ERR_OR_NULL(sta)) + goto drop; + } + } + + iwl_mvm_tx_skb(mvm, skb, sta); + return; + drop: + ieee80211_free_txskb(hw, skb); +} + +void iwl_mvm_mac_itxq_xmit(struct ieee80211_hw *hw, struct ieee80211_txq *txq) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_txq *mvmtxq = iwl_mvm_txq_from_mac80211(txq); + struct sk_buff *skb = NULL; + + /* + * No need for threads to be pending here, they can leave the first + * taker all the work. + * + * mvmtxq->tx_request logic: + * + * If 0, no one is currently TXing, set to 1 to indicate current thread + * will now start TX and other threads should quit. + * + * If 1, another thread is currently TXing, set to 2 to indicate to + * that thread that there was another request. Since that request may + * have raced with the check whether the queue is empty, the TXing + * thread should check the queue's status one more time before leaving. + * This check is done in order to not leave any TX hanging in the queue + * until the next TX invocation (which may not even happen). + * + * If 2, another thread is currently TXing, and it will already double + * check the queue, so do nothing. + */ + if (atomic_fetch_add_unless(&mvmtxq->tx_request, 1, 2)) + return; + + rcu_read_lock(); + do { + while (likely(!mvmtxq->stopped && + !test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status))) { + skb = ieee80211_tx_dequeue(hw, txq); + + if (!skb) { + if (txq->sta) + IWL_DEBUG_TX(mvm, + "TXQ of sta %pM tid %d is now empty\n", + txq->sta->addr, + txq->tid); + break; + } + + iwl_mvm_tx_skb(mvm, skb, txq->sta); + } + } while (atomic_dec_return(&mvmtxq->tx_request)); + rcu_read_unlock(); +} + +static void iwl_mvm_mac_wake_tx_queue(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_txq *mvmtxq = iwl_mvm_txq_from_mac80211(txq); + + /* + * Please note that racing is handled very carefully here: + * mvmtxq->txq_id is updated during allocation, and mvmtxq->list is + * deleted afterwards. + * This means that if: + * mvmtxq->txq_id != INVALID_QUEUE && list_empty(&mvmtxq->list): + * queue is allocated and we can TX. + * mvmtxq->txq_id != INVALID_QUEUE && !list_empty(&mvmtxq->list): + * a race, should defer the frame. + * mvmtxq->txq_id == INVALID_QUEUE && list_empty(&mvmtxq->list): + * need to allocate the queue and defer the frame. + * mvmtxq->txq_id == INVALID_QUEUE && !list_empty(&mvmtxq->list): + * queue is already scheduled for allocation, no need to allocate, + * should defer the frame. + */ + + /* If the queue is allocated TX and return. */ + if (!txq->sta || mvmtxq->txq_id != IWL_MVM_INVALID_QUEUE) { + /* + * Check that list is empty to avoid a race where txq_id is + * already updated, but the queue allocation work wasn't + * finished + */ + if (unlikely(txq->sta && !list_empty(&mvmtxq->list))) + return; + + iwl_mvm_mac_itxq_xmit(hw, txq); + return; + } + + /* The list is being deleted only after the queue is fully allocated. */ + if (!list_empty(&mvmtxq->list)) + return; + + list_add_tail(&mvmtxq->list, &mvm->add_stream_txqs); + schedule_work(&mvm->add_stream_wk); +} + +#define CHECK_BA_TRIGGER(_mvm, _trig, _tid_bm, _tid, _fmt...) \ + do { \ + if (!(le16_to_cpu(_tid_bm) & BIT(_tid))) \ + break; \ + iwl_fw_dbg_collect_trig(&(_mvm)->fwrt, _trig, _fmt); \ + } while (0) + +static void +iwl_mvm_ampdu_check_trigger(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 rx_ba_ssn, + enum ieee80211_ampdu_mlme_action action) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_ba *ba_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_BA); + if (!trig) + return; + + ba_trig = (void *)trig->data; + + switch (action) { + case IEEE80211_AMPDU_TX_OPERATIONAL: { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + + CHECK_BA_TRIGGER(mvm, trig, ba_trig->tx_ba_start, tid, + "TX AGG START: MAC %pM tid %d ssn %d\n", + sta->addr, tid, tid_data->ssn); + break; + } + case IEEE80211_AMPDU_TX_STOP_CONT: + CHECK_BA_TRIGGER(mvm, trig, ba_trig->tx_ba_stop, tid, + "TX AGG STOP: MAC %pM tid %d\n", + sta->addr, tid); + break; + case IEEE80211_AMPDU_RX_START: + CHECK_BA_TRIGGER(mvm, trig, ba_trig->rx_ba_start, tid, + "RX AGG START: MAC %pM tid %d ssn %d\n", + sta->addr, tid, rx_ba_ssn); + break; + case IEEE80211_AMPDU_RX_STOP: + CHECK_BA_TRIGGER(mvm, trig, ba_trig->rx_ba_stop, tid, + "RX AGG STOP: MAC %pM tid %d\n", + sta->addr, tid); + break; + default: + break; + } +} + +static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_ampdu_params *params) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + struct ieee80211_sta *sta = params->sta; + enum ieee80211_ampdu_mlme_action action = params->action; + u16 tid = params->tid; + u16 *ssn = ¶ms->ssn; + u16 buf_size = params->buf_size; + bool amsdu = params->amsdu; + u16 timeout = params->timeout; + + IWL_DEBUG_HT(mvm, "A-MPDU action on addr %pM tid %d: action %d\n", + sta->addr, tid, action); + + if (!(mvm->nvm_data->sku_cap_11n_enable)) + return -EACCES; + + mutex_lock(&mvm->mutex); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + if (iwl_mvm_vif_from_mac80211(vif)->ap_sta_id == + iwl_mvm_sta_from_mac80211(sta)->sta_id) { + struct iwl_mvm_vif *mvmvif; + u16 macid = iwl_mvm_vif_from_mac80211(vif)->id; + struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[macid]; + + mdata->opened_rx_ba_sessions = true; + mvmvif = iwl_mvm_vif_from_mac80211(vif); + cancel_delayed_work(&mvmvif->uapsd_nonagg_detected_wk); + } + if (!iwl_enable_rx_ampdu()) { + ret = -EINVAL; + break; + } + ret = iwl_mvm_sta_rx_agg(mvm, sta, tid, *ssn, true, buf_size, + timeout); + break; + case IEEE80211_AMPDU_RX_STOP: + ret = iwl_mvm_sta_rx_agg(mvm, sta, tid, 0, false, buf_size, + timeout); + break; + case IEEE80211_AMPDU_TX_START: + if (!iwl_enable_tx_ampdu()) { + ret = -EINVAL; + break; + } + ret = iwl_mvm_sta_tx_agg_start(mvm, vif, sta, tid, ssn); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + ret = iwl_mvm_sta_tx_agg_stop(mvm, vif, sta, tid); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + ret = iwl_mvm_sta_tx_agg_flush(mvm, vif, sta, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ret = iwl_mvm_sta_tx_agg_oper(mvm, vif, sta, tid, + buf_size, amsdu); + break; + default: + WARN_ON_ONCE(1); + ret = -EINVAL; + break; + } + + if (!ret) { + u16 rx_ba_ssn = 0; + + if (action == IEEE80211_AMPDU_RX_START) + rx_ba_ssn = *ssn; + + iwl_mvm_ampdu_check_trigger(mvm, vif, sta, tid, + rx_ba_ssn, action); + } + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_cleanup_iterator(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->uploaded = false; + mvmvif->ap_sta_id = IWL_MVM_INVALID_STA; + + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, &mvmvif->time_event_data); + spin_unlock_bh(&mvm->time_event_lock); + + mvmvif->phy_ctxt = NULL; + memset(&mvmvif->bf_data, 0, sizeof(mvmvif->bf_data)); + memset(&mvmvif->probe_resp_data, 0, sizeof(mvmvif->probe_resp_data)); +} + +static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) +{ + iwl_mvm_stop_device(mvm); + + mvm->cur_aid = 0; + + mvm->scan_status = 0; + mvm->ps_disabled = false; + mvm->rfkill_safe_init_done = false; + + /* just in case one was running */ + iwl_mvm_cleanup_roc_te(mvm); + ieee80211_remain_on_channel_expired(mvm->hw); + + iwl_mvm_ftm_restart(mvm); + + /* + * cleanup all interfaces, even inactive ones, as some might have + * gone down during the HW restart + */ + ieee80211_iterate_interfaces(mvm->hw, 0, iwl_mvm_cleanup_iterator, mvm); + + mvm->p2p_device_vif = NULL; + + iwl_mvm_reset_phy_ctxts(mvm); + memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table)); + memset(&mvm->last_bt_notif, 0, sizeof(mvm->last_bt_notif)); + memset(&mvm->last_bt_ci_cmd, 0, sizeof(mvm->last_bt_ci_cmd)); + + ieee80211_wake_queues(mvm->hw); + + mvm->vif_count = 0; + mvm->rx_ba_sessions = 0; + mvm->fwrt.dump.conf = FW_DBG_INVALID; + mvm->monitor_on = false; + + /* keep statistics ticking */ + iwl_mvm_accu_radio_stats(mvm); +} + +int __iwl_mvm_mac_start(struct iwl_mvm *mvm) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status)) { + /* + * Now convert the HW_RESTART_REQUESTED flag to IN_HW_RESTART + * so later code will - from now on - see that we're doing it. + */ + set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + clear_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status); + /* Clean up some internal and mac80211 state on restart */ + iwl_mvm_restart_cleanup(mvm); + } + ret = iwl_mvm_up(mvm); + + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_POST_INIT, + NULL); + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_PERIODIC, + NULL); + + mvm->last_reset_or_resume_time_jiffies = jiffies; + + if (ret && test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + /* Something went wrong - we need to finish some cleanup + * that normally iwl_mvm_mac_restart_complete() below + * would do. + */ + clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + } + + return ret; +} + +static int iwl_mvm_mac_start(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + ret = __iwl_mvm_mac_start(mvm); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_restart_complete(struct iwl_mvm *mvm) +{ + int ret; + + mutex_lock(&mvm->mutex); + + clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + + ret = iwl_mvm_update_quotas(mvm, true, NULL); + if (ret) + IWL_ERR(mvm, "Failed to update quotas after restart (%d)\n", + ret); + + iwl_mvm_send_recovery_cmd(mvm, ERROR_RECOVERY_END_OF_RECOVERY); + + /* + * If we have TDLS peers, remove them. We don't know the last seqno/PN + * of packets the FW sent out, so we must reconnect. + */ + iwl_mvm_teardown_tdls_peers(mvm); + + mutex_unlock(&mvm->mutex); +} + +static void +iwl_mvm_mac_reconfig_complete(struct ieee80211_hw *hw, + enum ieee80211_reconfig_type reconfig_type) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + switch (reconfig_type) { + case IEEE80211_RECONFIG_TYPE_RESTART: + iwl_mvm_restart_complete(mvm); + break; + case IEEE80211_RECONFIG_TYPE_SUSPEND: + break; + } +} + +void __iwl_mvm_mac_stop(struct iwl_mvm *mvm) +{ + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_ftm_initiator_smooth_stop(mvm); + + /* firmware counters are obviously reset now, but we shouldn't + * partially track so also clear the fw_reset_accu counters. + */ + memset(&mvm->accu_radio_stats, 0, sizeof(mvm->accu_radio_stats)); + + /* async_handlers_wk is now blocked */ + + if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, ADD_STA, 0) < 12) + iwl_mvm_rm_aux_sta(mvm); + + iwl_mvm_stop_device(mvm); + + iwl_mvm_async_handlers_purge(mvm); + /* async_handlers_list is empty and will stay empty: HW is stopped */ + + /* + * Clear IN_HW_RESTART and HW_RESTART_REQUESTED flag when stopping the + * hw (as restart_complete() won't be called in this case) and mac80211 + * won't execute the restart. + * But make sure to cleanup interfaces that have gone down before/during + * HW restart was requested. + */ + if (test_and_clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) || + test_and_clear_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status)) + ieee80211_iterate_interfaces(mvm->hw, 0, + iwl_mvm_cleanup_iterator, mvm); + + /* We shouldn't have any UIDs still set. Loop over all the UIDs to + * make sure there's nothing left there and warn if any is found. + */ + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + int i; + + for (i = 0; i < mvm->max_scans; i++) { + if (WARN_ONCE(mvm->scan_uid_status[i], + "UMAC scan UID %d status was not cleaned\n", + i)) + mvm->scan_uid_status[i] = 0; + } + } +} + +static void iwl_mvm_mac_stop(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + flush_work(&mvm->async_handlers_wk); + flush_work(&mvm->add_stream_wk); + + /* + * Lock and clear the firmware running bit here already, so that + * new commands coming in elsewhere, e.g. from debugfs, will not + * be able to proceed. This is important here because one of those + * debugfs files causes the firmware dump to be triggered, and if we + * don't stop debugfs accesses before canceling that it could be + * retriggered after we flush it but before we've cleared the bit. + */ + clear_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); + + cancel_delayed_work_sync(&mvm->cs_tx_unblock_dwork); + cancel_delayed_work_sync(&mvm->scan_timeout_dwork); + + /* + * The work item could be running or queued if the + * ROC time event stops just as we get here. + */ + flush_work(&mvm->roc_done_wk); + + mutex_lock(&mvm->mutex); + __iwl_mvm_mac_stop(mvm); + mutex_unlock(&mvm->mutex); + + /* + * The worker might have been waiting for the mutex, let it run and + * discover that its list is now empty. + */ + cancel_work_sync(&mvm->async_handlers_wk); +} + +static struct iwl_mvm_phy_ctxt *iwl_mvm_get_free_phy_ctxt(struct iwl_mvm *mvm) +{ + u16 i; + + lockdep_assert_held(&mvm->mutex); + + for (i = 0; i < NUM_PHY_CTX; i++) + if (!mvm->phy_ctxts[i].ref) + return &mvm->phy_ctxts[i]; + + IWL_ERR(mvm, "No available PHY context\n"); + return NULL; +} + +static int iwl_mvm_set_tx_power(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + s16 tx_power) +{ + int len; + struct iwl_dev_tx_power_cmd cmd = { + .common.set_mode = cpu_to_le32(IWL_TX_POWER_MODE_SET_MAC), + .common.mac_context_id = + cpu_to_le32(iwl_mvm_vif_from_mac80211(vif)->id), + .common.pwr_restriction = cpu_to_le16(8 * tx_power), + }; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + REDUCE_TX_POWER_CMD, + IWL_FW_CMD_VER_UNKNOWN); + + if (tx_power == IWL_DEFAULT_MAX_TX_POWER) + cmd.common.pwr_restriction = cpu_to_le16(IWL_DEV_MAX_TX_POWER); + + if (cmd_ver == 6) + len = sizeof(cmd.v6); + else if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_REDUCE_TX_POWER)) + len = sizeof(cmd.v5); + else if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TX_POWER_ACK)) + len = sizeof(cmd.v4); + else + len = sizeof(cmd.v3); + + /* all structs have the same common part, add it */ + len += sizeof(cmd.common); + + return iwl_mvm_send_cmd_pdu(mvm, REDUCE_TX_POWER_CMD, 0, len, &cmd); +} + +static int iwl_mvm_post_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + + if (vif->type == NL80211_IFTYPE_STATION) { + struct iwl_mvm_sta *mvmsta; + + mvmvif->csa_bcn_pending = false; + mvmsta = iwl_mvm_sta_from_staid_protected(mvm, + mvmvif->ap_sta_id); + + if (WARN_ON(!mvmsta)) { + ret = -EIO; + goto out_unlock; + } + + iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, false); + + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) { + ret = iwl_mvm_enable_beacon_filter(mvm, vif, 0); + if (ret) + goto out_unlock; + + iwl_mvm_stop_session_protection(mvm, vif); + } + } + + mvmvif->ps_disabled = false; + + ret = iwl_mvm_power_update_ps(mvm); + +out_unlock: + if (mvmvif->csa_failed) + ret = -EIO; + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_abort_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_chan_switch_te_cmd cmd = { + .mac_id = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), + }; + + IWL_DEBUG_MAC80211(mvm, "Abort CSA on mac %d\n", mvmvif->id); + + mutex_lock(&mvm->mutex); + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) + iwl_mvm_remove_csa_period(mvm, vif); + else + WARN_ON(iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, + CHANNEL_SWITCH_TIME_EVENT_CMD), + 0, sizeof(cmd), &cmd)); + mvmvif->csa_failed = true; + mutex_unlock(&mvm->mutex); + + iwl_mvm_post_channel_switch(hw, vif); +} + +static void iwl_mvm_channel_switch_disconnect_wk(struct work_struct *wk) +{ + struct iwl_mvm_vif *mvmvif; + struct ieee80211_vif *vif; + + mvmvif = container_of(wk, struct iwl_mvm_vif, csa_work.work); + vif = container_of((void *)mvmvif, struct ieee80211_vif, drv_priv); + + /* Trigger disconnect (should clear the CSA state) */ + ieee80211_chswitch_done(vif, false); +} + +static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + mvmvif->mvm = mvm; + RCU_INIT_POINTER(mvmvif->probe_resp_data, NULL); + + /* + * Not much to do here. The stack will not allow interface + * types or combinations that we didn't advertise, so we + * don't really have to check the types. + */ + + mutex_lock(&mvm->mutex); + + /* make sure that beacon statistics don't go backwards with FW reset */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + mvmvif->beacon_stats.accu_num_beacons += + mvmvif->beacon_stats.num_beacons; + + /* Allocate resources for the MAC context, and add it to the fw */ + ret = iwl_mvm_mac_ctxt_init(mvm, vif); + if (ret) + goto out_unlock; + + rcu_assign_pointer(mvm->vif_id_to_mac[mvmvif->id], vif); + + /* Counting number of interfaces is needed for legacy PM */ + if (vif->type != NL80211_IFTYPE_P2P_DEVICE) + mvm->vif_count++; + + /* + * The AP binding flow can be done only after the beacon + * template is configured (which happens only in the mac80211 + * start_ap() flow), and adding the broadcast station can happen + * only after the binding. + * In addition, since modifying the MAC before adding a bcast + * station is not allowed by the FW, delay the adding of MAC context to + * the point where we can also add the bcast station. + * In short: there's not much we can do at this point, other than + * allocating resources :) + */ + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { + ret = iwl_mvm_alloc_bcast_sta(mvm, vif); + if (ret) { + IWL_ERR(mvm, "Failed to allocate bcast sta\n"); + goto out_release; + } + + /* + * Only queue for this station is the mcast queue, + * which shouldn't be in TFD mask anyway + */ + ret = iwl_mvm_allocate_int_sta(mvm, &mvmvif->mcast_sta, + 0, vif->type, + IWL_STA_MULTICAST); + if (ret) + goto out_release; + + iwl_mvm_vif_dbgfs_register(mvm, vif); + goto out_unlock; + } + + mvmvif->features |= hw->netdev_features; + + ret = iwl_mvm_mac_ctxt_add(mvm, vif); + if (ret) + goto out_release; + + ret = iwl_mvm_power_update_mac(mvm); + if (ret) + goto out_remove_mac; + + /* beacon filtering */ + ret = iwl_mvm_disable_beacon_filter(mvm, vif, 0); + if (ret) + goto out_remove_mac; + + if (!mvm->bf_allowed_vif && + vif->type == NL80211_IFTYPE_STATION && !vif->p2p) { + mvm->bf_allowed_vif = mvmvif; + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER | + IEEE80211_VIF_SUPPORTS_CQM_RSSI; + } + + /* + * P2P_DEVICE interface does not have a channel context assigned to it, + * so a dedicated PHY context is allocated to it and the corresponding + * MAC context is bound to it at this stage. + */ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + + mvmvif->phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm); + if (!mvmvif->phy_ctxt) { + ret = -ENOSPC; + goto out_free_bf; + } + + iwl_mvm_phy_ctxt_ref(mvm, mvmvif->phy_ctxt); + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (ret) + goto out_unref_phy; + + ret = iwl_mvm_add_p2p_bcast_sta(mvm, vif); + if (ret) + goto out_unbind; + + /* Save a pointer to p2p device vif, so it can later be used to + * update the p2p device MAC when a GO is started/stopped */ + mvm->p2p_device_vif = vif; + } + + iwl_mvm_tcm_add_vif(mvm, vif); + INIT_DELAYED_WORK(&mvmvif->csa_work, + iwl_mvm_channel_switch_disconnect_wk); + + if (vif->type == NL80211_IFTYPE_MONITOR) + mvm->monitor_on = true; + + iwl_mvm_vif_dbgfs_register(mvm, vif); + goto out_unlock; + + out_unbind: + iwl_mvm_binding_remove_vif(mvm, vif); + out_unref_phy: + iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt); + out_free_bf: + if (mvm->bf_allowed_vif == mvmvif) { + mvm->bf_allowed_vif = NULL; + vif->driver_flags &= ~(IEEE80211_VIF_BEACON_FILTER | + IEEE80211_VIF_SUPPORTS_CQM_RSSI); + } + out_remove_mac: + mvmvif->phy_ctxt = NULL; + iwl_mvm_mac_ctxt_remove(mvm, vif); + out_release: + if (vif->type != NL80211_IFTYPE_P2P_DEVICE) + mvm->vif_count--; + out_unlock: + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_prepare_mac_removal(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + /* + * Flush the ROC worker which will flush the OFFCHANNEL queue. + * We assume here that all the packets sent to the OFFCHANNEL + * queue are sent in ROC session. + */ + flush_work(&mvm->roc_done_wk); + } +} + +static void iwl_mvm_mac_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_probe_resp_data *probe_data; + + iwl_mvm_prepare_mac_removal(mvm, vif); + + if (!(vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC)) + iwl_mvm_tcm_rm_vif(mvm, vif); + + mutex_lock(&mvm->mutex); + + probe_data = rcu_dereference_protected(mvmvif->probe_resp_data, + lockdep_is_held(&mvm->mutex)); + RCU_INIT_POINTER(mvmvif->probe_resp_data, NULL); + if (probe_data) + kfree_rcu(probe_data, rcu_head); + + if (mvm->bf_allowed_vif == mvmvif) { + mvm->bf_allowed_vif = NULL; + vif->driver_flags &= ~(IEEE80211_VIF_BEACON_FILTER | + IEEE80211_VIF_SUPPORTS_CQM_RSSI); + } + + if (vif->bss_conf.ftm_responder) + memset(&mvm->ftm_resp_stats, 0, sizeof(mvm->ftm_resp_stats)); + + iwl_mvm_vif_dbgfs_clean(mvm, vif); + + /* + * For AP/GO interface, the tear down of the resources allocated to the + * interface is be handled as part of the stop_ap flow. + */ + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { +#ifdef CONFIG_NL80211_TESTMODE + if (vif == mvm->noa_vif) { + mvm->noa_vif = NULL; + mvm->noa_duration = 0; + } +#endif + iwl_mvm_dealloc_int_sta(mvm, &mvmvif->mcast_sta); + iwl_mvm_dealloc_bcast_sta(mvm, vif); + goto out_release; + } + + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + mvm->p2p_device_vif = NULL; + iwl_mvm_rm_p2p_bcast_sta(mvm, vif); + iwl_mvm_binding_remove_vif(mvm, vif); + iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt); + mvmvif->phy_ctxt = NULL; + } + + if (mvm->vif_count && vif->type != NL80211_IFTYPE_P2P_DEVICE) + mvm->vif_count--; + + iwl_mvm_power_update_mac(mvm); + iwl_mvm_mac_ctxt_remove(mvm, vif); + + RCU_INIT_POINTER(mvm->vif_id_to_mac[mvmvif->id], NULL); + + if (vif->type == NL80211_IFTYPE_MONITOR) + mvm->monitor_on = false; + +out_release: + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_config(struct ieee80211_hw *hw, u32 changed) +{ + return 0; +} + +struct iwl_mvm_mc_iter_data { + struct iwl_mvm *mvm; + int port_id; +}; + +static void iwl_mvm_mc_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_mc_iter_data *data = _data; + struct iwl_mvm *mvm = data->mvm; + struct iwl_mcast_filter_cmd *cmd = mvm->mcast_filter_cmd; + struct iwl_host_cmd hcmd = { + .id = MCAST_FILTER_CMD, + .flags = CMD_ASYNC, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + int ret, len; + + /* if we don't have free ports, mcast frames will be dropped */ + if (WARN_ON_ONCE(data->port_id >= MAX_PORT_ID_NUM)) + return; + + if (vif->type != NL80211_IFTYPE_STATION || + !vif->bss_conf.assoc) + return; + + cmd->port_id = data->port_id++; + memcpy(cmd->bssid, vif->bss_conf.bssid, ETH_ALEN); + len = roundup(sizeof(*cmd) + cmd->count * ETH_ALEN, 4); + + hcmd.len[0] = len; + hcmd.data[0] = cmd; + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + if (ret) + IWL_ERR(mvm, "mcast filter cmd error. ret=%d\n", ret); +} + +static void iwl_mvm_recalc_multicast(struct iwl_mvm *mvm) +{ + struct iwl_mvm_mc_iter_data iter_data = { + .mvm = mvm, + }; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(!mvm->mcast_filter_cmd)) + return; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_mc_iface_iterator, &iter_data); +} + +static u64 iwl_mvm_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mcast_filter_cmd *cmd; + struct netdev_hw_addr *addr; + int addr_count; + bool pass_all; + int len; + + addr_count = netdev_hw_addr_list_count(mc_list); + pass_all = addr_count > MAX_MCAST_FILTERING_ADDRESSES || + IWL_MVM_FW_MCAST_FILTER_PASS_ALL; + if (pass_all) + addr_count = 0; + + len = roundup(sizeof(*cmd) + addr_count * ETH_ALEN, 4); + cmd = kzalloc(len, GFP_ATOMIC); + if (!cmd) + return 0; + + if (pass_all) { + cmd->pass_all = 1; + return (u64)(uintptr_t)cmd; + } + + netdev_hw_addr_list_for_each(addr, mc_list) { +#if defined(__linux__) + IWL_DEBUG_MAC80211(mvm, "mcast addr (%d): %pM\n", + cmd->count, addr->addr); +#elif defined(__FreeBSD__) + IWL_DEBUG_MAC80211(mvm, "mcast addr (%d): %6D\n", + cmd->count, addr->addr, ":"); +#endif + memcpy(&cmd->addr_list[cmd->count * ETH_ALEN], + addr->addr, ETH_ALEN); + cmd->count++; + } + + return (u64)(uintptr_t)cmd; +} + +static void iwl_mvm_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mcast_filter_cmd *cmd = (void *)(uintptr_t)multicast; + + mutex_lock(&mvm->mutex); + + /* replace previous configuration */ + kfree(mvm->mcast_filter_cmd); + mvm->mcast_filter_cmd = cmd; + + if (!cmd) + goto out; + + if (changed_flags & FIF_ALLMULTI) + cmd->pass_all = !!(*total_flags & FIF_ALLMULTI); + + if (cmd->pass_all) + cmd->count = 0; + + iwl_mvm_recalc_multicast(mvm); +out: + mutex_unlock(&mvm->mutex); + *total_flags = 0; +} + +static void iwl_mvm_config_iface_filter(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int filter_flags, + unsigned int changed_flags) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* We support only filter for probe requests */ + if (!(changed_flags & FIF_PROBE_REQ)) + return; + + /* Supported only for p2p client interfaces */ + if (vif->type != NL80211_IFTYPE_STATION || !vif->bss_conf.assoc || + !vif->p2p) + return; + + mutex_lock(&mvm->mutex); + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + mutex_unlock(&mvm->mutex); +} + +#ifdef CONFIG_IWLWIFI_BCAST_FILTERING +struct iwl_bcast_iter_data { + struct iwl_mvm *mvm; + struct iwl_bcast_filter_cmd *cmd; + u8 current_filter; +}; + +static void +iwl_mvm_set_bcast_filter(struct ieee80211_vif *vif, + const struct iwl_fw_bcast_filter *in_filter, + struct iwl_fw_bcast_filter *out_filter) +{ + struct iwl_fw_bcast_filter_attr *attr; + int i; + + memcpy(out_filter, in_filter, sizeof(*out_filter)); + + for (i = 0; i < ARRAY_SIZE(out_filter->attrs); i++) { + attr = &out_filter->attrs[i]; + + if (!attr->mask) + break; + + switch (attr->reserved1) { + case cpu_to_le16(BC_FILTER_MAGIC_IP): + if (vif->bss_conf.arp_addr_cnt != 1) { + attr->mask = 0; + continue; + } + + attr->val = vif->bss_conf.arp_addr_list[0]; + break; + case cpu_to_le16(BC_FILTER_MAGIC_MAC): + attr->val = *(__be32 *)&vif->addr[2]; + break; + default: + break; + } + attr->reserved1 = 0; + out_filter->num_attrs++; + } +} + +static void iwl_mvm_bcast_filter_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_bcast_iter_data *data = _data; + struct iwl_mvm *mvm = data->mvm; + struct iwl_bcast_filter_cmd *cmd = data->cmd; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_fw_bcast_mac *bcast_mac; + int i; + + if (WARN_ON(mvmvif->id >= ARRAY_SIZE(cmd->macs))) + return; + + bcast_mac = &cmd->macs[mvmvif->id]; + + /* + * enable filtering only for associated stations, but not for P2P + * Clients + */ + if (vif->type != NL80211_IFTYPE_STATION || vif->p2p || + !vif->bss_conf.assoc) + return; + + bcast_mac->default_discard = 1; + + /* copy all configured filters */ + for (i = 0; mvm->bcast_filters[i].attrs[0].mask; i++) { + /* + * Make sure we don't exceed our filters limit. + * if there is still a valid filter to be configured, + * be on the safe side and just allow bcast for this mac. + */ + if (WARN_ON_ONCE(data->current_filter >= + ARRAY_SIZE(cmd->filters))) { + bcast_mac->default_discard = 0; + bcast_mac->attached_filters = 0; + break; + } + + iwl_mvm_set_bcast_filter(vif, + &mvm->bcast_filters[i], + &cmd->filters[data->current_filter]); + + /* skip current filter if it contains no attributes */ + if (!cmd->filters[data->current_filter].num_attrs) + continue; + + /* attach the filter to current mac */ + bcast_mac->attached_filters |= + cpu_to_le16(BIT(data->current_filter)); + + data->current_filter++; + } +} + +bool iwl_mvm_bcast_filter_build_cmd(struct iwl_mvm *mvm, + struct iwl_bcast_filter_cmd *cmd) +{ + struct iwl_bcast_iter_data iter_data = { + .mvm = mvm, + .cmd = cmd, + }; + + if (IWL_MVM_FW_BCAST_FILTER_PASS_ALL) + return false; + + memset(cmd, 0, sizeof(*cmd)); + cmd->max_bcast_filters = ARRAY_SIZE(cmd->filters); + cmd->max_macs = ARRAY_SIZE(cmd->macs); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + /* use debugfs filters/macs if override is configured */ + if (mvm->dbgfs_bcast_filtering.override) { + memcpy(cmd->filters, &mvm->dbgfs_bcast_filtering.cmd.filters, + sizeof(cmd->filters)); + memcpy(cmd->macs, &mvm->dbgfs_bcast_filtering.cmd.macs, + sizeof(cmd->macs)); + return true; + } +#endif + + /* if no filters are configured, do nothing */ + if (!mvm->bcast_filters) + return false; + + /* configure and attach these filters for each associated sta vif */ + ieee80211_iterate_active_interfaces( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bcast_filter_iterator, &iter_data); + + return true; +} + +static int iwl_mvm_configure_bcast_filter(struct iwl_mvm *mvm) +{ + struct iwl_bcast_filter_cmd cmd; + + if (!(mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_BCAST_FILTERING)) + return 0; + + if (!iwl_mvm_bcast_filter_build_cmd(mvm, &cmd)) + return 0; + + return iwl_mvm_send_cmd_pdu(mvm, BCAST_FILTER_CMD, 0, + sizeof(cmd), &cmd); +} +#else +static inline int iwl_mvm_configure_bcast_filter(struct iwl_mvm *mvm) +{ + return 0; +} +#endif + +static int iwl_mvm_update_mu_groups(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mu_group_mgmt_cmd cmd = {}; + + memcpy(cmd.membership_status, vif->bss_conf.mu_group.membership, + WLAN_MEMBERSHIP_LEN); + memcpy(cmd.user_position, vif->bss_conf.mu_group.position, + WLAN_USER_POSITION_LEN); + + return iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(DATA_PATH_GROUP, + UPDATE_MU_GROUPS_CMD), + 0, sizeof(cmd), &cmd); +} + +static void iwl_mvm_mu_mimo_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + if (vif->mu_mimo_owner) { + struct iwl_mu_group_mgmt_notif *notif = _data; + + /* + * MU-MIMO Group Id action frame is little endian. We treat + * the data received from firmware as if it came from the + * action frame, so no conversion is needed. + */ + ieee80211_update_mu_groups(vif, + (u8 *)¬if->membership_status, + (u8 *)¬if->user_position); + } +} + +void iwl_mvm_mu_mimo_grp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mu_group_mgmt_notif *notif = (void *)pkt->data; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_mu_mimo_iface_iterator, notif); +} + +static u8 iwl_mvm_he_get_ppe_val(u8 *ppe, u8 ppe_pos_bit) +{ + u8 byte_num = ppe_pos_bit / 8; + u8 bit_num = ppe_pos_bit % 8; + u8 residue_bits; + u8 res; + + if (bit_num <= 5) + return (ppe[byte_num] >> bit_num) & + (BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE) - 1); + + /* + * If bit_num > 5, we have to combine bits with next byte. + * Calculate how many bits we need to take from current byte (called + * here "residue_bits"), and add them to bits from next byte. + */ + + residue_bits = 8 - bit_num; + + res = (ppe[byte_num + 1] & + (BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE - residue_bits) - 1)) << + residue_bits; + res += (ppe[byte_num] >> bit_num) & (BIT(residue_bits) - 1); + + return res; +} + +static void iwl_mvm_cfg_he_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, u8 sta_id) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_he_sta_context_cmd sta_ctxt_cmd = { + .sta_id = sta_id, + .tid_limit = IWL_MAX_TID_COUNT, + .bss_color = vif->bss_conf.he_bss_color.color, + .htc_trig_based_pkt_ext = vif->bss_conf.htc_trig_based_pkt_ext, + .frame_time_rts_th = + cpu_to_le16(vif->bss_conf.frame_time_rts_th), + }; + int size = fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_MBSSID_HE) ? + sizeof(sta_ctxt_cmd) : + sizeof(struct iwl_he_sta_context_cmd_v1); + struct ieee80211_sta *sta; + u32 flags; + int i; + const struct ieee80211_sta_he_cap *own_he_cap = NULL; + struct ieee80211_chanctx_conf *chanctx_conf; + const struct ieee80211_supported_band *sband; + + rcu_read_lock(); + + chanctx_conf = rcu_dereference(vif->chanctx_conf); + if (WARN_ON(!chanctx_conf)) { + rcu_read_unlock(); + return; + } + + sband = mvm->hw->wiphy->bands[chanctx_conf->def.chan->band]; + own_he_cap = ieee80211_get_he_iftype_cap(sband, + ieee80211_vif_type_p2p(vif)); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_ctxt_cmd.sta_id]); + if (IS_ERR_OR_NULL(sta)) { + rcu_read_unlock(); + WARN(1, "Can't find STA to configure HE\n"); + return; + } + + if (!sta->he_cap.has_he) { + rcu_read_unlock(); + return; + } + + flags = 0; + + /* Block 26-tone RU OFDMA transmissions */ + if (mvmvif->he_ru_2mhz_block) + flags |= STA_CTXT_HE_RU_2MHZ_BLOCK; + + /* HTC flags */ + if (sta->he_cap.he_cap_elem.mac_cap_info[0] & + IEEE80211_HE_MAC_CAP0_HTC_HE) + sta_ctxt_cmd.htc_flags |= cpu_to_le32(IWL_HE_HTC_SUPPORT); + if ((sta->he_cap.he_cap_elem.mac_cap_info[1] & + IEEE80211_HE_MAC_CAP1_LINK_ADAPTATION) || + (sta->he_cap.he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_LINK_ADAPTATION)) { + u8 link_adap = + ((sta->he_cap.he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_LINK_ADAPTATION) << 1) + + (sta->he_cap.he_cap_elem.mac_cap_info[1] & + IEEE80211_HE_MAC_CAP1_LINK_ADAPTATION); + + if (link_adap == 2) + sta_ctxt_cmd.htc_flags |= + cpu_to_le32(IWL_HE_HTC_LINK_ADAP_UNSOLICITED); + else if (link_adap == 3) + sta_ctxt_cmd.htc_flags |= + cpu_to_le32(IWL_HE_HTC_LINK_ADAP_BOTH); + } + if (sta->he_cap.he_cap_elem.mac_cap_info[2] & IEEE80211_HE_MAC_CAP2_BSR) + sta_ctxt_cmd.htc_flags |= cpu_to_le32(IWL_HE_HTC_BSR_SUPP); + if (sta->he_cap.he_cap_elem.mac_cap_info[3] & + IEEE80211_HE_MAC_CAP3_OMI_CONTROL) + sta_ctxt_cmd.htc_flags |= cpu_to_le32(IWL_HE_HTC_OMI_SUPP); + if (sta->he_cap.he_cap_elem.mac_cap_info[4] & IEEE80211_HE_MAC_CAP4_BQR) + sta_ctxt_cmd.htc_flags |= cpu_to_le32(IWL_HE_HTC_BQR_SUPP); + + /* + * Initialize the PPE thresholds to "None" (7), as described in Table + * 9-262ac of 80211.ax/D3.0. + */ + memset(&sta_ctxt_cmd.pkt_ext, 7, sizeof(sta_ctxt_cmd.pkt_ext)); + + /* If PPE Thresholds exist, parse them into a FW-familiar format. */ + if (sta->he_cap.he_cap_elem.phy_cap_info[6] & + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) { + u8 nss = (sta->he_cap.ppe_thres[0] & + IEEE80211_PPE_THRES_NSS_MASK) + 1; + u8 ru_index_bitmap = + (sta->he_cap.ppe_thres[0] & + IEEE80211_PPE_THRES_RU_INDEX_BITMASK_MASK) >> + IEEE80211_PPE_THRES_RU_INDEX_BITMASK_POS; + u8 *ppe = &sta->he_cap.ppe_thres[0]; + u8 ppe_pos_bit = 7; /* Starting after PPE header */ + + /* + * FW currently supports only nss == MAX_HE_SUPP_NSS + * + * If nss > MAX: we can ignore values we don't support + * If nss < MAX: we can set zeros in other streams + */ + if (nss > MAX_HE_SUPP_NSS) { + IWL_INFO(mvm, "Got NSS = %d - trimming to %d\n", nss, + MAX_HE_SUPP_NSS); + nss = MAX_HE_SUPP_NSS; + } + + for (i = 0; i < nss; i++) { + u8 ru_index_tmp = ru_index_bitmap << 1; + u8 bw; + + for (bw = 0; bw < MAX_HE_CHANNEL_BW_INDX; bw++) { + ru_index_tmp >>= 1; + if (!(ru_index_tmp & 1)) + continue; + + sta_ctxt_cmd.pkt_ext.pkt_ext_qam_th[i][bw][1] = + iwl_mvm_he_get_ppe_val(ppe, + ppe_pos_bit); + ppe_pos_bit += + IEEE80211_PPE_THRES_INFO_PPET_SIZE; + sta_ctxt_cmd.pkt_ext.pkt_ext_qam_th[i][bw][0] = + iwl_mvm_he_get_ppe_val(ppe, + ppe_pos_bit); + ppe_pos_bit += + IEEE80211_PPE_THRES_INFO_PPET_SIZE; + } + } + + flags |= STA_CTXT_HE_PACKET_EXT; + } else if ((sta->he_cap.he_cap_elem.phy_cap_info[9] & + IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_MASK) != + IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_RESERVED) { + int low_th = -1; + int high_th = -1; + + /* Take the PPE thresholds from the nominal padding info */ + switch (sta->he_cap.he_cap_elem.phy_cap_info[9] & + IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_MASK) { + case IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_0US: + low_th = IWL_HE_PKT_EXT_NONE; + high_th = IWL_HE_PKT_EXT_NONE; + break; + case IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_8US: + low_th = IWL_HE_PKT_EXT_BPSK; + high_th = IWL_HE_PKT_EXT_NONE; + break; + case IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_16US: + low_th = IWL_HE_PKT_EXT_NONE; + high_th = IWL_HE_PKT_EXT_BPSK; + break; + } + + /* Set the PPE thresholds accordingly */ + if (low_th >= 0 && high_th >= 0) { + struct iwl_he_pkt_ext *pkt_ext = + (struct iwl_he_pkt_ext *)&sta_ctxt_cmd.pkt_ext; + + for (i = 0; i < MAX_HE_SUPP_NSS; i++) { + u8 bw; + + for (bw = 0; bw < MAX_HE_CHANNEL_BW_INDX; + bw++) { + pkt_ext->pkt_ext_qam_th[i][bw][0] = + low_th; + pkt_ext->pkt_ext_qam_th[i][bw][1] = + high_th; + } + } + + flags |= STA_CTXT_HE_PACKET_EXT; + } + } + + if (sta->he_cap.he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_32BIT_BA_BITMAP) + flags |= STA_CTXT_HE_32BIT_BA_BITMAP; + + if (sta->he_cap.he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_ACK_EN) + flags |= STA_CTXT_HE_ACK_ENABLED; + + rcu_read_unlock(); + + /* Mark MU EDCA as enabled, unless none detected on some AC */ + flags |= STA_CTXT_HE_MU_EDCA_CW; + for (i = 0; i < IEEE80211_NUM_ACS; i++) { + struct ieee80211_he_mu_edca_param_ac_rec *mu_edca = + &mvmvif->queue_params[i].mu_edca_param_rec; + u8 ac = iwl_mvm_mac80211_ac_to_ucode_ac(i); + + if (!mvmvif->queue_params[i].mu_edca) { + flags &= ~STA_CTXT_HE_MU_EDCA_CW; + break; + } + + sta_ctxt_cmd.trig_based_txf[ac].cwmin = + cpu_to_le16(mu_edca->ecw_min_max & 0xf); + sta_ctxt_cmd.trig_based_txf[ac].cwmax = + cpu_to_le16((mu_edca->ecw_min_max & 0xf0) >> 4); + sta_ctxt_cmd.trig_based_txf[ac].aifsn = + cpu_to_le16(mu_edca->aifsn); + sta_ctxt_cmd.trig_based_txf[ac].mu_time = + cpu_to_le16(mu_edca->mu_edca_timer); + } + + + if (vif->bss_conf.uora_exists) { + flags |= STA_CTXT_HE_TRIG_RND_ALLOC; + + sta_ctxt_cmd.rand_alloc_ecwmin = + vif->bss_conf.uora_ocw_range & 0x7; + sta_ctxt_cmd.rand_alloc_ecwmax = + (vif->bss_conf.uora_ocw_range >> 3) & 0x7; + } + + if (own_he_cap && !(own_he_cap->he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_ACK_EN)) + flags |= STA_CTXT_HE_NIC_NOT_ACK_ENABLED; + + if (vif->bss_conf.nontransmitted) { + flags |= STA_CTXT_HE_REF_BSSID_VALID; + ether_addr_copy(sta_ctxt_cmd.ref_bssid_addr, + vif->bss_conf.transmitter_bssid); + sta_ctxt_cmd.max_bssid_indicator = + vif->bss_conf.bssid_indicator; + sta_ctxt_cmd.bssid_index = vif->bss_conf.bssid_index; + sta_ctxt_cmd.ema_ap = vif->bss_conf.ema_ap; + sta_ctxt_cmd.profile_periodicity = + vif->bss_conf.profile_periodicity; + } + + sta_ctxt_cmd.flags = cpu_to_le32(flags); + + if (iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(STA_HE_CTXT_CMD, + DATA_PATH_GROUP, 0), + 0, size, &sta_ctxt_cmd)) + IWL_ERR(mvm, "Failed to config FW to work HE!\n"); +} + +static void iwl_mvm_protect_assoc(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration_override) +{ + u32 duration = IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS; + u32 min_duration = IWL_MVM_TE_SESSION_PROTECTION_MIN_TIME_MS; + + if (duration_override > duration) + duration = duration_override; + + /* Try really hard to protect the session and hear a beacon + * The new session protection command allows us to protect the + * session for a much longer time since the firmware will internally + * create two events: a 300TU one with a very high priority that + * won't be fragmented which should be enough for 99% of the cases, + * and another one (which we configure here to be 900TU long) which + * will have a slightly lower priority, but more importantly, can be + * fragmented so that it'll allow other activities to run. + */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) + iwl_mvm_schedule_session_protection(mvm, vif, 900, + min_duration, false); + else + iwl_mvm_protect_session(mvm, vif, duration, + min_duration, 500, false); +} + +static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + /* + * Re-calculate the tsf id, as the leader-follower relations depend + * on the beacon interval, which was not known when the station + * interface was added. + */ + if (changes & BSS_CHANGED_ASSOC && bss_conf->assoc) { + if (vif->bss_conf.he_support && + !iwlwifi_mod_params.disable_11ax) + iwl_mvm_cfg_he_sta(mvm, vif, mvmvif->ap_sta_id); + + iwl_mvm_mac_ctxt_recalc_tsf_id(mvm, vif); + } + + /* Update MU EDCA params */ + if (changes & BSS_CHANGED_QOS && mvmvif->associated && + bss_conf->assoc && vif->bss_conf.he_support && + !iwlwifi_mod_params.disable_11ax) + iwl_mvm_cfg_he_sta(mvm, vif, mvmvif->ap_sta_id); + + /* + * If we're not associated yet, take the (new) BSSID before associating + * so the firmware knows. If we're already associated, then use the old + * BSSID here, and we'll send a cleared one later in the CHANGED_ASSOC + * branch for disassociation below. + */ + if (changes & BSS_CHANGED_BSSID && !mvmvif->associated) + memcpy(mvmvif->bssid, bss_conf->bssid, ETH_ALEN); + + ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, mvmvif->bssid); + if (ret) + IWL_ERR(mvm, "failed to update MAC %pM\n", vif->addr); + + /* after sending it once, adopt mac80211 data */ + memcpy(mvmvif->bssid, bss_conf->bssid, ETH_ALEN); + mvmvif->associated = bss_conf->assoc; + + if (changes & BSS_CHANGED_ASSOC) { + if (bss_conf->assoc) { + /* clear statistics to get clean beacon counter */ + iwl_mvm_request_statistics(mvm, true); + memset(&mvmvif->beacon_stats, 0, + sizeof(mvmvif->beacon_stats)); + + /* add quota for this interface */ + ret = iwl_mvm_update_quotas(mvm, true, NULL); + if (ret) { + IWL_ERR(mvm, "failed to update quotas\n"); + return; + } + + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, + &mvm->status) && + !fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) { + /* + * If we're restarting then the firmware will + * obviously have lost synchronisation with + * the AP. It will attempt to synchronise by + * itself, but we can make it more reliable by + * scheduling a session protection time event. + * + * The firmware needs to receive a beacon to + * catch up with synchronisation, use 110% of + * the beacon interval. + * + * Set a large maximum delay to allow for more + * than a single interface. + * + * For new firmware versions, rely on the + * firmware. This is relevant for DCM scenarios + * only anyway. + */ + u32 dur = (11 * vif->bss_conf.beacon_int) / 10; + iwl_mvm_protect_session(mvm, vif, dur, dur, + 5 * dur, false); + } else if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, + &mvm->status) && + !vif->bss_conf.dtim_period) { + /* + * If we're not restarting and still haven't + * heard a beacon (dtim period unknown) then + * make sure we still have enough minimum time + * remaining in the time event, since the auth + * might actually have taken quite a while + * (especially for SAE) and so the remaining + * time could be small without us having heard + * a beacon yet. + */ + iwl_mvm_protect_assoc(mvm, vif, 0); + } + + iwl_mvm_sf_update(mvm, vif, false); + iwl_mvm_power_vif_assoc(mvm, vif); + if (vif->p2p) { + iwl_mvm_update_smps(mvm, vif, + IWL_MVM_SMPS_REQ_PROT, + IEEE80211_SMPS_DYNAMIC); + } + } else if (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) { + /* + * If update fails - SF might be running in associated + * mode while disassociated - which is forbidden. + */ + ret = iwl_mvm_sf_update(mvm, vif, false); + WARN_ONCE(ret && + !test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status), + "Failed to update SF upon disassociation\n"); + + /* + * If we get an assert during the connection (after the + * station has been added, but before the vif is set + * to associated), mac80211 will re-add the station and + * then configure the vif. Since the vif is not + * associated, we would remove the station here and + * this would fail the recovery. + */ + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, + &mvm->status)) { + /* + * Remove AP station now that + * the MAC is unassoc + */ + ret = iwl_mvm_rm_sta_id(mvm, vif, + mvmvif->ap_sta_id); + if (ret) + IWL_ERR(mvm, + "failed to remove AP station\n"); + + mvmvif->ap_sta_id = IWL_MVM_INVALID_STA; + } + + /* remove quota for this interface */ + ret = iwl_mvm_update_quotas(mvm, false, NULL); + if (ret) + IWL_ERR(mvm, "failed to update quotas\n"); + + /* this will take the cleared BSSID from bss_conf */ + ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + if (ret) + IWL_ERR(mvm, + "failed to update MAC %pM (clear after unassoc)\n", + vif->addr); + } + + /* + * The firmware tracks the MU-MIMO group on its own. + * However, on HW restart we should restore this data. + */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + (changes & BSS_CHANGED_MU_GROUPS) && vif->mu_mimo_owner) { + ret = iwl_mvm_update_mu_groups(mvm, vif); + if (ret) + IWL_ERR(mvm, + "failed to update VHT MU_MIMO groups\n"); + } + + iwl_mvm_recalc_multicast(mvm); + iwl_mvm_configure_bcast_filter(mvm); + + /* reset rssi values */ + mvmvif->bf_data.ave_beacon_signal = 0; + + iwl_mvm_bt_coex_vif_change(mvm); + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, + IEEE80211_SMPS_AUTOMATIC); + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_UMAC_SCAN)) + iwl_mvm_config_scan(mvm); + } + + if (changes & BSS_CHANGED_BEACON_INFO) { + /* + * We received a beacon from the associated AP so + * remove the session protection. + * A firmware with the new API will remove it automatically. + */ + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) + iwl_mvm_stop_session_protection(mvm, vif); + + iwl_mvm_sf_update(mvm, vif, false); + WARN_ON(iwl_mvm_enable_beacon_filter(mvm, vif, 0)); + } + + if (changes & (BSS_CHANGED_PS | BSS_CHANGED_P2P_PS | BSS_CHANGED_QOS | + /* + * Send power command on every beacon change, + * because we may have not enabled beacon abort yet. + */ + BSS_CHANGED_BEACON_INFO)) { + ret = iwl_mvm_power_update_mac(mvm); + if (ret) + IWL_ERR(mvm, "failed to update power mode\n"); + } + + if (changes & BSS_CHANGED_CQM) { + IWL_DEBUG_MAC80211(mvm, "cqm info_changed\n"); + /* reset cqm events tracking */ + mvmvif->bf_data.last_cqm_event = 0; + if (mvmvif->bf_data.bf_enabled) { + ret = iwl_mvm_enable_beacon_filter(mvm, vif, 0); + if (ret) + IWL_ERR(mvm, + "failed to update CQM thresholds\n"); + } + } + + if (changes & BSS_CHANGED_ARP_FILTER) { + IWL_DEBUG_MAC80211(mvm, "arp filter changed\n"); + iwl_mvm_configure_bcast_filter(mvm); + } + + if (changes & BSS_CHANGED_BANDWIDTH) + iwl_mvm_apply_fw_smps_request(vif); +} + +static int iwl_mvm_start_ap_ibss(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret, i; + + mutex_lock(&mvm->mutex); + + /* Send the beacon template */ + ret = iwl_mvm_mac_ctxt_beacon_changed(mvm, vif); + if (ret) + goto out_unlock; + + /* + * Re-calculate the tsf id, as the leader-follower relations depend on + * the beacon interval, which was not known when the AP interface + * was added. + */ + if (vif->type == NL80211_IFTYPE_AP) + iwl_mvm_mac_ctxt_recalc_tsf_id(mvm, vif); + + mvmvif->ap_assoc_sta_count = 0; + + /* Add the mac context */ + ret = iwl_mvm_mac_ctxt_add(mvm, vif); + if (ret) + goto out_unlock; + + /* Perform the binding */ + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (ret) + goto out_remove; + + /* + * This is not very nice, but the simplest: + * For older FWs adding the mcast sta before the bcast station may + * cause assert 0x2b00. + * This is fixed in later FW so make the order of removal depend on + * the TLV + */ + if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) { + ret = iwl_mvm_add_mcast_sta(mvm, vif); + if (ret) + goto out_unbind; + /* + * Send the bcast station. At this stage the TBTT and DTIM time + * events are added and applied to the scheduler + */ + ret = iwl_mvm_send_add_bcast_sta(mvm, vif); + if (ret) { + iwl_mvm_rm_mcast_sta(mvm, vif); + goto out_unbind; + } + } else { + /* + * Send the bcast station. At this stage the TBTT and DTIM time + * events are added and applied to the scheduler + */ + ret = iwl_mvm_send_add_bcast_sta(mvm, vif); + if (ret) + goto out_unbind; + ret = iwl_mvm_add_mcast_sta(mvm, vif); + if (ret) { + iwl_mvm_send_rm_bcast_sta(mvm, vif); + goto out_unbind; + } + } + + /* must be set before quota calculations */ + mvmvif->ap_ibss_active = true; + + /* send all the early keys to the device now */ + for (i = 0; i < ARRAY_SIZE(mvmvif->ap_early_keys); i++) { + struct ieee80211_key_conf *key = mvmvif->ap_early_keys[i]; + + if (!key) + continue; + + mvmvif->ap_early_keys[i] = NULL; + + ret = __iwl_mvm_mac_set_key(hw, SET_KEY, vif, NULL, key); + if (ret) + goto out_quota_failed; + } + + if (vif->type == NL80211_IFTYPE_AP && !vif->p2p) { + iwl_mvm_vif_set_low_latency(mvmvif, true, + LOW_LATENCY_VIF_TYPE); + iwl_mvm_send_low_latency_cmd(mvm, true, mvmvif->id); + } + + /* power updated needs to be done before quotas */ + iwl_mvm_power_update_mac(mvm); + + ret = iwl_mvm_update_quotas(mvm, false, NULL); + if (ret) + goto out_quota_failed; + + /* Need to update the P2P Device MAC (only GO, IBSS is single vif) */ + if (vif->p2p && mvm->p2p_device_vif) + iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif, false, NULL); + + iwl_mvm_bt_coex_vif_change(mvm); + + /* we don't support TDLS during DCM */ + if (iwl_mvm_phy_ctx_count(mvm) > 1) + iwl_mvm_teardown_tdls_peers(mvm); + + iwl_mvm_ftm_restart_responder(mvm, vif); + + goto out_unlock; + +out_quota_failed: + iwl_mvm_power_update_mac(mvm); + mvmvif->ap_ibss_active = false; + iwl_mvm_send_rm_bcast_sta(mvm, vif); + iwl_mvm_rm_mcast_sta(mvm, vif); +out_unbind: + iwl_mvm_binding_remove_vif(mvm, vif); +out_remove: + iwl_mvm_mac_ctxt_remove(mvm, vif); +out_unlock: + mutex_unlock(&mvm->mutex); + return ret; +} + +static void iwl_mvm_stop_ap_ibss(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + iwl_mvm_prepare_mac_removal(mvm, vif); + + mutex_lock(&mvm->mutex); + + /* Handle AP stop while in CSA */ + if (rcu_access_pointer(mvm->csa_vif) == vif) { + iwl_mvm_remove_time_event(mvm, mvmvif, + &mvmvif->time_event_data); + RCU_INIT_POINTER(mvm->csa_vif, NULL); + mvmvif->csa_countdown = false; + } + + if (rcu_access_pointer(mvm->csa_tx_blocked_vif) == vif) { + RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); + mvm->csa_tx_block_bcn_timeout = 0; + } + + mvmvif->ap_ibss_active = false; + mvm->ap_last_beacon_gp2 = 0; + + if (vif->type == NL80211_IFTYPE_AP && !vif->p2p) { + iwl_mvm_vif_set_low_latency(mvmvif, false, + LOW_LATENCY_VIF_TYPE); + iwl_mvm_send_low_latency_cmd(mvm, false, mvmvif->id); + } + + iwl_mvm_bt_coex_vif_change(mvm); + + /* Need to update the P2P Device MAC (only GO, IBSS is single vif) */ + if (vif->p2p && mvm->p2p_device_vif) + iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif, false, NULL); + + iwl_mvm_update_quotas(mvm, false, NULL); + + iwl_mvm_ftm_responder_clear(mvm, vif); + + /* + * This is not very nice, but the simplest: + * For older FWs removing the mcast sta before the bcast station may + * cause assert 0x2b00. + * This is fixed in later FW (which will stop beaconing when removing + * bcast station). + * So make the order of removal depend on the TLV + */ + if (!fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + iwl_mvm_rm_mcast_sta(mvm, vif); + iwl_mvm_send_rm_bcast_sta(mvm, vif); + if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + iwl_mvm_rm_mcast_sta(mvm, vif); + iwl_mvm_binding_remove_vif(mvm, vif); + + iwl_mvm_power_update_mac(mvm); + + iwl_mvm_mac_ctxt_remove(mvm, vif); + + mutex_unlock(&mvm->mutex); +} + +static void +iwl_mvm_bss_info_changed_ap_ibss(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* Changes will be applied when the AP/IBSS is started */ + if (!mvmvif->ap_ibss_active) + return; + + if (changes & (BSS_CHANGED_ERP_CTS_PROT | BSS_CHANGED_HT | + BSS_CHANGED_BANDWIDTH | BSS_CHANGED_QOS) && + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL)) + IWL_ERR(mvm, "failed to update MAC %pM\n", vif->addr); + + /* Need to send a new beacon template to the FW */ + if (changes & BSS_CHANGED_BEACON && + iwl_mvm_mac_ctxt_beacon_changed(mvm, vif)) + IWL_WARN(mvm, "Failed updating beacon data\n"); + + if (changes & BSS_CHANGED_FTM_RESPONDER) { + int ret = iwl_mvm_ftm_start_responder(mvm, vif); + + if (ret) + IWL_WARN(mvm, "Failed to enable FTM responder (%d)\n", + ret); + } + +} + +static void iwl_mvm_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + + if (changes & BSS_CHANGED_IDLE && !bss_conf->idle) + iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, true); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + iwl_mvm_bss_info_changed_station(mvm, vif, bss_conf, changes); + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + iwl_mvm_bss_info_changed_ap_ibss(mvm, vif, bss_conf, changes); + break; + case NL80211_IFTYPE_MONITOR: + if (changes & BSS_CHANGED_MU_GROUPS) + iwl_mvm_update_mu_groups(mvm, vif); + break; + default: + /* shouldn't happen */ + WARN_ON_ONCE(1); + } + + if (changes & BSS_CHANGED_TXPOWER) { + IWL_DEBUG_CALIB(mvm, "Changing TX Power to %d dBm\n", + bss_conf->txpower); + iwl_mvm_set_tx_power(mvm, vif, bss_conf->txpower); + } + + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *hw_req) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + if (hw_req->req.n_channels == 0 || + hw_req->req.n_channels > mvm->fw->ucode_capa.n_scan_channels) + return -EINVAL; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_reg_scan_start(mvm, vif, &hw_req->req, &hw_req->ies); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_mac_cancel_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + + /* Due to a race condition, it's possible that mac80211 asks + * us to stop a hw_scan when it's already stopped. This can + * happen, for instance, if we stopped the scan ourselves, + * called ieee80211_scan_completed() and the userspace called + * cancel scan scan before ieee80211_scan_work() could run. + * To handle that, simply return if the scan is not running. + */ + if (mvm->scan_status & IWL_MVM_SCAN_REGULAR) + iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_REGULAR, true); + + mutex_unlock(&mvm->mutex); +} + +static void +iwl_mvm_mac_allow_buffered_frames(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tids, + int num_frames, + enum ieee80211_frame_release_type reason, + bool more_data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* Called when we need to transmit (a) frame(s) from mac80211 */ + + iwl_mvm_sta_modify_sleep_tx_count(mvm, sta, reason, num_frames, + tids, more_data, false); +} + +static void +iwl_mvm_mac_release_buffered_frames(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tids, + int num_frames, + enum ieee80211_frame_release_type reason, + bool more_data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* Called when we need to transmit (a) frame(s) from agg or dqa queue */ + + iwl_mvm_sta_modify_sleep_tx_count(mvm, sta, reason, num_frames, + tids, more_data, true); +} + +static void __iwl_mvm_mac_sta_notify(struct ieee80211_hw *hw, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + unsigned long txqs = 0, tids = 0; + int tid; + + /* + * If we have TVQM then we get too high queue numbers - luckily + * we really shouldn't get here with that because such hardware + * should have firmware supporting buffer station offload. + */ + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return; + + spin_lock_bh(&mvmsta->lock); + for (tid = 0; tid < ARRAY_SIZE(mvmsta->tid_data); tid++) { + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + + if (tid_data->txq_id == IWL_MVM_INVALID_QUEUE) + continue; + + __set_bit(tid_data->txq_id, &txqs); + + if (iwl_mvm_tid_queued(mvm, tid_data) == 0) + continue; + + __set_bit(tid, &tids); + } + + switch (cmd) { + case STA_NOTIFY_SLEEP: + for_each_set_bit(tid, &tids, IWL_MAX_TID_COUNT) + ieee80211_sta_set_buffered(sta, tid, true); + + if (txqs) + iwl_trans_freeze_txq_timer(mvm->trans, txqs, true); + /* + * The fw updates the STA to be asleep. Tx packets on the Tx + * queues to this station will not be transmitted. The fw will + * send a Tx response with TX_STATUS_FAIL_DEST_PS. + */ + break; + case STA_NOTIFY_AWAKE: + if (WARN_ON(mvmsta->sta_id == IWL_MVM_INVALID_STA)) + break; + + if (txqs) + iwl_trans_freeze_txq_timer(mvm->trans, txqs, false); + iwl_mvm_sta_modify_ps_wake(mvm, sta); + break; + default: + break; + } + spin_unlock_bh(&mvmsta->lock); +} + +static void iwl_mvm_mac_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + __iwl_mvm_mac_sta_notify(hw, cmd, sta); +} + +void iwl_mvm_sta_pm_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_pm_state_notification *notif = (void *)pkt->data; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + bool sleeping = (notif->type != IWL_MVM_PM_EVENT_AWAKE); + + if (WARN_ON(notif->sta_id >= mvm->fw->ucode_capa.num_stations)) + return; + + rcu_read_lock(); + sta = rcu_dereference(mvm->fw_id_to_mac_id[notif->sta_id]); + if (WARN_ON(IS_ERR_OR_NULL(sta))) { + rcu_read_unlock(); + return; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (!mvmsta->vif || + mvmsta->vif->type != NL80211_IFTYPE_AP) { + rcu_read_unlock(); + return; + } + + if (mvmsta->sleeping != sleeping) { + mvmsta->sleeping = sleeping; + __iwl_mvm_mac_sta_notify(mvm->hw, + sleeping ? STA_NOTIFY_SLEEP : STA_NOTIFY_AWAKE, + sta); + ieee80211_sta_ps_transition(sta, sleeping); + } + + if (sleeping) { + switch (notif->type) { + case IWL_MVM_PM_EVENT_AWAKE: + case IWL_MVM_PM_EVENT_ASLEEP: + break; + case IWL_MVM_PM_EVENT_UAPSD: + ieee80211_sta_uapsd_trigger(sta, IEEE80211_NUM_TIDS); + break; + case IWL_MVM_PM_EVENT_PS_POLL: + ieee80211_sta_pspoll(sta); + break; + default: + break; + } + } + + rcu_read_unlock(); +} + +static void iwl_mvm_sta_pre_rcu_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + /* + * This is called before mac80211 does RCU synchronisation, + * so here we already invalidate our internal RCU-protected + * station pointer. The rest of the code will thus no longer + * be able to find the station this way, and we don't rely + * on further RCU synchronisation after the sta_state() + * callback deleted the station. + */ + mutex_lock(&mvm->mutex); + if (sta == rcu_access_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id])) + rcu_assign_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id], + ERR_PTR(-ENOENT)); + + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_check_uapsd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + const u8 *bssid) +{ + int i; + + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + struct iwl_mvm_tcm_mac *mdata; + + mdata = &mvm->tcm.data[iwl_mvm_vif_from_mac80211(vif)->id]; + ewma_rate_init(&mdata->uapsd_nonagg_detect.rate); + mdata->opened_rx_ba_sessions = false; + } + + if (!(mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT)) + return; + + if (vif->p2p && !iwl_mvm_is_p2p_scm_uapsd_supported(mvm)) { + vif->driver_flags &= ~IEEE80211_VIF_SUPPORTS_UAPSD; + return; + } + + if (!vif->p2p && + (iwlwifi_mod_params.uapsd_disable & IWL_DISABLE_UAPSD_BSS)) { + vif->driver_flags &= ~IEEE80211_VIF_SUPPORTS_UAPSD; + return; + } + + for (i = 0; i < IWL_MVM_UAPSD_NOAGG_LIST_LEN; i++) { + if (ether_addr_equal(mvm->uapsd_noagg_bssids[i].addr, bssid)) { + vif->driver_flags &= ~IEEE80211_VIF_SUPPORTS_UAPSD; + return; + } + } + + vif->driver_flags |= IEEE80211_VIF_SUPPORTS_UAPSD; +} + +static void +iwl_mvm_tdls_check_trigger(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, u8 *peer_addr, + enum nl80211_tdls_operation action) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_tdls *tdls_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_TDLS); + if (!trig) + return; + + tdls_trig = (void *)trig->data; + + if (!(tdls_trig->action_bitmap & BIT(action))) + return; + + if (tdls_trig->peer_mode && + memcmp(tdls_trig->peer, peer_addr, ETH_ALEN) != 0) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "TDLS event occurred, peer %pM, action %d", + peer_addr, action); +} + +struct iwl_mvm_he_obss_narrow_bw_ru_data { + bool tolerated; +}; + +static void iwl_mvm_check_he_obss_narrow_bw_ru_iter(struct wiphy *wiphy, + struct cfg80211_bss *bss, + void *_data) +{ + struct iwl_mvm_he_obss_narrow_bw_ru_data *data = _data; + const struct cfg80211_bss_ies *ies; + const struct element *elem; + + rcu_read_lock(); + ies = rcu_dereference(bss->ies); + elem = cfg80211_find_elem(WLAN_EID_EXT_CAPABILITY, ies->data, + ies->len); + + if (!elem || elem->datalen < 10 || + !(elem->data[10] & + WLAN_EXT_CAPA10_OBSS_NARROW_BW_RU_TOLERANCE_SUPPORT)) { + data->tolerated = false; + } + rcu_read_unlock(); +} + +static void iwl_mvm_check_he_obss_narrow_bw_ru(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_he_obss_narrow_bw_ru_data iter_data = { + .tolerated = true, + }; + + if (!(vif->bss_conf.chandef.chan->flags & IEEE80211_CHAN_RADAR)) { + mvmvif->he_ru_2mhz_block = false; + return; + } + + cfg80211_bss_iter(hw->wiphy, &vif->bss_conf.chandef, + iwl_mvm_check_he_obss_narrow_bw_ru_iter, + &iter_data); + + /* + * If there is at least one AP on radar channel that cannot + * tolerate 26-tone RU UL OFDMA transmissions using HE TB PPDU. + */ + mvmvif->he_ru_2mhz_block = !iter_data.tolerated; +} + +static void iwl_mvm_reset_cca_40mhz_workaround(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct ieee80211_supported_band *sband; + const struct ieee80211_sta_he_cap *he_cap; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + if (!mvm->cca_40mhz_workaround) + return; + + /* decrement and check that we reached zero */ + mvm->cca_40mhz_workaround--; + if (mvm->cca_40mhz_workaround) + return; + + sband = mvm->hw->wiphy->bands[NL80211_BAND_2GHZ]; + + sband->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + he_cap = ieee80211_get_he_iftype_cap(sband, + ieee80211_vif_type_p2p(vif)); + + if (he_cap) { + /* we know that ours is writable */ + struct ieee80211_sta_he_cap *he = (void *)(uintptr_t)he_cap; + + he->he_cap_elem.phy_cap_info[0] |= + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G; + } +} + +static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + int ret; + + IWL_DEBUG_MAC80211(mvm, "station %pM state change %d->%d\n", + sta->addr, old_state, new_state); + + /* this would be a mac80211 bug ... but don't crash */ + if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) + return test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status) ? 0 : -EINVAL; + + /* + * If we are in a STA removal flow and in DQA mode: + * + * This is after the sync_rcu part, so the queues have already been + * flushed. No more TXs on their way in mac80211's path, and no more in + * the queues. + * Also, we won't be getting any new TX frames for this station. + * What we might have are deferred TX frames that need to be taken care + * of. + * + * Drop any still-queued deferred-frame before removing the STA, and + * make sure the worker is no longer handling frames for this STA. + */ + if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) { + flush_work(&mvm->add_stream_wk); + + /* + * No need to make sure deferred TX indication is off since the + * worker will already remove it if it was on + */ + + /* + * Additionally, reset the 40 MHz capability if we disconnected + * from the AP now. + */ + iwl_mvm_reset_cca_40mhz_workaround(mvm, vif); + } + + mutex_lock(&mvm->mutex); + /* track whether or not the station is associated */ + mvm_sta->sta_state = new_state; + + if (old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) { + /* + * Firmware bug - it'll crash if the beacon interval is less + * than 16. We can't avoid connecting at all, so refuse the + * station state change, this will cause mac80211 to abandon + * attempts to connect to this AP, and eventually wpa_s will + * blocklist the AP... + */ + if (vif->type == NL80211_IFTYPE_STATION && + vif->bss_conf.beacon_int < 16) { + IWL_ERR(mvm, + "AP %pM beacon interval is %d, refusing due to firmware bug!\n", + sta->addr, vif->bss_conf.beacon_int); + ret = -EINVAL; + goto out_unlock; + } + + if (vif->type == NL80211_IFTYPE_STATION) + vif->bss_conf.he_support = sta->he_cap.has_he; + + if (sta->tdls && + (vif->p2p || + iwl_mvm_tdls_sta_count(mvm, NULL) == + IWL_MVM_TDLS_STA_COUNT || + iwl_mvm_phy_ctx_count(mvm) > 1)) { + IWL_DEBUG_MAC80211(mvm, "refusing TDLS sta\n"); + ret = -EBUSY; + goto out_unlock; + } + + ret = iwl_mvm_add_sta(mvm, vif, sta); + if (sta->tdls && ret == 0) { + iwl_mvm_recalc_tdls_state(mvm, vif, true); + iwl_mvm_tdls_check_trigger(mvm, vif, sta->addr, + NL80211_TDLS_SETUP); + } + + sta->max_rc_amsdu_len = 1; + } else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_AUTH) { + /* + * EBS may be disabled due to previous failures reported by FW. + * Reset EBS status here assuming environment has been changed. + */ + mvm->last_ebs_successful = true; + iwl_mvm_check_uapsd(mvm, vif, sta->addr); + ret = 0; + } else if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_ASSOC) { + if (vif->type == NL80211_IFTYPE_AP) { + vif->bss_conf.he_support = sta->he_cap.has_he; + mvmvif->ap_assoc_sta_count++; + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + if (vif->bss_conf.he_support && + !iwlwifi_mod_params.disable_11ax) + iwl_mvm_cfg_he_sta(mvm, vif, mvm_sta->sta_id); + } else if (vif->type == NL80211_IFTYPE_STATION) { + vif->bss_conf.he_support = sta->he_cap.has_he; + + mvmvif->he_ru_2mhz_block = false; + if (sta->he_cap.has_he) + iwl_mvm_check_he_obss_narrow_bw_ru(hw, vif); + + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + } + + iwl_mvm_rs_rate_init(mvm, sta, mvmvif->phy_ctxt->channel->band, + false); + ret = iwl_mvm_update_sta(mvm, vif, sta); + } else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTHORIZED) { + ret = 0; + + /* we don't support TDLS during DCM */ + if (iwl_mvm_phy_ctx_count(mvm) > 1) + iwl_mvm_teardown_tdls_peers(mvm); + + if (sta->tdls) { + iwl_mvm_tdls_check_trigger(mvm, vif, sta->addr, + NL80211_TDLS_ENABLE_LINK); + } else { + /* enable beacon filtering */ + WARN_ON(iwl_mvm_enable_beacon_filter(mvm, vif, 0)); + + mvmvif->authorized = 1; + + /* + * Now that the station is authorized, i.e., keys were already + * installed, need to indicate to the FW that + * multicast data frames can be forwarded to the driver + */ + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + } + + iwl_mvm_rs_rate_init(mvm, sta, mvmvif->phy_ctxt->channel->band, + true); + } else if (old_state == IEEE80211_STA_AUTHORIZED && + new_state == IEEE80211_STA_ASSOC) { + if (!sta->tdls) { + /* Multicast data frames are no longer allowed */ + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + + /* + * Set this after the above iwl_mvm_mac_ctxt_changed() + * to avoid sending high prio again for a little time. + */ + mvmvif->authorized = 0; + + /* disable beacon filtering */ + ret = iwl_mvm_disable_beacon_filter(mvm, vif, 0); + WARN_ON(ret && + !test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status)); + } + ret = 0; + } else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTH) { + if (vif->type == NL80211_IFTYPE_AP) { + mvmvif->ap_assoc_sta_count--; + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + } else if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls) { + /* remove session protection if still running */ + iwl_mvm_stop_session_protection(mvm, vif); + } + ret = 0; + } else if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_NONE) { + ret = 0; + } else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) { + ret = iwl_mvm_rm_sta(mvm, vif, sta); + if (sta->tdls) { + iwl_mvm_recalc_tdls_state(mvm, vif, false); + iwl_mvm_tdls_check_trigger(mvm, vif, sta->addr, + NL80211_TDLS_DISABLE_LINK); + } + + if (unlikely(ret && + test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status))) + ret = 0; + } else { + ret = -EIO; + } + out_unlock: + mutex_unlock(&mvm->mutex); + + if (sta->tdls && ret == 0) { + if (old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) + ieee80211_reserve_tid(sta, IWL_MVM_TDLS_FW_TID); + else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) + ieee80211_unreserve_tid(sta, IWL_MVM_TDLS_FW_TID); + } + + return ret; +} + +static int iwl_mvm_mac_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mvm->rts_threshold = value; + + return 0; +} + +static void iwl_mvm_sta_rc_update(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u32 changed) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (changed & (IEEE80211_RC_BW_CHANGED | + IEEE80211_RC_SUPP_RATES_CHANGED | + IEEE80211_RC_NSS_CHANGED)) + iwl_mvm_rs_rate_init(mvm, sta, mvmvif->phy_ctxt->channel->band, + true); + + if (vif->type == NL80211_IFTYPE_STATION && + changed & IEEE80211_RC_NSS_CHANGED) + iwl_mvm_sf_update(mvm, vif, false); +} + +static int iwl_mvm_mac_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 ac, + const struct ieee80211_tx_queue_params *params) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->queue_params[ac] = *params; + + /* + * No need to update right away, we'll get BSS_CHANGED_QOS + * The exception is P2P_DEVICE interface which needs immediate update. + */ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + int ret; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + mutex_unlock(&mvm->mutex); + return ret; + } + return 0; +} + +static void iwl_mvm_mac_mgd_prepare_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_prep_tx_info *info) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + iwl_mvm_protect_assoc(mvm, vif, info->duration); + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_mac_mgd_complete_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_prep_tx_info *info) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* for successful cases (auth/assoc), don't cancel session protection */ + if (info->success) + return; + + mutex_lock(&mvm->mutex); + iwl_mvm_stop_session_protection(mvm, vif); + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_sched_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_scan_ies *ies) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + int ret; + + mutex_lock(&mvm->mutex); + + if (!vif->bss_conf.idle) { + ret = -EBUSY; + goto out; + } + + ret = iwl_mvm_sched_scan_start(mvm, vif, req, ies, IWL_MVM_SCAN_SCHED); + +out: + mutex_unlock(&mvm->mutex); + return ret; +} + +static int iwl_mvm_mac_sched_scan_stop(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + + /* Due to a race condition, it's possible that mac80211 asks + * us to stop a sched_scan when it's already stopped. This + * can happen, for instance, if we stopped the scan ourselves, + * called ieee80211_sched_scan_stopped() and the userspace called + * stop sched scan scan before ieee80211_sched_scan_stopped_work() + * could run. To handle this, simply return if the scan is + * not running. + */ + if (!(mvm->scan_status & IWL_MVM_SCAN_SCHED)) { + mutex_unlock(&mvm->mutex); + return 0; + } + + ret = iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, false); + mutex_unlock(&mvm->mutex); + iwl_mvm_wait_for_async_handlers(mvm); + + return ret; +} + +static int __iwl_mvm_mac_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvmsta; + struct iwl_mvm_key_pn *ptk_pn; + int keyidx = key->keyidx; + int ret, i; + u8 key_offset; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + if (!mvm->trans->trans_cfg->gen2) { + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE; + } else if (vif->type == NL80211_IFTYPE_STATION) { + key->flags |= IEEE80211_KEY_FLAG_PUT_MIC_SPACE; + } else { + IWL_DEBUG_MAC80211(mvm, "Use SW encryption for TKIP\n"); + return -EOPNOTSUPP; + } + break; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + if (!iwl_mvm_has_new_tx_api(mvm)) + key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + case WLAN_CIPHER_SUITE_BIP_GMAC_128: + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + WARN_ON_ONCE(!ieee80211_hw_check(hw, MFP_CAPABLE)); + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (vif->type == NL80211_IFTYPE_STATION) + break; + if (iwl_mvm_has_new_tx_api(mvm)) + return -EOPNOTSUPP; + /* support HW crypto on TX */ + return 0; + default: + /* currently FW supports only one optional cipher scheme */ + if (hw->n_cipher_schemes && + hw->cipher_schemes->cipher == key->cipher) + key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE; + else + return -EOPNOTSUPP; + } + + switch (cmd) { + case SET_KEY: + if (keyidx == 6 || keyidx == 7) + rcu_assign_pointer(mvmvif->bcn_prot.keys[keyidx - 6], + key); + + if ((vif->type == NL80211_IFTYPE_ADHOC || + vif->type == NL80211_IFTYPE_AP) && !sta) { + /* + * GTK on AP interface is a TX-only key, return 0; + * on IBSS they're per-station and because we're lazy + * we don't support them for RX, so do the same. + * CMAC/GMAC in AP/IBSS modes must be done in software. + */ + if (key->cipher == WLAN_CIPHER_SUITE_AES_CMAC || + key->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_128 || + key->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) { + ret = -EOPNOTSUPP; + break; + } + + if (key->cipher != WLAN_CIPHER_SUITE_GCMP && + key->cipher != WLAN_CIPHER_SUITE_GCMP_256 && + !iwl_mvm_has_new_tx_api(mvm)) { + key->hw_key_idx = STA_KEY_IDX_INVALID; + ret = 0; + break; + } + + if (!mvmvif->ap_ibss_active) { + for (i = 0; + i < ARRAY_SIZE(mvmvif->ap_early_keys); + i++) { + if (!mvmvif->ap_early_keys[i]) { + mvmvif->ap_early_keys[i] = key; + break; + } + } + + if (i >= ARRAY_SIZE(mvmvif->ap_early_keys)) + ret = -ENOSPC; + else + ret = 0; + + break; + } + } + + /* During FW restart, in order to restore the state as it was, + * don't try to reprogram keys we previously failed for. + */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + key->hw_key_idx == STA_KEY_IDX_INVALID) { + IWL_DEBUG_MAC80211(mvm, + "skip invalid idx key programming during restart\n"); + ret = 0; + break; + } + + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + sta && iwl_mvm_has_new_rx_api(mvm) && + key->flags & IEEE80211_KEY_FLAG_PAIRWISE && + (key->cipher == WLAN_CIPHER_SUITE_CCMP || + key->cipher == WLAN_CIPHER_SUITE_GCMP || + key->cipher == WLAN_CIPHER_SUITE_GCMP_256)) { + struct ieee80211_key_seq seq; + int tid, q; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + WARN_ON(rcu_access_pointer(mvmsta->ptk_pn[keyidx])); + ptk_pn = kzalloc(struct_size(ptk_pn, q, + mvm->trans->num_rx_queues), + GFP_KERNEL); + if (!ptk_pn) { + ret = -ENOMEM; + break; + } + + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + ieee80211_get_key_rx_seq(key, tid, &seq); + for (q = 0; q < mvm->trans->num_rx_queues; q++) + memcpy(ptk_pn->q[q].pn[tid], + seq.ccmp.pn, + IEEE80211_CCMP_PN_LEN); + } + + rcu_assign_pointer(mvmsta->ptk_pn[keyidx], ptk_pn); + } + + /* in HW restart reuse the index, otherwise request a new one */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + key_offset = key->hw_key_idx; + else + key_offset = STA_KEY_IDX_INVALID; + + IWL_DEBUG_MAC80211(mvm, "set hwcrypto key\n"); + ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, key_offset); + if (ret) { + IWL_WARN(mvm, "set key failed\n"); + key->hw_key_idx = STA_KEY_IDX_INVALID; + /* + * can't add key for RX, but we don't need it + * in the device for TX so still return 0, + * unless we have new TX API where we cannot + * put key material into the TX_CMD + */ + if (iwl_mvm_has_new_tx_api(mvm)) + ret = -EOPNOTSUPP; + else + ret = 0; + } + + break; + case DISABLE_KEY: + if (keyidx == 6 || keyidx == 7) + RCU_INIT_POINTER(mvmvif->bcn_prot.keys[keyidx - 6], + NULL); + + ret = -ENOENT; + for (i = 0; i < ARRAY_SIZE(mvmvif->ap_early_keys); i++) { + if (mvmvif->ap_early_keys[i] == key) { + mvmvif->ap_early_keys[i] = NULL; + ret = 0; + } + } + + /* found in pending list - don't do anything else */ + if (ret == 0) + break; + + if (key->hw_key_idx == STA_KEY_IDX_INVALID) { + ret = 0; + break; + } + + if (sta && iwl_mvm_has_new_rx_api(mvm) && + key->flags & IEEE80211_KEY_FLAG_PAIRWISE && + (key->cipher == WLAN_CIPHER_SUITE_CCMP || + key->cipher == WLAN_CIPHER_SUITE_GCMP || + key->cipher == WLAN_CIPHER_SUITE_GCMP_256)) { + mvmsta = iwl_mvm_sta_from_mac80211(sta); + ptk_pn = rcu_dereference_protected( + mvmsta->ptk_pn[keyidx], + lockdep_is_held(&mvm->mutex)); + RCU_INIT_POINTER(mvmsta->ptk_pn[keyidx], NULL); + if (ptk_pn) + kfree_rcu(ptk_pn, rcu_head); + } + + IWL_DEBUG_MAC80211(mvm, "disable hwcrypto key\n"); + ret = iwl_mvm_remove_sta_key(mvm, vif, sta, key); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int iwl_mvm_mac_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + ret = __iwl_mvm_mac_set_key(hw, cmd, vif, sta, key); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_mac_update_tkip_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, + u32 iv32, u16 *phase1key) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + if (keyconf->hw_key_idx == STA_KEY_IDX_INVALID) + return; + + iwl_mvm_update_tkip_key(mvm, vif, keyconf, sta, iv32, phase1key); +} + + +static bool iwl_mvm_rx_aux_roc(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_hs20_roc_res *resp; + int resp_len = iwl_rx_packet_payload_len(pkt); + struct iwl_mvm_time_event_data *te_data = data; + + if (WARN_ON(pkt->hdr.cmd != HOT_SPOT_CMD)) + return true; + + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + IWL_ERR(mvm, "Invalid HOT_SPOT_CMD response\n"); + return true; + } + + resp = (void *)pkt->data; + + IWL_DEBUG_TE(mvm, + "Aux ROC: Received response from ucode: status=%d uid=%d\n", + resp->status, resp->event_unique_id); + + te_data->uid = le32_to_cpu(resp->event_unique_id); + IWL_DEBUG_TE(mvm, "TIME_EVENT_CMD response - UID = 0x%x\n", + te_data->uid); + + spin_lock_bh(&mvm->time_event_lock); + list_add_tail(&te_data->list, &mvm->aux_roc_te_list); + spin_unlock_bh(&mvm->time_event_lock); + + return true; +} + +#define AUX_ROC_MIN_DURATION MSEC_TO_TU(100) +#define AUX_ROC_MIN_DELAY MSEC_TO_TU(200) +#define AUX_ROC_MAX_DELAY MSEC_TO_TU(600) +#define AUX_ROC_SAFETY_BUFFER MSEC_TO_TU(20) +#define AUX_ROC_MIN_SAFETY_BUFFER MSEC_TO_TU(10) +static int iwl_mvm_send_aux_roc_cmd(struct iwl_mvm *mvm, + struct ieee80211_channel *channel, + struct ieee80211_vif *vif, + int duration) +{ + int res; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->hs_time_event_data; + static const u16 time_event_response[] = { HOT_SPOT_CMD }; + struct iwl_notification_wait wait_time_event; + u32 dtim_interval = vif->bss_conf.dtim_period * + vif->bss_conf.beacon_int; + u32 req_dur, delay; + struct iwl_hs20_roc_req aux_roc_req = { + .action = cpu_to_le32(FW_CTXT_ACTION_ADD), + .id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(MAC_INDEX_AUX, 0)), + .sta_id_and_color = cpu_to_le32(mvm->aux_sta.sta_id), + }; + struct iwl_hs20_roc_req_tail *tail = iwl_mvm_chan_info_cmd_tail(mvm, + &aux_roc_req.channel_info); + u16 len = sizeof(aux_roc_req) - iwl_mvm_chan_info_padding(mvm); + + /* Set the channel info data */ + iwl_mvm_set_chan_info(mvm, &aux_roc_req.channel_info, channel->hw_value, + iwl_mvm_phy_band_from_nl80211(channel->band), + PHY_VHT_CHANNEL_MODE20, + 0); + + /* Set the time and duration */ + tail->apply_time = cpu_to_le32(iwl_mvm_get_systime(mvm)); + + delay = AUX_ROC_MIN_DELAY; + req_dur = MSEC_TO_TU(duration); + + /* + * If we are associated we want the delay time to be at least one + * dtim interval so that the FW can wait until after the DTIM and + * then start the time event, this will potentially allow us to + * remain off-channel for the max duration. + * Since we want to use almost a whole dtim interval we would also + * like the delay to be for 2-3 dtim intervals, in case there are + * other time events with higher priority. + */ + if (vif->bss_conf.assoc) { + delay = min_t(u32, dtim_interval * 3, AUX_ROC_MAX_DELAY); + /* We cannot remain off-channel longer than the DTIM interval */ + if (dtim_interval <= req_dur) { + req_dur = dtim_interval - AUX_ROC_SAFETY_BUFFER; + if (req_dur <= AUX_ROC_MIN_DURATION) + req_dur = dtim_interval - + AUX_ROC_MIN_SAFETY_BUFFER; + } + } + + tail->duration = cpu_to_le32(req_dur); + tail->apply_time_max_delay = cpu_to_le32(delay); + + IWL_DEBUG_TE(mvm, + "ROC: Requesting to remain on channel %u for %ums\n", + channel->hw_value, req_dur); + IWL_DEBUG_TE(mvm, + "\t(requested = %ums, max_delay = %ums, dtim_interval = %ums)\n", + duration, delay, dtim_interval); + + /* Set the node address */ + memcpy(tail->node_addr, vif->addr, ETH_ALEN); + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + + if (WARN_ON(te_data->id == HOT_SPOT_CMD)) { + spin_unlock_bh(&mvm->time_event_lock); + return -EIO; + } + + te_data->vif = vif; + te_data->duration = duration; + te_data->id = HOT_SPOT_CMD; + + spin_unlock_bh(&mvm->time_event_lock); + + /* + * Use a notification wait, which really just processes the + * command response and doesn't wait for anything, in order + * to be able to process the response and get the UID inside + * the RX path. Using CMD_WANT_SKB doesn't work because it + * stores the buffer and then wakes up this thread, by which + * time another notification (that the time event started) + * might already be processed unsuccessfully. + */ + iwl_init_notification_wait(&mvm->notif_wait, &wait_time_event, + time_event_response, + ARRAY_SIZE(time_event_response), + iwl_mvm_rx_aux_roc, te_data); + + res = iwl_mvm_send_cmd_pdu(mvm, HOT_SPOT_CMD, 0, len, + &aux_roc_req); + + if (res) { + IWL_ERR(mvm, "Couldn't send HOT_SPOT_CMD: %d\n", res); + iwl_remove_notification(&mvm->notif_wait, &wait_time_event); + goto out_clear_te; + } + + /* No need to wait for anything, so just pass 1 (0 isn't valid) */ + res = iwl_wait_notification(&mvm->notif_wait, &wait_time_event, 1); + /* should never fail */ + WARN_ON_ONCE(res); + + if (res) { + out_clear_te: + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + + return res; +} + +static int iwl_mvm_roc(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel *channel, + int duration, + enum ieee80211_roc_type type) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct cfg80211_chan_def chandef; + struct iwl_mvm_phy_ctxt *phy_ctxt; + bool band_change_removal; + int ret, i; + + IWL_DEBUG_MAC80211(mvm, "enter (%d, %d, %d)\n", channel->hw_value, + duration, type); + + /* + * Flush the done work, just in case it's still pending, so that + * the work it does can complete and we can accept new frames. + */ + flush_work(&mvm->roc_done_wk); + + mutex_lock(&mvm->mutex); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_HOTSPOT_SUPPORT)) { + /* Use aux roc framework (HS20) */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + ADD_STA, 0) >= 12) { + u32 lmac_id; + + lmac_id = iwl_mvm_get_lmac_id(mvm->fw, + channel->band); + ret = iwl_mvm_add_aux_sta(mvm, lmac_id); + if (WARN(ret, + "Failed to allocate aux station")) + goto out_unlock; + } + ret = iwl_mvm_send_aux_roc_cmd(mvm, channel, + vif, duration); + goto out_unlock; + } + IWL_ERR(mvm, "hotspot not supported\n"); + ret = -EINVAL; + goto out_unlock; + case NL80211_IFTYPE_P2P_DEVICE: + /* handle below */ + break; + default: + IWL_ERR(mvm, "vif isn't P2P_DEVICE: %d\n", vif->type); + ret = -EINVAL; + goto out_unlock; + } + + for (i = 0; i < NUM_PHY_CTX; i++) { + phy_ctxt = &mvm->phy_ctxts[i]; + if (phy_ctxt->ref == 0 || mvmvif->phy_ctxt == phy_ctxt) + continue; + + if (phy_ctxt->ref && channel == phy_ctxt->channel) { + /* + * Unbind the P2P_DEVICE from the current PHY context, + * and if the PHY context is not used remove it. + */ + ret = iwl_mvm_binding_remove_vif(mvm, vif); + if (WARN(ret, "Failed unbinding P2P_DEVICE\n")) + goto out_unlock; + + iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt); + + /* Bind the P2P_DEVICE to the current PHY Context */ + mvmvif->phy_ctxt = phy_ctxt; + + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (WARN(ret, "Failed binding P2P_DEVICE\n")) + goto out_unlock; + + iwl_mvm_phy_ctxt_ref(mvm, mvmvif->phy_ctxt); + goto schedule_time_event; + } + } + + /* Need to update the PHY context only if the ROC channel changed */ + if (channel == mvmvif->phy_ctxt->channel) + goto schedule_time_event; + + cfg80211_chandef_create(&chandef, channel, NL80211_CHAN_NO_HT); + + /* + * Check if the remain-on-channel is on a different band and that + * requires context removal, see iwl_mvm_phy_ctxt_changed(). If + * so, we'll need to release and then re-configure here, since we + * must not remove a PHY context that's part of a binding. + */ + band_change_removal = + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT) && + mvmvif->phy_ctxt->channel->band != chandef.chan->band; + + if (mvmvif->phy_ctxt->ref == 1 && !band_change_removal) { + /* + * Change the PHY context configuration as it is currently + * referenced only by the P2P Device MAC (and we can modify it) + */ + ret = iwl_mvm_phy_ctxt_changed(mvm, mvmvif->phy_ctxt, + &chandef, 1, 1); + if (ret) + goto out_unlock; + } else { + /* + * The PHY context is shared with other MACs (or we're trying to + * switch bands), so remove the P2P Device from the binding, + * allocate an new PHY context and create a new binding. + */ + phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm); + if (!phy_ctxt) { + ret = -ENOSPC; + goto out_unlock; + } + + ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &chandef, + 1, 1); + if (ret) { + IWL_ERR(mvm, "Failed to change PHY context\n"); + goto out_unlock; + } + + /* Unbind the P2P_DEVICE from the current PHY context */ + ret = iwl_mvm_binding_remove_vif(mvm, vif); + if (WARN(ret, "Failed unbinding P2P_DEVICE\n")) + goto out_unlock; + + iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt); + + /* Bind the P2P_DEVICE to the new allocated PHY context */ + mvmvif->phy_ctxt = phy_ctxt; + + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (WARN(ret, "Failed binding P2P_DEVICE\n")) + goto out_unlock; + + iwl_mvm_phy_ctxt_ref(mvm, mvmvif->phy_ctxt); + } + +schedule_time_event: + /* Schedule the time events */ + ret = iwl_mvm_start_p2p_roc(mvm, vif, duration, type); + +out_unlock: + mutex_unlock(&mvm->mutex); + IWL_DEBUG_MAC80211(mvm, "leave\n"); + return ret; +} + +static int iwl_mvm_cancel_roc(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + IWL_DEBUG_MAC80211(mvm, "enter\n"); + + mutex_lock(&mvm->mutex); + iwl_mvm_stop_roc(mvm, vif); + mutex_unlock(&mvm->mutex); + + IWL_DEBUG_MAC80211(mvm, "leave\n"); + return 0; +} + +struct iwl_mvm_ftm_responder_iter_data { + bool responder; + struct ieee80211_chanctx_conf *ctx; +}; + +static void iwl_mvm_ftm_responder_chanctx_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_ftm_responder_iter_data *data = _data; + + if (rcu_access_pointer(vif->chanctx_conf) == data->ctx && + vif->type == NL80211_IFTYPE_AP && vif->bss_conf.ftmr_params) + data->responder = true; +} + +static bool iwl_mvm_is_ftm_responder_chanctx(struct iwl_mvm *mvm, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm_ftm_responder_iter_data data = { + .responder = false, + .ctx = ctx, + }; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_ftm_responder_chanctx_iter, + &data); + return data.responder; +} + +static int __iwl_mvm_add_chanctx(struct iwl_mvm *mvm, + struct ieee80211_chanctx_conf *ctx) +{ + u16 *phy_ctxt_id = (u16 *)ctx->drv_priv; + struct iwl_mvm_phy_ctxt *phy_ctxt; + bool responder = iwl_mvm_is_ftm_responder_chanctx(mvm, ctx); + struct cfg80211_chan_def *def = responder ? &ctx->def : &ctx->min_def; + int ret; + + lockdep_assert_held(&mvm->mutex); + + IWL_DEBUG_MAC80211(mvm, "Add channel context\n"); + + phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm); + if (!phy_ctxt) { + ret = -ENOSPC; + goto out; + } + + ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, def, + ctx->rx_chains_static, + ctx->rx_chains_dynamic); + if (ret) { + IWL_ERR(mvm, "Failed to add PHY context\n"); + goto out; + } + + iwl_mvm_phy_ctxt_ref(mvm, phy_ctxt); + *phy_ctxt_id = phy_ctxt->id; +out: + return ret; +} + +static int iwl_mvm_add_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + ret = __iwl_mvm_add_chanctx(mvm, ctx); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void __iwl_mvm_remove_chanctx(struct iwl_mvm *mvm, + struct ieee80211_chanctx_conf *ctx) +{ + u16 *phy_ctxt_id = (u16 *)ctx->drv_priv; + struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id]; + + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_phy_ctxt_unref(mvm, phy_ctxt); +} + +static void iwl_mvm_remove_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + __iwl_mvm_remove_chanctx(mvm, ctx); + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_change_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx, + u32 changed) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + u16 *phy_ctxt_id = (u16 *)ctx->drv_priv; + struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id]; + bool responder = iwl_mvm_is_ftm_responder_chanctx(mvm, ctx); + struct cfg80211_chan_def *def = responder ? &ctx->def : &ctx->min_def; + + if (WARN_ONCE((phy_ctxt->ref > 1) && + (changed & ~(IEEE80211_CHANCTX_CHANGE_WIDTH | + IEEE80211_CHANCTX_CHANGE_RX_CHAINS | + IEEE80211_CHANCTX_CHANGE_RADAR | + IEEE80211_CHANCTX_CHANGE_MIN_WIDTH)), + "Cannot change PHY. Ref=%d, changed=0x%X\n", + phy_ctxt->ref, changed)) + return; + + mutex_lock(&mvm->mutex); + + /* we are only changing the min_width, may be a noop */ + if (changed == IEEE80211_CHANCTX_CHANGE_MIN_WIDTH) { + if (phy_ctxt->width == def->width) + goto out_unlock; + + /* we are just toggling between 20_NOHT and 20 */ + if (phy_ctxt->width <= NL80211_CHAN_WIDTH_20 && + def->width <= NL80211_CHAN_WIDTH_20) + goto out_unlock; + } + + iwl_mvm_bt_coex_vif_change(mvm); + iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, def, + ctx->rx_chains_static, + ctx->rx_chains_dynamic); + +out_unlock: + mutex_unlock(&mvm->mutex); +} + +static int __iwl_mvm_assign_vif_chanctx(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx, + bool switching_chanctx) +{ + u16 *phy_ctxt_id = (u16 *)ctx->drv_priv; + struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id]; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + lockdep_assert_held(&mvm->mutex); + + mvmvif->phy_ctxt = phy_ctxt; + + switch (vif->type) { + case NL80211_IFTYPE_AP: + /* only needed if we're switching chanctx (i.e. during CSA) */ + if (switching_chanctx) { + mvmvif->ap_ibss_active = true; + break; + } + fallthrough; + case NL80211_IFTYPE_ADHOC: + /* + * The AP binding flow is handled as part of the start_ap flow + * (in bss_info_changed), similarly for IBSS. + */ + ret = 0; + goto out; + case NL80211_IFTYPE_STATION: + mvmvif->csa_bcn_pending = false; + break; + case NL80211_IFTYPE_MONITOR: + /* always disable PS when a monitor interface is active */ + mvmvif->ps_disabled = true; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (ret) + goto out; + + /* + * Power state must be updated before quotas, + * otherwise fw will complain. + */ + iwl_mvm_power_update_mac(mvm); + + /* Setting the quota at this stage is only required for monitor + * interfaces. For the other types, the bss_info changed flow + * will handle quota settings. + */ + if (vif->type == NL80211_IFTYPE_MONITOR) { + mvmvif->monitor_active = true; + ret = iwl_mvm_update_quotas(mvm, false, NULL); + if (ret) + goto out_remove_binding; + + ret = iwl_mvm_add_snif_sta(mvm, vif); + if (ret) + goto out_remove_binding; + + } + + /* Handle binding during CSA */ + if (vif->type == NL80211_IFTYPE_AP) { + iwl_mvm_update_quotas(mvm, false, NULL); + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + } + + if (switching_chanctx && vif->type == NL80211_IFTYPE_STATION) { + mvmvif->csa_bcn_pending = true; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) { + u32 duration = 3 * vif->bss_conf.beacon_int; + + /* Protect the session to make sure we hear the first + * beacon on the new channel. + */ + iwl_mvm_protect_session(mvm, vif, duration, duration, + vif->bss_conf.beacon_int / 2, + true); + } + + iwl_mvm_update_quotas(mvm, false, NULL); + } + + goto out; + +out_remove_binding: + iwl_mvm_binding_remove_vif(mvm, vif); + iwl_mvm_power_update_mac(mvm); +out: + if (ret) + mvmvif->phy_ctxt = NULL; + return ret; +} +static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + ret = __iwl_mvm_assign_vif_chanctx(mvm, vif, ctx, false); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void __iwl_mvm_unassign_vif_chanctx(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx, + bool switching_chanctx) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_vif *disabled_vif = NULL; + + lockdep_assert_held(&mvm->mutex); + iwl_mvm_remove_time_event(mvm, mvmvif, &mvmvif->time_event_data); + + switch (vif->type) { + case NL80211_IFTYPE_ADHOC: + goto out; + case NL80211_IFTYPE_MONITOR: + mvmvif->monitor_active = false; + mvmvif->ps_disabled = false; + iwl_mvm_rm_snif_sta(mvm, vif); + break; + case NL80211_IFTYPE_AP: + /* This part is triggered only during CSA */ + if (!switching_chanctx || !mvmvif->ap_ibss_active) + goto out; + + mvmvif->csa_countdown = false; + + /* Set CS bit on all the stations */ + iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, true); + + /* Save blocked iface, the timeout is set on the next beacon */ + rcu_assign_pointer(mvm->csa_tx_blocked_vif, vif); + + mvmvif->ap_ibss_active = false; + break; + case NL80211_IFTYPE_STATION: + if (!switching_chanctx) + break; + + disabled_vif = vif; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) + iwl_mvm_mac_ctxt_changed(mvm, vif, true, NULL); + break; + default: + break; + } + + iwl_mvm_update_quotas(mvm, false, disabled_vif); + iwl_mvm_binding_remove_vif(mvm, vif); + +out: + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD) && + switching_chanctx) + return; + mvmvif->phy_ctxt = NULL; + iwl_mvm_power_update_mac(mvm); +} + +static void iwl_mvm_unassign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + __iwl_mvm_unassign_vif_chanctx(mvm, vif, ctx, false); + mutex_unlock(&mvm->mutex); +} + +static int +iwl_mvm_switch_vif_chanctx_swap(struct iwl_mvm *mvm, + struct ieee80211_vif_chanctx_switch *vifs) +{ + int ret; + + mutex_lock(&mvm->mutex); + __iwl_mvm_unassign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, true); + __iwl_mvm_remove_chanctx(mvm, vifs[0].old_ctx); + + ret = __iwl_mvm_add_chanctx(mvm, vifs[0].new_ctx); + if (ret) { + IWL_ERR(mvm, "failed to add new_ctx during channel switch\n"); + goto out_reassign; + } + + ret = __iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].new_ctx, + true); + if (ret) { + IWL_ERR(mvm, + "failed to assign new_ctx during channel switch\n"); + goto out_remove; + } + + /* we don't support TDLS during DCM - can be caused by channel switch */ + if (iwl_mvm_phy_ctx_count(mvm) > 1) + iwl_mvm_teardown_tdls_peers(mvm); + + goto out; + +out_remove: + __iwl_mvm_remove_chanctx(mvm, vifs[0].new_ctx); + +out_reassign: + if (__iwl_mvm_add_chanctx(mvm, vifs[0].old_ctx)) { + IWL_ERR(mvm, "failed to add old_ctx back after failure.\n"); + goto out_restart; + } + + if (__iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, + true)) { + IWL_ERR(mvm, "failed to reassign old_ctx after failure.\n"); + goto out_restart; + } + + goto out; + +out_restart: + /* things keep failing, better restart the hw */ + iwl_mvm_nic_restart(mvm, false); + +out: + mutex_unlock(&mvm->mutex); + + return ret; +} + +static int +iwl_mvm_switch_vif_chanctx_reassign(struct iwl_mvm *mvm, + struct ieee80211_vif_chanctx_switch *vifs) +{ + int ret; + + mutex_lock(&mvm->mutex); + __iwl_mvm_unassign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, true); + + ret = __iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].new_ctx, + true); + if (ret) { + IWL_ERR(mvm, + "failed to assign new_ctx during channel switch\n"); + goto out_reassign; + } + + goto out; + +out_reassign: + if (__iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, + true)) { + IWL_ERR(mvm, "failed to reassign old_ctx after failure.\n"); + goto out_restart; + } + + goto out; + +out_restart: + /* things keep failing, better restart the hw */ + iwl_mvm_nic_restart(mvm, false); + +out: + mutex_unlock(&mvm->mutex); + + return ret; +} + +static int iwl_mvm_switch_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif_chanctx_switch *vifs, + int n_vifs, + enum ieee80211_chanctx_switch_mode mode) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + /* we only support a single-vif right now */ + if (n_vifs > 1) + return -EOPNOTSUPP; + + switch (mode) { + case CHANCTX_SWMODE_SWAP_CONTEXTS: + ret = iwl_mvm_switch_vif_chanctx_swap(mvm, vifs); + break; + case CHANCTX_SWMODE_REASSIGN_VIF: + ret = iwl_mvm_switch_vif_chanctx_reassign(mvm, vifs); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static int iwl_mvm_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + return mvm->ibss_manager; +} + +static int iwl_mvm_set_tim(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + bool set) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + if (!mvm_sta || !mvm_sta->vif) { + IWL_ERR(mvm, "Station is not associated to a vif\n"); + return -EINVAL; + } + + return iwl_mvm_mac_ctxt_beacon_changed(mvm, mvm_sta->vif); +} + +#ifdef CONFIG_NL80211_TESTMODE +static const struct nla_policy iwl_mvm_tm_policy[IWL_MVM_TM_ATTR_MAX + 1] = { + [IWL_MVM_TM_ATTR_CMD] = { .type = NLA_U32 }, + [IWL_MVM_TM_ATTR_NOA_DURATION] = { .type = NLA_U32 }, + [IWL_MVM_TM_ATTR_BEACON_FILTER_STATE] = { .type = NLA_U32 }, +}; + +static int __iwl_mvm_mac_testmode_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + void *data, int len) +{ + struct nlattr *tb[IWL_MVM_TM_ATTR_MAX + 1]; + int err; + u32 noa_duration; + + err = nla_parse_deprecated(tb, IWL_MVM_TM_ATTR_MAX, data, len, + iwl_mvm_tm_policy, NULL); + if (err) + return err; + + if (!tb[IWL_MVM_TM_ATTR_CMD]) + return -EINVAL; + + switch (nla_get_u32(tb[IWL_MVM_TM_ATTR_CMD])) { + case IWL_MVM_TM_CMD_SET_NOA: + if (!vif || vif->type != NL80211_IFTYPE_AP || !vif->p2p || + !vif->bss_conf.enable_beacon || + !tb[IWL_MVM_TM_ATTR_NOA_DURATION]) + return -EINVAL; + + noa_duration = nla_get_u32(tb[IWL_MVM_TM_ATTR_NOA_DURATION]); + if (noa_duration >= vif->bss_conf.beacon_int) + return -EINVAL; + + mvm->noa_duration = noa_duration; + mvm->noa_vif = vif; + + return iwl_mvm_update_quotas(mvm, true, NULL); + case IWL_MVM_TM_CMD_SET_BEACON_FILTER: + /* must be associated client vif - ignore authorized */ + if (!vif || vif->type != NL80211_IFTYPE_STATION || + !vif->bss_conf.assoc || !vif->bss_conf.dtim_period || + !tb[IWL_MVM_TM_ATTR_BEACON_FILTER_STATE]) + return -EINVAL; + + if (nla_get_u32(tb[IWL_MVM_TM_ATTR_BEACON_FILTER_STATE])) + return iwl_mvm_enable_beacon_filter(mvm, vif, 0); + return iwl_mvm_disable_beacon_filter(mvm, vif, 0); + } + + return -EOPNOTSUPP; +} + +static int iwl_mvm_mac_testmode_cmd(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + void *data, int len) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int err; + + mutex_lock(&mvm->mutex); + err = __iwl_mvm_mac_testmode_cmd(mvm, vif, data, len); + mutex_unlock(&mvm->mutex); + + return err; +} +#endif + +static void iwl_mvm_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + /* By implementing this operation, we prevent mac80211 from + * starting its own channel switch timer, so that we can call + * ieee80211_chswitch_done() ourselves at the right time + * (which is when the absence time event starts). + */ + + IWL_DEBUG_MAC80211(IWL_MAC80211_GET_MVM(hw), + "dummy channel switch op\n"); +} + +static int iwl_mvm_schedule_client_csa(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_chan_switch_te_cmd cmd = { + .mac_id = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_ADD), + .tsf = cpu_to_le32(chsw->timestamp), + .cs_count = chsw->count, + .cs_mode = chsw->block_tx, + }; + + lockdep_assert_held(&mvm->mutex); + + if (chsw->delay) + cmd.cs_delayed_bcn_count = + DIV_ROUND_UP(chsw->delay, vif->bss_conf.beacon_int); + + return iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, + CHANNEL_SWITCH_TIME_EVENT_CMD), + 0, sizeof(cmd), &cmd); +} + +static int iwl_mvm_old_pre_chan_sw_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u32 apply_time; + + /* Schedule the time event to a bit before beacon 1, + * to make sure we're in the new channel when the + * GO/AP arrives. In case count <= 1 immediately schedule the + * TE (this might result with some packet loss or connection + * loss). + */ + if (chsw->count <= 1) + apply_time = 0; + else + apply_time = chsw->device_timestamp + + ((vif->bss_conf.beacon_int * (chsw->count - 1) - + IWL_MVM_CHANNEL_SWITCH_TIME_CLIENT) * 1024); + + if (chsw->block_tx) + iwl_mvm_csa_client_absent(mvm, vif); + + if (mvmvif->bf_data.bf_enabled) { + int ret = iwl_mvm_disable_beacon_filter(mvm, vif, 0); + + if (ret) + return ret; + } + + iwl_mvm_schedule_csa_period(mvm, vif, vif->bss_conf.beacon_int, + apply_time); + + return 0; +} + +#define IWL_MAX_CSA_BLOCK_TX 1500 +static int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct ieee80211_vif *csa_vif; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + mutex_lock(&mvm->mutex); + + mvmvif->csa_failed = false; + + IWL_DEBUG_MAC80211(mvm, "pre CSA to freq %d\n", + chsw->chandef.center_freq1); + + iwl_fw_dbg_trigger_simple_stop(&mvm->fwrt, + ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_CHANNEL_SWITCH); + + switch (vif->type) { + case NL80211_IFTYPE_AP: + csa_vif = + rcu_dereference_protected(mvm->csa_vif, + lockdep_is_held(&mvm->mutex)); + if (WARN_ONCE(csa_vif && csa_vif->csa_active, + "Another CSA is already in progress")) { + ret = -EBUSY; + goto out_unlock; + } + + /* we still didn't unblock tx. prevent new CS meanwhile */ + if (rcu_dereference_protected(mvm->csa_tx_blocked_vif, + lockdep_is_held(&mvm->mutex))) { + ret = -EBUSY; + goto out_unlock; + } + + rcu_assign_pointer(mvm->csa_vif, vif); + + if (WARN_ONCE(mvmvif->csa_countdown, + "Previous CSA countdown didn't complete")) { + ret = -EBUSY; + goto out_unlock; + } + + mvmvif->csa_target_freq = chsw->chandef.chan->center_freq; + + break; + case NL80211_IFTYPE_STATION: + /* + * We haven't configured the firmware to be associated yet since + * we don't know the dtim period. In this case, the firmware can't + * track the beacons. + */ + if (!vif->bss_conf.assoc || !vif->bss_conf.dtim_period) { + ret = -EBUSY; + goto out_unlock; + } + + if (chsw->delay > IWL_MAX_CSA_BLOCK_TX) + schedule_delayed_work(&mvmvif->csa_work, 0); + + if (chsw->block_tx) { + /* + * In case of undetermined / long time with immediate + * quiet monitor status to gracefully disconnect + */ + if (!chsw->count || + chsw->count * vif->bss_conf.beacon_int > + IWL_MAX_CSA_BLOCK_TX) + schedule_delayed_work(&mvmvif->csa_work, + msecs_to_jiffies(IWL_MAX_CSA_BLOCK_TX)); + } + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) { + ret = iwl_mvm_old_pre_chan_sw_sta(mvm, vif, chsw); + if (ret) + goto out_unlock; + } else { + iwl_mvm_schedule_client_csa(mvm, vif, chsw); + } + + mvmvif->csa_count = chsw->count; + mvmvif->csa_misbehave = false; + break; + default: + break; + } + + mvmvif->ps_disabled = true; + + ret = iwl_mvm_power_update_ps(mvm); + if (ret) + goto out_unlock; + + /* we won't be on this channel any longer */ + iwl_mvm_teardown_tdls_peers(mvm); + +out_unlock: + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_channel_switch_rx_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_chan_switch_te_cmd cmd = { + .mac_id = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_MODIFY), + .tsf = cpu_to_le32(chsw->timestamp), + .cs_count = chsw->count, + .cs_mode = chsw->block_tx, + }; + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_CS_MODIFY)) + return; + + IWL_DEBUG_MAC80211(mvm, "Modify CSA on mac %d count = %d (old %d) mode = %d\n", + mvmvif->id, chsw->count, mvmvif->csa_count, chsw->block_tx); + + if (chsw->count >= mvmvif->csa_count && chsw->block_tx) { + if (mvmvif->csa_misbehave) { + /* Second time, give up on this AP*/ + iwl_mvm_abort_channel_switch(hw, vif); + ieee80211_chswitch_done(vif, false); + mvmvif->csa_misbehave = false; + return; + } + mvmvif->csa_misbehave = true; + } + mvmvif->csa_count = chsw->count; + + mutex_lock(&mvm->mutex); + if (mvmvif->csa_failed) + goto out_unlock; + + WARN_ON(iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, + CHANNEL_SWITCH_TIME_EVENT_CMD), + 0, sizeof(cmd), &cmd)); +out_unlock: + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_flush_no_vif(struct iwl_mvm *mvm, u32 queues, bool drop) +{ + int i; + + if (!iwl_mvm_has_new_tx_api(mvm)) { + if (drop) { + mutex_lock(&mvm->mutex); + iwl_mvm_flush_tx_path(mvm, + iwl_mvm_flushable_queues(mvm) & queues); + mutex_unlock(&mvm->mutex); + } else { + iwl_trans_wait_tx_queues_empty(mvm->trans, queues); + } + return; + } + + mutex_lock(&mvm->mutex); + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + struct ieee80211_sta *sta; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta)) + continue; + + if (drop) + iwl_mvm_flush_sta_tids(mvm, i, 0xFFFF); + else + iwl_mvm_wait_sta_queues_empty(mvm, + iwl_mvm_sta_from_mac80211(sta)); + } + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_mac_flush(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u32 queues, bool drop) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif; + struct iwl_mvm_sta *mvmsta; + struct ieee80211_sta *sta; + int i; + u32 msk = 0; + + if (!vif) { + iwl_mvm_flush_no_vif(mvm, queues, drop); + return; + } + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + /* Make sure we're done with the deferred traffic before flushing */ + flush_work(&mvm->add_stream_wk); + + mutex_lock(&mvm->mutex); + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* flush the AP-station and all TDLS peers */ + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta)) + continue; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + if (mvmsta->vif != vif) + continue; + + /* make sure only TDLS peers or the AP are flushed */ + WARN_ON(i != mvmvif->ap_sta_id && !sta->tdls); + + if (drop) { + if (iwl_mvm_flush_sta(mvm, mvmsta, false)) + IWL_ERR(mvm, "flush request fail\n"); + } else { + msk |= mvmsta->tfd_queue_msk; + if (iwl_mvm_has_new_tx_api(mvm)) + iwl_mvm_wait_sta_queues_empty(mvm, mvmsta); + } + } + + mutex_unlock(&mvm->mutex); + + /* this can take a while, and we may need/want other operations + * to succeed while doing this, so do it without the mutex held + */ + if (!drop && !iwl_mvm_has_new_tx_api(mvm)) + iwl_trans_wait_tx_queues_empty(mvm->trans, msk); +} + +static int iwl_mvm_mac_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + memset(survey, 0, sizeof(*survey)); + + /* only support global statistics right now */ + if (idx != 0) + return -ENOENT; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_RADIO_BEACON_STATS)) + return -ENOENT; + + mutex_lock(&mvm->mutex); + + if (iwl_mvm_firmware_running(mvm)) { + ret = iwl_mvm_request_statistics(mvm, false); + if (ret) + goto out; + } + + survey->filled = SURVEY_INFO_TIME | + SURVEY_INFO_TIME_RX | + SURVEY_INFO_TIME_TX | + SURVEY_INFO_TIME_SCAN; + survey->time = mvm->accu_radio_stats.on_time_rf + + mvm->radio_stats.on_time_rf; + do_div(survey->time, USEC_PER_MSEC); + + survey->time_rx = mvm->accu_radio_stats.rx_time + + mvm->radio_stats.rx_time; + do_div(survey->time_rx, USEC_PER_MSEC); + + survey->time_tx = mvm->accu_radio_stats.tx_time + + mvm->radio_stats.tx_time; + do_div(survey->time_tx, USEC_PER_MSEC); + + survey->time_scan = mvm->accu_radio_stats.on_time_scan + + mvm->radio_stats.on_time_scan; + do_div(survey->time_scan, USEC_PER_MSEC); + + ret = 0; + out: + mutex_unlock(&mvm->mutex); + return ret; +} + +static void iwl_mvm_set_sta_rate(u32 rate_n_flags, struct rate_info *rinfo) +{ + u32 format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + + switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { + case RATE_MCS_CHAN_WIDTH_20: + rinfo->bw = RATE_INFO_BW_20; + break; + case RATE_MCS_CHAN_WIDTH_40: + rinfo->bw = RATE_INFO_BW_40; + break; + case RATE_MCS_CHAN_WIDTH_80: + rinfo->bw = RATE_INFO_BW_80; + break; + case RATE_MCS_CHAN_WIDTH_160: + rinfo->bw = RATE_INFO_BW_160; + break; + } + + if (format == RATE_MCS_CCK_MSK || + format == RATE_MCS_LEGACY_OFDM_MSK) { + int rate = u32_get_bits(rate_n_flags, RATE_LEGACY_RATE_MSK); + + /* add the offset needed to get to the legacy ofdm indices */ + if (format == RATE_MCS_LEGACY_OFDM_MSK) + rate += IWL_FIRST_OFDM_RATE; + + switch (rate) { + case IWL_RATE_1M_INDEX: + rinfo->legacy = 10; + break; + case IWL_RATE_2M_INDEX: + rinfo->legacy = 20; + break; + case IWL_RATE_5M_INDEX: + rinfo->legacy = 55; + break; + case IWL_RATE_11M_INDEX: + rinfo->legacy = 110; + break; + case IWL_RATE_6M_INDEX: + rinfo->legacy = 60; + break; + case IWL_RATE_9M_INDEX: + rinfo->legacy = 90; + break; + case IWL_RATE_12M_INDEX: + rinfo->legacy = 120; + break; + case IWL_RATE_18M_INDEX: + rinfo->legacy = 180; + break; + case IWL_RATE_24M_INDEX: + rinfo->legacy = 240; + break; + case IWL_RATE_36M_INDEX: + rinfo->legacy = 360; + break; + case IWL_RATE_48M_INDEX: + rinfo->legacy = 480; + break; + case IWL_RATE_54M_INDEX: + rinfo->legacy = 540; + } + return; + } + + rinfo->nss = u32_get_bits(rate_n_flags, + RATE_MCS_NSS_MSK) + 1; + rinfo->mcs = format == RATE_MCS_HT_MSK ? + RATE_HT_MCS_INDEX(rate_n_flags) : + u32_get_bits(rate_n_flags, RATE_MCS_CODE_MSK); + + if (format == RATE_MCS_HE_MSK) { + u32 gi_ltf = u32_get_bits(rate_n_flags, + RATE_MCS_HE_GI_LTF_MSK); + + rinfo->flags |= RATE_INFO_FLAGS_HE_MCS; + + if (rate_n_flags & RATE_MCS_HE_106T_MSK) { + rinfo->bw = RATE_INFO_BW_HE_RU; + rinfo->he_ru_alloc = NL80211_RATE_INFO_HE_RU_ALLOC_106; + } + + switch (rate_n_flags & RATE_MCS_HE_TYPE_MSK) { + case RATE_MCS_HE_TYPE_SU: + case RATE_MCS_HE_TYPE_EXT_SU: + if (gi_ltf == 0 || gi_ltf == 1) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + else if (gi_ltf == 2) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else if (gi_ltf == 3) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + else + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + break; + case RATE_MCS_HE_TYPE_MU: + if (gi_ltf == 0 || gi_ltf == 1) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + else if (gi_ltf == 2) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + break; + case RATE_MCS_HE_TYPE_TRIG: + if (gi_ltf == 0 || gi_ltf == 1) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + break; + } + + if (rate_n_flags & RATE_HE_DUAL_CARRIER_MODE_MSK) + rinfo->he_dcm = 1; + return; + } + + if (rate_n_flags & RATE_MCS_SGI_MSK) + rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI; + + if (format == RATE_MCS_HT_MSK) { + rinfo->flags |= RATE_INFO_FLAGS_MCS; + + } else if (format == RATE_MCS_VHT_MSK) { + rinfo->flags |= RATE_INFO_FLAGS_VHT_MCS; + } + +} + +static void iwl_mvm_mac_sta_statistics(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct station_info *sinfo) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (mvmsta->avg_energy) { + sinfo->signal_avg = -(s8)mvmsta->avg_energy; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG); + } + + if (iwl_mvm_has_tlc_offload(mvm)) { + struct iwl_lq_sta_rs_fw *lq_sta = &mvmsta->lq_sta.rs_fw; + + iwl_mvm_set_sta_rate(lq_sta->last_rate_n_flags, &sinfo->txrate); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); + } + + /* if beacon filtering isn't on mac80211 does it anyway */ + if (!(vif->driver_flags & IEEE80211_VIF_BEACON_FILTER)) + return; + + if (!vif->bss_conf.assoc) + return; + + mutex_lock(&mvm->mutex); + + if (mvmvif->ap_sta_id != mvmsta->sta_id) + goto unlock; + + if (iwl_mvm_request_statistics(mvm, false)) + goto unlock; + + sinfo->rx_beacon = mvmvif->beacon_stats.num_beacons + + mvmvif->beacon_stats.accu_num_beacons; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_RX); + if (mvmvif->beacon_stats.avg_signal) { + /* firmware only reports a value after RXing a few beacons */ + sinfo->rx_beacon_signal_avg = mvmvif->beacon_stats.avg_signal; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_SIGNAL_AVG); + } + unlock: + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_event_mlme_callback_ini(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_mlme_event *mlme) +{ + if ((mlme->data == ASSOC_EVENT || mlme->data == AUTH_EVENT) && + (mlme->status == MLME_DENIED || mlme->status == MLME_TIMEOUT)) { + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_ASSOC_FAILED, + NULL); + return; + } + + if (mlme->data == DEAUTH_RX_EVENT || mlme->data == DEAUTH_TX_EVENT) { + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_DEASSOC, + NULL); + return; + } +} + +static void iwl_mvm_event_mlme_callback(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_event *event) +{ +#define CHECK_MLME_TRIGGER(_cnt, _fmt...) \ + do { \ + if ((trig_mlme->_cnt) && --(trig_mlme->_cnt)) \ + break; \ + iwl_fw_dbg_collect_trig(&(mvm)->fwrt, trig, _fmt); \ + } while (0) + + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_mlme *trig_mlme; + + if (iwl_trans_dbg_ini_valid(mvm->trans)) { + iwl_mvm_event_mlme_callback_ini(mvm, vif, &event->u.mlme); + return; + } + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_MLME); + if (!trig) + return; + + trig_mlme = (void *)trig->data; + + if (event->u.mlme.data == ASSOC_EVENT) { + if (event->u.mlme.status == MLME_DENIED) + CHECK_MLME_TRIGGER(stop_assoc_denied, + "DENIED ASSOC: reason %d", + event->u.mlme.reason); + else if (event->u.mlme.status == MLME_TIMEOUT) + CHECK_MLME_TRIGGER(stop_assoc_timeout, + "ASSOC TIMEOUT"); + } else if (event->u.mlme.data == AUTH_EVENT) { + if (event->u.mlme.status == MLME_DENIED) + CHECK_MLME_TRIGGER(stop_auth_denied, + "DENIED AUTH: reason %d", + event->u.mlme.reason); + else if (event->u.mlme.status == MLME_TIMEOUT) + CHECK_MLME_TRIGGER(stop_auth_timeout, + "AUTH TIMEOUT"); + } else if (event->u.mlme.data == DEAUTH_RX_EVENT) { + CHECK_MLME_TRIGGER(stop_rx_deauth, + "DEAUTH RX %d", event->u.mlme.reason); + } else if (event->u.mlme.data == DEAUTH_TX_EVENT) { + CHECK_MLME_TRIGGER(stop_tx_deauth, + "DEAUTH TX %d", event->u.mlme.reason); + } +#undef CHECK_MLME_TRIGGER +} + +static void iwl_mvm_event_bar_rx_callback(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_event *event) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_ba *ba_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_BA); + if (!trig) + return; + + ba_trig = (void *)trig->data; + + if (!(le16_to_cpu(ba_trig->rx_bar) & BIT(event->u.ba.tid))) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "BAR received from %pM, tid %d, ssn %d", + event->u.ba.sta->addr, event->u.ba.tid, + event->u.ba.ssn); +} + +static void iwl_mvm_mac_event_callback(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const struct ieee80211_event *event) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + switch (event->type) { + case MLME_EVENT: + iwl_mvm_event_mlme_callback(mvm, vif, event); + break; + case BAR_RX_EVENT: + iwl_mvm_event_bar_rx_callback(mvm, vif, event); + break; + case BA_FRAME_TIMEOUT: + iwl_mvm_event_frame_timeout_callback(mvm, vif, event->u.ba.sta, + event->u.ba.tid); + break; + default: + break; + } +} + +void iwl_mvm_sync_rx_queues_internal(struct iwl_mvm *mvm, + enum iwl_mvm_rxq_notif_type type, + bool sync, + const void *data, u32 size) +{ + struct { + struct iwl_rxq_sync_cmd cmd; + struct iwl_mvm_internal_rxq_notif notif; + } __packed cmd = { + .cmd.rxq_mask = cpu_to_le32(BIT(mvm->trans->num_rx_queues) - 1), + .cmd.count = + cpu_to_le32(sizeof(struct iwl_mvm_internal_rxq_notif) + + size), + .notif.type = type, + .notif.sync = sync, + }; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DATA_PATH_GROUP, TRIGGER_RX_QUEUES_NOTIF_CMD), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + .data[1] = data, + .len[1] = size, + .flags = sync ? 0 : CMD_ASYNC, + }; + int ret; + + /* size must be a multiple of DWORD */ + if (WARN_ON(cmd.cmd.count & cpu_to_le32(3))) + return; + + if (!iwl_mvm_has_new_rx_api(mvm)) + return; + + if (sync) { + cmd.notif.cookie = mvm->queue_sync_cookie; + mvm->queue_sync_state = (1 << mvm->trans->num_rx_queues) - 1; + } + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + if (ret) { + IWL_ERR(mvm, "Failed to trigger RX queues sync (%d)\n", ret); + goto out; + } + + if (sync) { + lockdep_assert_held(&mvm->mutex); + ret = wait_event_timeout(mvm->rx_sync_waitq, + READ_ONCE(mvm->queue_sync_state) == 0 || + iwl_mvm_is_radio_killed(mvm), + HZ); + WARN_ONCE(!ret && !iwl_mvm_is_radio_killed(mvm), + "queue sync: failed to sync, state is 0x%lx\n", + mvm->queue_sync_state); + } + +out: + if (sync) { + mvm->queue_sync_state = 0; + mvm->queue_sync_cookie++; + } +} + +static void iwl_mvm_sync_rx_queues(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + iwl_mvm_sync_rx_queues_internal(mvm, IWL_MVM_RXQ_EMPTY, true, NULL, 0); + mutex_unlock(&mvm->mutex); +} + +static int +iwl_mvm_mac_get_ftm_responder_stats(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_ftm_responder_stats *stats) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->p2p || vif->type != NL80211_IFTYPE_AP || + !mvmvif->ap_ibss_active || !vif->bss_conf.ftm_responder) + return -EINVAL; + + mutex_lock(&mvm->mutex); + *stats = mvm->ftm_resp_stats; + mutex_unlock(&mvm->mutex); + + stats->filled = BIT(NL80211_FTM_STATS_SUCCESS_NUM) | + BIT(NL80211_FTM_STATS_PARTIAL_NUM) | + BIT(NL80211_FTM_STATS_FAILED_NUM) | + BIT(NL80211_FTM_STATS_ASAP_NUM) | + BIT(NL80211_FTM_STATS_NON_ASAP_NUM) | + BIT(NL80211_FTM_STATS_TOTAL_DURATION_MSEC) | + BIT(NL80211_FTM_STATS_UNKNOWN_TRIGGERS_NUM) | + BIT(NL80211_FTM_STATS_RESCHEDULE_REQUESTS_NUM) | + BIT(NL80211_FTM_STATS_OUT_OF_WINDOW_TRIGGERS_NUM); + + return 0; +} + +static int iwl_mvm_start_pmsr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *request) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_ftm_start(mvm, vif, request); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_abort_pmsr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *request) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + iwl_mvm_ftm_abort(mvm, request); + mutex_unlock(&mvm->mutex); +} + +static bool iwl_mvm_can_hw_csum(struct sk_buff *skb) +{ + u8 protocol = ip_hdr(skb)->protocol; + + if (!IS_ENABLED(CONFIG_INET)) + return false; + + return protocol == IPPROTO_TCP || protocol == IPPROTO_UDP; +} + +static bool iwl_mvm_mac_can_aggregate(struct ieee80211_hw *hw, + struct sk_buff *head, + struct sk_buff *skb) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* For now don't aggregate IPv6 in AMSDU */ + if (skb->protocol != htons(ETH_P_IP)) + return false; + + if (!iwl_mvm_is_csum_supported(mvm)) + return true; + + return iwl_mvm_can_hw_csum(skb) == iwl_mvm_can_hw_csum(head); +} + +const struct ieee80211_ops iwl_mvm_hw_ops = { + .tx = iwl_mvm_mac_tx, + .wake_tx_queue = iwl_mvm_mac_wake_tx_queue, + .ampdu_action = iwl_mvm_mac_ampdu_action, + .get_antenna = iwl_mvm_op_get_antenna, + .start = iwl_mvm_mac_start, + .reconfig_complete = iwl_mvm_mac_reconfig_complete, + .stop = iwl_mvm_mac_stop, + .add_interface = iwl_mvm_mac_add_interface, + .remove_interface = iwl_mvm_mac_remove_interface, + .config = iwl_mvm_mac_config, + .prepare_multicast = iwl_mvm_prepare_multicast, + .configure_filter = iwl_mvm_configure_filter, + .config_iface_filter = iwl_mvm_config_iface_filter, + .bss_info_changed = iwl_mvm_bss_info_changed, + .hw_scan = iwl_mvm_mac_hw_scan, + .cancel_hw_scan = iwl_mvm_mac_cancel_hw_scan, + .sta_pre_rcu_remove = iwl_mvm_sta_pre_rcu_remove, + .sta_state = iwl_mvm_mac_sta_state, + .sta_notify = iwl_mvm_mac_sta_notify, + .allow_buffered_frames = iwl_mvm_mac_allow_buffered_frames, + .release_buffered_frames = iwl_mvm_mac_release_buffered_frames, + .set_rts_threshold = iwl_mvm_mac_set_rts_threshold, + .sta_rc_update = iwl_mvm_sta_rc_update, + .conf_tx = iwl_mvm_mac_conf_tx, + .mgd_prepare_tx = iwl_mvm_mac_mgd_prepare_tx, + .mgd_complete_tx = iwl_mvm_mac_mgd_complete_tx, + .mgd_protect_tdls_discover = iwl_mvm_mac_mgd_protect_tdls_discover, + .flush = iwl_mvm_mac_flush, + .sched_scan_start = iwl_mvm_mac_sched_scan_start, + .sched_scan_stop = iwl_mvm_mac_sched_scan_stop, + .set_key = iwl_mvm_mac_set_key, + .update_tkip_key = iwl_mvm_mac_update_tkip_key, + .remain_on_channel = iwl_mvm_roc, + .cancel_remain_on_channel = iwl_mvm_cancel_roc, + .add_chanctx = iwl_mvm_add_chanctx, + .remove_chanctx = iwl_mvm_remove_chanctx, + .change_chanctx = iwl_mvm_change_chanctx, + .assign_vif_chanctx = iwl_mvm_assign_vif_chanctx, + .unassign_vif_chanctx = iwl_mvm_unassign_vif_chanctx, + .switch_vif_chanctx = iwl_mvm_switch_vif_chanctx, + + .start_ap = iwl_mvm_start_ap_ibss, + .stop_ap = iwl_mvm_stop_ap_ibss, + .join_ibss = iwl_mvm_start_ap_ibss, + .leave_ibss = iwl_mvm_stop_ap_ibss, + + .tx_last_beacon = iwl_mvm_tx_last_beacon, + + .set_tim = iwl_mvm_set_tim, + + .channel_switch = iwl_mvm_channel_switch, + .pre_channel_switch = iwl_mvm_pre_channel_switch, + .post_channel_switch = iwl_mvm_post_channel_switch, + .abort_channel_switch = iwl_mvm_abort_channel_switch, + .channel_switch_rx_beacon = iwl_mvm_channel_switch_rx_beacon, + + .tdls_channel_switch = iwl_mvm_tdls_channel_switch, + .tdls_cancel_channel_switch = iwl_mvm_tdls_cancel_channel_switch, + .tdls_recv_channel_switch = iwl_mvm_tdls_recv_channel_switch, + + .event_callback = iwl_mvm_mac_event_callback, + + .sync_rx_queues = iwl_mvm_sync_rx_queues, + + CFG80211_TESTMODE_CMD(iwl_mvm_mac_testmode_cmd) + +#ifdef CONFIG_PM_SLEEP + /* look at d3.c */ + .suspend = iwl_mvm_suspend, + .resume = iwl_mvm_resume, + .set_wakeup = iwl_mvm_set_wakeup, + .set_rekey_data = iwl_mvm_set_rekey_data, +#if IS_ENABLED(CONFIG_IPV6) + .ipv6_addr_change = iwl_mvm_ipv6_addr_change, +#endif + .set_default_unicast_key = iwl_mvm_set_default_unicast_key, +#endif + .get_survey = iwl_mvm_mac_get_survey, + .sta_statistics = iwl_mvm_mac_sta_statistics, + .get_ftm_responder_stats = iwl_mvm_mac_get_ftm_responder_stats, + .start_pmsr = iwl_mvm_start_pmsr, + .abort_pmsr = iwl_mvm_abort_pmsr, + + .can_aggregate_in_amsdu = iwl_mvm_mac_can_aggregate, +#ifdef CONFIG_IWLWIFI_DEBUGFS + .sta_add_debugfs = iwl_mvm_sta_add_debugfs, +#endif +}; diff --git a/sys/contrib/dev/iwlwifi/mvm/mvm.h b/sys/contrib/dev/iwlwifi/mvm/mvm.h new file mode 100644 index 00000000000..254c59b6d9d --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/mvm.h @@ -0,0 +1,2169 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __IWL_MVM_H__ +#define __IWL_MVM_H__ + +#include +#include +#ifdef CONFIG_IWLWIFI_LEDS +#include +#endif +#include + +#ifdef CONFIG_THERMAL +#include +#endif + +#include + +#include "iwl-op-mode.h" +#include "iwl-trans.h" +#include "fw/notif-wait.h" +#include "iwl-eeprom-parse.h" +#include "fw/file.h" +#include "iwl-config.h" +#include "sta.h" +#include "fw-api.h" +#include "constants.h" +#include "fw/runtime.h" +#include "fw/dbg.h" +#include "fw/acpi.h" +#include "iwl-nvm-parse.h" + +#include +#if defined(__FreeBSD__) +#include +#endif + +#define IWL_MVM_MAX_ADDRESSES 5 +/* RSSI offset for WkP */ +#define IWL_RSSI_OFFSET 50 +#define IWL_MVM_MISSED_BEACONS_THRESHOLD 8 +#define IWL_MVM_MISSED_BEACONS_THRESHOLD_LONG 16 + +/* A TimeUnit is 1024 microsecond */ +#define MSEC_TO_TU(_msec) (_msec*1000/1024) + +/* For GO, this value represents the number of TUs before CSA "beacon + * 0" TBTT when the CSA time-event needs to be scheduled to start. It + * must be big enough to ensure that we switch in time. + */ +#define IWL_MVM_CHANNEL_SWITCH_TIME_GO 40 + +/* For client, this value represents the number of TUs before CSA + * "beacon 1" TBTT, instead. This is because we don't know when the + * GO/AP will be in the new channel, so we switch early enough. + */ +#define IWL_MVM_CHANNEL_SWITCH_TIME_CLIENT 10 + +/* + * This value (in TUs) is used to fine tune the CSA NoA end time which should + * be just before "beacon 0" TBTT. + */ +#define IWL_MVM_CHANNEL_SWITCH_MARGIN 4 + +/* + * Number of beacons to transmit on a new channel until we unblock tx to + * the stations, even if we didn't identify them on a new channel + */ +#define IWL_MVM_CS_UNBLOCK_TX_TIMEOUT 3 + +/* offchannel queue towards mac80211 */ +#define IWL_MVM_OFFCHANNEL_QUEUE 0 + +extern const struct ieee80211_ops iwl_mvm_hw_ops; + +/** + * struct iwl_mvm_mod_params - module parameters for iwlmvm + * @init_dbg: if true, then the NIC won't be stopped if the INIT fw asserted. + * We will register to mac80211 to have testmode working. The NIC must not + * be up'ed after the INIT fw asserted. This is useful to be able to use + * proprietary tools over testmode to debug the INIT fw. + * @power_scheme: one of enum iwl_power_scheme + */ +struct iwl_mvm_mod_params { + bool init_dbg; + int power_scheme; +}; +extern struct iwl_mvm_mod_params iwlmvm_mod_params; + +struct iwl_mvm_phy_ctxt { + u16 id; + u16 color; + u32 ref; + + enum nl80211_chan_width width; + + /* + * TODO: This should probably be removed. Currently here only for rate + * scaling algorithm + */ + struct ieee80211_channel *channel; +}; + +struct iwl_mvm_time_event_data { + struct ieee80211_vif *vif; + struct list_head list; + unsigned long end_jiffies; + u32 duration; + bool running; + u32 uid; + + /* + * The access to the 'id' field must be done when the + * mvm->time_event_lock is held, as it value is used to indicate + * if the te is in the time event list or not (when id == TE_MAX) + */ + u32 id; +}; + + /* Power management */ + +/** + * enum iwl_power_scheme + * @IWL_POWER_LEVEL_CAM - Continuously Active Mode + * @IWL_POWER_LEVEL_BPS - Balanced Power Save (default) + * @IWL_POWER_LEVEL_LP - Low Power + */ +enum iwl_power_scheme { + IWL_POWER_SCHEME_CAM = 1, + IWL_POWER_SCHEME_BPS, + IWL_POWER_SCHEME_LP +}; + +#define IWL_CONN_MAX_LISTEN_INTERVAL 10 +#define IWL_UAPSD_MAX_SP IEEE80211_WMM_IE_STA_QOSINFO_SP_ALL + +#ifdef CONFIG_IWLWIFI_DEBUGFS +enum iwl_dbgfs_pm_mask { + MVM_DEBUGFS_PM_KEEP_ALIVE = BIT(0), + MVM_DEBUGFS_PM_SKIP_OVER_DTIM = BIT(1), + MVM_DEBUGFS_PM_SKIP_DTIM_PERIODS = BIT(2), + MVM_DEBUGFS_PM_RX_DATA_TIMEOUT = BIT(3), + MVM_DEBUGFS_PM_TX_DATA_TIMEOUT = BIT(4), + MVM_DEBUGFS_PM_LPRX_ENA = BIT(6), + MVM_DEBUGFS_PM_LPRX_RSSI_THRESHOLD = BIT(7), + MVM_DEBUGFS_PM_SNOOZE_ENABLE = BIT(8), + MVM_DEBUGFS_PM_UAPSD_MISBEHAVING = BIT(9), + MVM_DEBUGFS_PM_USE_PS_POLL = BIT(10), +}; + +struct iwl_dbgfs_pm { + u16 keep_alive_seconds; + u32 rx_data_timeout; + u32 tx_data_timeout; + bool skip_over_dtim; + u8 skip_dtim_periods; + bool lprx_ena; + u32 lprx_rssi_threshold; + bool snooze_ena; + bool uapsd_misbehaving; + bool use_ps_poll; + int mask; +}; + +/* beacon filtering */ + +enum iwl_dbgfs_bf_mask { + MVM_DEBUGFS_BF_ENERGY_DELTA = BIT(0), + MVM_DEBUGFS_BF_ROAMING_ENERGY_DELTA = BIT(1), + MVM_DEBUGFS_BF_ROAMING_STATE = BIT(2), + MVM_DEBUGFS_BF_TEMP_THRESHOLD = BIT(3), + MVM_DEBUGFS_BF_TEMP_FAST_FILTER = BIT(4), + MVM_DEBUGFS_BF_TEMP_SLOW_FILTER = BIT(5), + MVM_DEBUGFS_BF_ENABLE_BEACON_FILTER = BIT(6), + MVM_DEBUGFS_BF_DEBUG_FLAG = BIT(7), + MVM_DEBUGFS_BF_ESCAPE_TIMER = BIT(8), + MVM_DEBUGFS_BA_ESCAPE_TIMER = BIT(9), + MVM_DEBUGFS_BA_ENABLE_BEACON_ABORT = BIT(10), +}; + +struct iwl_dbgfs_bf { + u32 bf_energy_delta; + u32 bf_roaming_energy_delta; + u32 bf_roaming_state; + u32 bf_temp_threshold; + u32 bf_temp_fast_filter; + u32 bf_temp_slow_filter; + u32 bf_enable_beacon_filter; + u32 bf_debug_flag; + u32 bf_escape_timer; + u32 ba_escape_timer; + u32 ba_enable_beacon_abort; + int mask; +}; +#endif + +enum iwl_mvm_smps_type_request { + IWL_MVM_SMPS_REQ_BT_COEX, + IWL_MVM_SMPS_REQ_TT, + IWL_MVM_SMPS_REQ_PROT, + IWL_MVM_SMPS_REQ_FW, + NUM_IWL_MVM_SMPS_REQ, +}; + +enum iwl_bt_force_ant_mode { + BT_FORCE_ANT_DIS = 0, + BT_FORCE_ANT_AUTO, + BT_FORCE_ANT_BT, + BT_FORCE_ANT_WIFI, + + BT_FORCE_ANT_MAX, +}; + +/** + * struct iwl_mvm_low_latency_force - low latency force mode set by debugfs + * @LOW_LATENCY_FORCE_UNSET: unset force mode + * @LOW_LATENCY_FORCE_ON: for low latency on + * @LOW_LATENCY_FORCE_OFF: for low latency off + * @NUM_LOW_LATENCY_FORCE: max num of modes + */ +enum iwl_mvm_low_latency_force { + LOW_LATENCY_FORCE_UNSET, + LOW_LATENCY_FORCE_ON, + LOW_LATENCY_FORCE_OFF, + NUM_LOW_LATENCY_FORCE +}; + +/** +* struct iwl_mvm_low_latency_cause - low latency set causes +* @LOW_LATENCY_TRAFFIC: indicates low latency traffic was detected +* @LOW_LATENCY_DEBUGFS: low latency mode set from debugfs +* @LOW_LATENCY_VCMD: low latency mode set from vendor command +* @LOW_LATENCY_VIF_TYPE: low latency mode set because of vif type (ap) +* @LOW_LATENCY_DEBUGFS_FORCE_ENABLE: indicate that force mode is enabled +* the actual set/unset is done with LOW_LATENCY_DEBUGFS_FORCE +* @LOW_LATENCY_DEBUGFS_FORCE: low latency force mode from debugfs +* set this with LOW_LATENCY_DEBUGFS_FORCE_ENABLE flag +* in low_latency. +*/ +enum iwl_mvm_low_latency_cause { + LOW_LATENCY_TRAFFIC = BIT(0), + LOW_LATENCY_DEBUGFS = BIT(1), + LOW_LATENCY_VCMD = BIT(2), + LOW_LATENCY_VIF_TYPE = BIT(3), + LOW_LATENCY_DEBUGFS_FORCE_ENABLE = BIT(4), + LOW_LATENCY_DEBUGFS_FORCE = BIT(5), +}; + +/** +* struct iwl_mvm_vif_bf_data - beacon filtering related data +* @bf_enabled: indicates if beacon filtering is enabled +* @ba_enabled: indicated if beacon abort is enabled +* @ave_beacon_signal: average beacon signal +* @last_cqm_event: rssi of the last cqm event +* @bt_coex_min_thold: minimum threshold for BT coex +* @bt_coex_max_thold: maximum threshold for BT coex +* @last_bt_coex_event: rssi of the last BT coex event +*/ +struct iwl_mvm_vif_bf_data { + bool bf_enabled; + bool ba_enabled; + int ave_beacon_signal; + int last_cqm_event; + int bt_coex_min_thold; + int bt_coex_max_thold; + int last_bt_coex_event; +}; + +/** + * struct iwl_probe_resp_data - data for NoA/CSA updates + * @rcu_head: used for freeing the data on update + * @notif: notification data + * @noa_len: length of NoA attribute, calculated from the notification + */ +struct iwl_probe_resp_data { + struct rcu_head rcu_head; + struct iwl_probe_resp_data_notif notif; + int noa_len; +}; + +/** + * struct iwl_mvm_vif - data per Virtual Interface, it is a MAC context + * @id: between 0 and 3 + * @color: to solve races upon MAC addition and removal + * @ap_sta_id: the sta_id of the AP - valid only if VIF type is STA + * @bssid: BSSID for this (client) interface + * @associated: indicates that we're currently associated, used only for + * managing the firmware state in iwl_mvm_bss_info_changed_station() + * @ap_assoc_sta_count: count of stations associated to us - valid only + * if VIF type is AP + * @uploaded: indicates the MAC context has been added to the device + * @ap_ibss_active: indicates that AP/IBSS is configured and that the interface + * should get quota etc. + * @pm_enabled - Indicate if MAC power management is allowed + * @monitor_active: indicates that monitor context is configured, and that the + * interface should get quota etc. + * @low_latency: bit flags for low latency + * see enum &iwl_mvm_low_latency_cause for causes. + * @low_latency_actual: boolean, indicates low latency is set, + * as a result from low_latency bit flags and takes force into account. + * @authorized: indicates the AP station was set to authorized + * @ps_disabled: indicates that this interface requires PS to be disabled + * @queue_params: QoS params for this MAC + * @bcast_sta: station used for broadcast packets. Used by the following + * vifs: P2P_DEVICE, GO and AP. + * @beacon_skb: the skb used to hold the AP/GO beacon template + * @smps_requests: the SMPS requests of different parts of the driver, + * combined on update to yield the overall request to mac80211. + * @beacon_stats: beacon statistics, containing the # of received beacons, + * # of received beacons accumulated over FW restart, and the current + * average signal of beacons retrieved from the firmware + * @csa_failed: CSA failed to schedule time event, report an error later + * @features: hw features active for this vif + * @probe_resp_data: data from FW notification to store NOA and CSA related + * data to be inserted into probe response. + */ +struct iwl_mvm_vif { + struct iwl_mvm *mvm; + u16 id; + u16 color; + u8 ap_sta_id; + + u8 bssid[ETH_ALEN]; + bool associated; + u8 ap_assoc_sta_count; + + u16 cab_queue; + + bool uploaded; + bool ap_ibss_active; + bool pm_enabled; + bool monitor_active; + u8 low_latency: 6; + u8 low_latency_actual: 1; + u8 authorized:1; + bool ps_disabled; + struct iwl_mvm_vif_bf_data bf_data; + + struct { + u32 num_beacons, accu_num_beacons; + u8 avg_signal; + } beacon_stats; + + u32 ap_beacon_time; + + enum iwl_tsf_id tsf_id; + + /* + * QoS data from mac80211, need to store this here + * as mac80211 has a separate callback but we need + * to have the data for the MAC context + */ + struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; + struct iwl_mvm_time_event_data time_event_data; + struct iwl_mvm_time_event_data hs_time_event_data; + + struct iwl_mvm_int_sta bcast_sta; + struct iwl_mvm_int_sta mcast_sta; + + /* + * Assigned while mac80211 has the interface in a channel context, + * or, for P2P Device, while it exists. + */ + struct iwl_mvm_phy_ctxt *phy_ctxt; + +#ifdef CONFIG_PM + /* WoWLAN GTK rekey data */ + struct { + u8 kck[NL80211_KCK_EXT_LEN]; + u8 kek[NL80211_KEK_EXT_LEN]; + size_t kek_len; + size_t kck_len; + u32 akm; + __le64 replay_ctr; + bool valid; + } rekey_data; + + int tx_key_idx; + + bool seqno_valid; + u16 seqno; +#endif + +#if IS_ENABLED(CONFIG_IPV6) + /* IPv6 addresses for WoWLAN */ + struct in6_addr target_ipv6_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX]; + unsigned long tentative_addrs[BITS_TO_LONGS(IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX)]; + int num_target_ipv6_addrs; +#endif + +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct dentry *dbgfs_dir; + struct dentry *dbgfs_slink; + struct iwl_dbgfs_pm dbgfs_pm; + struct iwl_dbgfs_bf dbgfs_bf; + struct iwl_mac_power_cmd mac_pwr_cmd; + int dbgfs_quota_min; +#endif + + enum ieee80211_smps_mode smps_requests[NUM_IWL_MVM_SMPS_REQ]; + + /* FW identified misbehaving AP */ + u8 uapsd_misbehaving_bssid[ETH_ALEN]; + + struct delayed_work uapsd_nonagg_detected_wk; + + /* Indicates that CSA countdown may be started */ + bool csa_countdown; + bool csa_failed; + u16 csa_target_freq; + u16 csa_count; + u16 csa_misbehave; + struct delayed_work csa_work; + + /* Indicates that we are waiting for a beacon on a new channel */ + bool csa_bcn_pending; + + /* TCP Checksum Offload */ + netdev_features_t features; + + struct iwl_probe_resp_data __rcu *probe_resp_data; + + /* we can only have 2 GTK + 2 IGTK active at a time */ + struct ieee80211_key_conf *ap_early_keys[4]; + + /* 26-tone RU OFDMA transmissions should be blocked */ + bool he_ru_2mhz_block; + + struct { + struct ieee80211_key_conf __rcu *keys[2]; + } bcn_prot; +}; + +static inline struct iwl_mvm_vif * +iwl_mvm_vif_from_mac80211(struct ieee80211_vif *vif) +{ + return (void *)vif->drv_priv; +} + +extern const u8 tid_to_mac80211_ac[]; + +#define IWL_MVM_SCAN_STOPPING_SHIFT 8 + +enum iwl_scan_status { + IWL_MVM_SCAN_REGULAR = BIT(0), + IWL_MVM_SCAN_SCHED = BIT(1), + IWL_MVM_SCAN_NETDETECT = BIT(2), + + IWL_MVM_SCAN_STOPPING_REGULAR = BIT(8), + IWL_MVM_SCAN_STOPPING_SCHED = BIT(9), + IWL_MVM_SCAN_STOPPING_NETDETECT = BIT(10), + + IWL_MVM_SCAN_REGULAR_MASK = IWL_MVM_SCAN_REGULAR | + IWL_MVM_SCAN_STOPPING_REGULAR, + IWL_MVM_SCAN_SCHED_MASK = IWL_MVM_SCAN_SCHED | + IWL_MVM_SCAN_STOPPING_SCHED, + IWL_MVM_SCAN_NETDETECT_MASK = IWL_MVM_SCAN_NETDETECT | + IWL_MVM_SCAN_STOPPING_NETDETECT, + + IWL_MVM_SCAN_STOPPING_MASK = 0xff << IWL_MVM_SCAN_STOPPING_SHIFT, + IWL_MVM_SCAN_MASK = 0xff, +}; + +enum iwl_mvm_scan_type { + IWL_SCAN_TYPE_NOT_SET, + IWL_SCAN_TYPE_UNASSOC, + IWL_SCAN_TYPE_WILD, + IWL_SCAN_TYPE_MILD, + IWL_SCAN_TYPE_FRAGMENTED, + IWL_SCAN_TYPE_FAST_BALANCE, +}; + +enum iwl_mvm_sched_scan_pass_all_states { + SCHED_SCAN_PASS_ALL_DISABLED, + SCHED_SCAN_PASS_ALL_ENABLED, + SCHED_SCAN_PASS_ALL_FOUND, +}; + +/** + * struct iwl_mvm_tt_mgnt - Thermal Throttling Management structure + * @ct_kill_exit: worker to exit thermal kill + * @dynamic_smps: Is thermal throttling enabled dynamic_smps? + * @tx_backoff: The current thremal throttling tx backoff in uSec. + * @min_backoff: The minimal tx backoff due to power restrictions + * @params: Parameters to configure the thermal throttling algorithm. + * @throttle: Is thermal throttling is active? + */ +struct iwl_mvm_tt_mgmt { + struct delayed_work ct_kill_exit; + bool dynamic_smps; + u32 tx_backoff; + u32 min_backoff; + struct iwl_tt_params params; + bool throttle; +}; + +#ifdef CONFIG_THERMAL +/** + *struct iwl_mvm_thermal_device - thermal zone related data + * @temp_trips: temperature thresholds for report + * @fw_trips_index: keep indexes to original array - temp_trips + * @tzone: thermal zone device data +*/ +struct iwl_mvm_thermal_device { + s16 temp_trips[IWL_MAX_DTS_TRIPS]; + u8 fw_trips_index[IWL_MAX_DTS_TRIPS]; + struct thermal_zone_device *tzone; +}; + +/* + * struct iwl_mvm_cooling_device + * @cur_state: current state + * @cdev: struct thermal cooling device + */ +struct iwl_mvm_cooling_device { + u32 cur_state; + struct thermal_cooling_device *cdev; +}; +#endif + +#define IWL_MVM_NUM_LAST_FRAMES_UCODE_RATES 8 + +struct iwl_mvm_frame_stats { + u32 legacy_frames; + u32 ht_frames; + u32 vht_frames; + u32 bw_20_frames; + u32 bw_40_frames; + u32 bw_80_frames; + u32 bw_160_frames; + u32 sgi_frames; + u32 ngi_frames; + u32 siso_frames; + u32 mimo2_frames; + u32 agg_frames; + u32 ampdu_count; + u32 success_frames; + u32 fail_frames; + u32 last_rates[IWL_MVM_NUM_LAST_FRAMES_UCODE_RATES]; + int last_frame_idx; +}; + +#define IWL_MVM_DEBUG_SET_TEMPERATURE_DISABLE 0xff +#define IWL_MVM_DEBUG_SET_TEMPERATURE_MIN -100 +#define IWL_MVM_DEBUG_SET_TEMPERATURE_MAX 200 + +enum iwl_mvm_tdls_cs_state { + IWL_MVM_TDLS_SW_IDLE = 0, + IWL_MVM_TDLS_SW_REQ_SENT, + IWL_MVM_TDLS_SW_RESP_RCVD, + IWL_MVM_TDLS_SW_REQ_RCVD, + IWL_MVM_TDLS_SW_ACTIVE, +}; + +enum iwl_mvm_traffic_load { + IWL_MVM_TRAFFIC_LOW, + IWL_MVM_TRAFFIC_MEDIUM, + IWL_MVM_TRAFFIC_HIGH, +}; + +DECLARE_EWMA(rate, 16, 16) + +struct iwl_mvm_tcm_mac { + struct { + u32 pkts[IEEE80211_NUM_ACS]; + u32 airtime; + } tx; + struct { + u32 pkts[IEEE80211_NUM_ACS]; + u32 airtime; + u32 last_ampdu_ref; + } rx; + struct { + /* track AP's transfer in client mode */ + u64 rx_bytes; + struct ewma_rate rate; + bool detected; + } uapsd_nonagg_detect; + bool opened_rx_ba_sessions; +}; + +struct iwl_mvm_tcm { + struct delayed_work work; + spinlock_t lock; /* used when time elapsed */ + unsigned long ts; /* timestamp when period ends */ + unsigned long ll_ts; + unsigned long uapsd_nonagg_ts; + bool paused; + struct iwl_mvm_tcm_mac data[NUM_MAC_INDEX_DRIVER]; + struct { + u32 elapsed; /* milliseconds for this TCM period */ + u32 airtime[NUM_MAC_INDEX_DRIVER]; + enum iwl_mvm_traffic_load load[NUM_MAC_INDEX_DRIVER]; + enum iwl_mvm_traffic_load band_load[NUM_NL80211_BANDS]; + enum iwl_mvm_traffic_load global_load; + bool low_latency[NUM_MAC_INDEX_DRIVER]; + bool change[NUM_MAC_INDEX_DRIVER]; + } result; +}; + +/** + * struct iwl_mvm_reorder_buffer - per ra/tid/queue reorder buffer + * @head_sn: reorder window head sn + * @num_stored: number of mpdus stored in the buffer + * @buf_size: the reorder buffer size as set by the last addba request + * @queue: queue of this reorder buffer + * @last_amsdu: track last ASMDU SN for duplication detection + * @last_sub_index: track ASMDU sub frame index for duplication detection + * @reorder_timer: timer for frames are in the reorder buffer. For AMSDU + * it is the time of last received sub-frame + * @removed: prevent timer re-arming + * @valid: reordering is valid for this queue + * @lock: protect reorder buffer internal state + * @mvm: mvm pointer, needed for frame timer context + * @consec_oldsn_drops: consecutive drops due to old SN + * @consec_oldsn_ampdu_gp2: A-MPDU GP2 timestamp to track + * when to apply old SN consecutive drop workaround + * @consec_oldsn_prev_drop: track whether or not an MPDU + * that was single/part of the previous A-MPDU was + * dropped due to old SN + */ +struct iwl_mvm_reorder_buffer { + u16 head_sn; + u16 num_stored; + u16 buf_size; + int queue; + u16 last_amsdu; + u8 last_sub_index; + struct timer_list reorder_timer; + bool removed; + bool valid; + spinlock_t lock; + struct iwl_mvm *mvm; + unsigned int consec_oldsn_drops; + u32 consec_oldsn_ampdu_gp2; + unsigned int consec_oldsn_prev_drop:1; +} ____cacheline_aligned_in_smp; + +/** + * struct _iwl_mvm_reorder_buf_entry - reorder buffer entry per-queue/per-seqno + * @frames: list of skbs stored + * @reorder_time: time the packet was stored in the reorder buffer + */ +struct _iwl_mvm_reorder_buf_entry { + struct sk_buff_head frames; + unsigned long reorder_time; +}; + +/* make this indirection to get the aligned thing */ +struct iwl_mvm_reorder_buf_entry { + struct _iwl_mvm_reorder_buf_entry e; +} +#if defined(__FreeBSD__) +__aligned(roundup2(sizeof(struct _iwl_mvm_reorder_buf_entry), 32)) +#elif !defined(__CHECKER__) +/* sparse doesn't like this construct: "bad integer constant expression" */ +/* clang on FreeBSD: error: 'aligned' attribute requires integer constant */ +__aligned(roundup_pow_of_two(sizeof(struct _iwl_mvm_reorder_buf_entry))) +#endif +; + +/** + * struct iwl_mvm_baid_data - BA session data + * @sta_id: station id + * @tid: tid of the session + * @baid baid of the session + * @timeout: the timeout set in the addba request + * @entries_per_queue: # of buffers per queue, this actually gets + * aligned up to avoid cache line sharing between queues + * @last_rx: last rx jiffies, updated only if timeout passed from last update + * @session_timer: timer to check if BA session expired, runs at 2 * timeout + * @mvm: mvm pointer, needed for timer context + * @reorder_buf: reorder buffer, allocated per queue + * @reorder_buf_data: data + */ +struct iwl_mvm_baid_data { + struct rcu_head rcu_head; + u8 sta_id; + u8 tid; + u8 baid; + u16 timeout; + u16 entries_per_queue; + unsigned long last_rx; + struct timer_list session_timer; + struct iwl_mvm_baid_data __rcu **rcu_ptr; + struct iwl_mvm *mvm; + struct iwl_mvm_reorder_buffer reorder_buf[IWL_MAX_RX_HW_QUEUES]; + struct iwl_mvm_reorder_buf_entry entries[]; +}; + +static inline struct iwl_mvm_baid_data * +iwl_mvm_baid_data_from_reorder_buf(struct iwl_mvm_reorder_buffer *buf) +{ + return (void *)((u8 *)buf - + offsetof(struct iwl_mvm_baid_data, reorder_buf) - + sizeof(*buf) * buf->queue); +} + +/* + * enum iwl_mvm_queue_status - queue status + * @IWL_MVM_QUEUE_FREE: the queue is not allocated nor reserved + * Basically, this means that this queue can be used for any purpose + * @IWL_MVM_QUEUE_RESERVED: queue is reserved but not yet in use + * This is the state of a queue that has been dedicated for some RATID + * (agg'd or not), but that hasn't yet gone through the actual enablement + * of iwl_mvm_enable_txq(), and therefore no traffic can go through it yet. + * Note that in this state there is no requirement to already know what TID + * should be used with this queue, it is just marked as a queue that will + * be used, and shouldn't be allocated to anyone else. + * @IWL_MVM_QUEUE_READY: queue is ready to be used + * This is the state of a queue that has been fully configured (including + * SCD pointers, etc), has a specific RA/TID assigned to it, and can be + * used to send traffic. + * @IWL_MVM_QUEUE_SHARED: queue is shared, or in a process of becoming shared + * This is a state in which a single queue serves more than one TID, all of + * which are not aggregated. Note that the queue is only associated to one + * RA. + */ +enum iwl_mvm_queue_status { + IWL_MVM_QUEUE_FREE, + IWL_MVM_QUEUE_RESERVED, + IWL_MVM_QUEUE_READY, + IWL_MVM_QUEUE_SHARED, +}; + +#define IWL_MVM_DQA_QUEUE_TIMEOUT (5 * HZ) +#define IWL_MVM_INVALID_QUEUE 0xFFFF + +#define IWL_MVM_NUM_CIPHERS 10 + + +struct iwl_mvm_txq { + struct list_head list; + u16 txq_id; + atomic_t tx_request; + bool stopped; +}; + +static inline struct iwl_mvm_txq * +iwl_mvm_txq_from_mac80211(struct ieee80211_txq *txq) +{ + return (void *)txq->drv_priv; +} + +static inline struct iwl_mvm_txq * +iwl_mvm_txq_from_tid(struct ieee80211_sta *sta, u8 tid) +{ + if (tid == IWL_MAX_TID_COUNT) + tid = IEEE80211_NUM_TIDS; + + return (void *)sta->txq[tid]->drv_priv; +} + +/** + * struct iwl_mvm_tvqm_txq_info - maps TVQM hw queue to tid + * + * @sta_id: sta id + * @txq_tid: txq tid + */ +struct iwl_mvm_tvqm_txq_info { + u8 sta_id; + u8 txq_tid; +}; + +struct iwl_mvm_dqa_txq_info { + u8 ra_sta_id; /* The RA this queue is mapped to, if exists */ + bool reserved; /* Is this the TXQ reserved for a STA */ + u8 mac80211_ac; /* The mac80211 AC this queue is mapped to */ + u8 txq_tid; /* The TID "owner" of this queue*/ + u16 tid_bitmap; /* Bitmap of the TIDs mapped to this queue */ + /* Timestamp for inactivation per TID of this queue */ + unsigned long last_frame_time[IWL_MAX_TID_COUNT + 1]; + enum iwl_mvm_queue_status status; +}; + +struct iwl_mvm { + /* for logger access */ + struct device *dev; + + struct iwl_trans *trans; + const struct iwl_fw *fw; + const struct iwl_cfg *cfg; + struct iwl_phy_db *phy_db; + struct ieee80211_hw *hw; + + /* for protecting access to iwl_mvm */ + struct mutex mutex; + struct list_head async_handlers_list; + spinlock_t async_handlers_lock; + struct work_struct async_handlers_wk; + + struct work_struct roc_done_wk; + + unsigned long init_status; + + unsigned long status; + + u32 queue_sync_cookie; + unsigned long queue_sync_state; + /* + * for beacon filtering - + * currently only one interface can be supported + */ + struct iwl_mvm_vif *bf_allowed_vif; + + bool hw_registered; + bool rfkill_safe_init_done; + + u8 cca_40mhz_workaround; + + u32 ampdu_ref; + bool ampdu_toggle; + + struct iwl_notif_wait_data notif_wait; + + union { + struct mvm_statistics_rx_v3 rx_stats_v3; + struct mvm_statistics_rx rx_stats; + }; + + struct { + u64 rx_time; + u64 tx_time; + u64 on_time_rf; + u64 on_time_scan; + } radio_stats, accu_radio_stats; + + struct list_head add_stream_txqs; + union { + struct iwl_mvm_dqa_txq_info queue_info[IWL_MAX_HW_QUEUES]; + struct iwl_mvm_tvqm_txq_info tvqm_info[IWL_MAX_TVQM_QUEUES]; + }; + struct work_struct add_stream_wk; /* To add streams to queues */ + + const char *nvm_file_name; + struct iwl_nvm_data *nvm_data; + /* NVM sections */ + struct iwl_nvm_section nvm_sections[NVM_MAX_NUM_SECTIONS]; + + struct iwl_fw_runtime fwrt; + + /* EEPROM MAC addresses */ + struct mac_address addresses[IWL_MVM_MAX_ADDRESSES]; + + /* data related to data path */ + struct iwl_rx_phy_info last_phy_info; + struct ieee80211_sta __rcu *fw_id_to_mac_id[IWL_MVM_STATION_COUNT_MAX]; + u8 rx_ba_sessions; + + /* configured by mac80211 */ + u32 rts_threshold; + + /* Scan status, cmd (pre-allocated) and auxiliary station */ + unsigned int scan_status; + void *scan_cmd; + struct iwl_mcast_filter_cmd *mcast_filter_cmd; + /* For CDB this is low band scan type, for non-CDB - type. */ + enum iwl_mvm_scan_type scan_type; + enum iwl_mvm_scan_type hb_scan_type; + + enum iwl_mvm_sched_scan_pass_all_states sched_scan_pass_all; + struct delayed_work scan_timeout_dwork; + + /* max number of simultaneous scans the FW supports */ + unsigned int max_scans; + + /* UMAC scan tracking */ + u32 scan_uid_status[IWL_MVM_MAX_UMAC_SCANS]; + + /* start time of last scan in TSF of the mac that requested the scan */ + u64 scan_start; + + /* the vif that requested the current scan */ + struct iwl_mvm_vif *scan_vif; + + /* rx chain antennas set through debugfs for the scan command */ + u8 scan_rx_ant; + +#ifdef CONFIG_IWLWIFI_BCAST_FILTERING + /* broadcast filters to configure for each associated station */ + const struct iwl_fw_bcast_filter *bcast_filters; +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct { + bool override; + struct iwl_bcast_filter_cmd cmd; + } dbgfs_bcast_filtering; +#endif +#endif + + /* Internal station */ + struct iwl_mvm_int_sta aux_sta; + struct iwl_mvm_int_sta snif_sta; + + bool last_ebs_successful; + + u8 scan_last_antenna_idx; /* to toggle TX between antennas */ + u8 mgmt_last_antenna_idx; + + /* last smart fifo state that was successfully sent to firmware */ + enum iwl_sf_state sf_state; + + /* + * Leave this pointer outside the ifdef below so that it can be + * assigned without ifdef in the source code. + */ + struct dentry *debugfs_dir; +#ifdef CONFIG_IWLWIFI_DEBUGFS + u32 dbgfs_sram_offset, dbgfs_sram_len; + u32 dbgfs_prph_reg_addr; + bool disable_power_off; + bool disable_power_off_d3; + bool beacon_inject_active; + + bool scan_iter_notif_enabled; + + struct debugfs_blob_wrapper nvm_hw_blob; + struct debugfs_blob_wrapper nvm_sw_blob; + struct debugfs_blob_wrapper nvm_calib_blob; + struct debugfs_blob_wrapper nvm_prod_blob; + struct debugfs_blob_wrapper nvm_phy_sku_blob; + struct debugfs_blob_wrapper nvm_reg_blob; + + struct iwl_mvm_frame_stats drv_rx_stats; + spinlock_t drv_stats_lock; + u16 dbgfs_rx_phyinfo; +#endif + + struct iwl_mvm_phy_ctxt phy_ctxts[NUM_PHY_CTX]; + + struct list_head time_event_list; + spinlock_t time_event_lock; + + /* + * A bitmap indicating the index of the key in use. The firmware + * can hold 16 keys at most. Reflect this fact. + */ + unsigned long fw_key_table[BITS_TO_LONGS(STA_KEY_MAX_NUM)]; + u8 fw_key_deleted[STA_KEY_MAX_NUM]; + + u8 vif_count; + struct ieee80211_vif __rcu *vif_id_to_mac[NUM_MAC_INDEX_DRIVER]; + + /* -1 for always, 0 for never, >0 for that many times */ + s8 fw_restart; + u8 *error_recovery_buf; + +#ifdef CONFIG_IWLWIFI_LEDS + struct led_classdev led; +#endif + + struct ieee80211_vif *p2p_device_vif; + +#ifdef CONFIG_PM + struct wiphy_wowlan_support wowlan; + int gtk_ivlen, gtk_icvlen, ptk_ivlen, ptk_icvlen; + + /* sched scan settings for net detect */ + struct ieee80211_scan_ies nd_ies; + struct cfg80211_match_set *nd_match_sets; + int n_nd_match_sets; + struct ieee80211_channel **nd_channels; + int n_nd_channels; + bool net_detect; + u8 offload_tid; +#ifdef CONFIG_IWLWIFI_DEBUGFS + bool d3_wake_sysassert; + bool d3_test_active; + u32 d3_test_pme_ptr; + struct ieee80211_vif *keep_vif; + u32 last_netdetect_scans; /* no. of scans in the last net-detect wake */ +#endif +#endif + + wait_queue_head_t rx_sync_waitq; + + /* BT-Coex */ + struct iwl_bt_coex_profile_notif last_bt_notif; + struct iwl_bt_coex_ci_cmd last_bt_ci_cmd; + + u8 bt_tx_prio; + enum iwl_bt_force_ant_mode bt_force_ant_mode; + + /* Aux ROC */ + struct list_head aux_roc_te_list; + + /* Thermal Throttling and CTkill */ + struct iwl_mvm_tt_mgmt thermal_throttle; +#ifdef CONFIG_THERMAL + struct iwl_mvm_thermal_device tz_device; + struct iwl_mvm_cooling_device cooling_dev; +#endif + + s32 temperature; /* Celsius */ + /* + * Debug option to set the NIC temperature. This option makes the + * driver think this is the actual NIC temperature, and ignore the + * real temperature that is received from the fw + */ + bool temperature_test; /* Debug test temperature is enabled */ + + bool fw_static_smps_request; + + unsigned long bt_coex_last_tcm_ts; + struct iwl_mvm_tcm tcm; + + u8 uapsd_noagg_bssid_write_idx; + struct mac_address uapsd_noagg_bssids[IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM] + __aligned(2); + + struct iwl_time_quota_cmd last_quota_cmd; + +#ifdef CONFIG_NL80211_TESTMODE + u32 noa_duration; + struct ieee80211_vif *noa_vif; +#endif + + /* Tx queues */ + u16 aux_queue; + u16 snif_queue; + u16 probe_queue; + u16 p2p_dev_queue; + + /* Indicate if device power save is allowed */ + u8 ps_disabled; /* u8 instead of bool to ease debugfs_create_* usage */ + /* Indicate if 32Khz external clock is valid */ + u32 ext_clock_valid; + + struct ieee80211_vif __rcu *csa_vif; + struct ieee80211_vif __rcu *csa_tx_blocked_vif; + u8 csa_tx_block_bcn_timeout; + + /* system time of last beacon (for AP/GO interface) */ + u32 ap_last_beacon_gp2; + + /* indicates that we transmitted the last beacon */ + bool ibss_manager; + + bool lar_regdom_set; + enum iwl_mcc_source mcc_src; + + /* TDLS channel switch data */ + struct { + struct delayed_work dwork; + enum iwl_mvm_tdls_cs_state state; + + /* + * Current cs sta - might be different from periodic cs peer + * station. Value is meaningless when the cs-state is idle. + */ + u8 cur_sta_id; + + /* TDLS periodic channel-switch peer */ + struct { + u8 sta_id; + u8 op_class; + bool initiator; /* are we the link initiator */ + struct cfg80211_chan_def chandef; + struct sk_buff *skb; /* ch sw template */ + u32 ch_sw_tm_ie; + + /* timestamp of last ch-sw request sent (GP2 time) */ + u32 sent_timestamp; + } peer; + } tdls_cs; + + + u32 ciphers[IWL_MVM_NUM_CIPHERS]; + struct ieee80211_cipher_scheme cs[IWL_UCODE_MAX_CS]; + + struct cfg80211_ftm_responder_stats ftm_resp_stats; + struct { + struct cfg80211_pmsr_request *req; + struct wireless_dev *req_wdev; + struct list_head loc_list; + int responses[IWL_MVM_TOF_MAX_APS]; + struct { + struct list_head resp; + } smooth; + struct list_head pasn_list; + } ftm_initiator; + + struct list_head resp_pasn_list; + + struct { + u8 d0i3_resp; + u8 range_resp; + } cmd_ver; + + struct ieee80211_vif *nan_vif; +#define IWL_MAX_BAID 32 + struct iwl_mvm_baid_data __rcu *baid_map[IWL_MAX_BAID]; + + /* + * Drop beacons from other APs in AP mode when there are no connected + * clients. + */ + bool drop_bcn_ap_mode; + + struct delayed_work cs_tx_unblock_dwork; + + /* does a monitor vif exist (only one can exist hence bool) */ + bool monitor_on; + + /* sniffer data to include in radiotap */ + __le16 cur_aid; + u8 cur_bssid[ETH_ALEN]; + + unsigned long last_6ghz_passive_scan_jiffies; + unsigned long last_reset_or_resume_time_jiffies; +}; + +/* Extract MVM priv from op_mode and _hw */ +#define IWL_OP_MODE_GET_MVM(_iwl_op_mode) \ + ((struct iwl_mvm *)(_iwl_op_mode)->op_mode_specific) + +#define IWL_MAC80211_GET_MVM(_hw) \ + IWL_OP_MODE_GET_MVM((struct iwl_op_mode *)((_hw)->priv)) + +/** + * enum iwl_mvm_status - MVM status bits + * @IWL_MVM_STATUS_HW_RFKILL: HW RF-kill is asserted + * @IWL_MVM_STATUS_HW_CTKILL: CT-kill is active + * @IWL_MVM_STATUS_ROC_RUNNING: remain-on-channel is running + * @IWL_MVM_STATUS_HW_RESTART_REQUESTED: HW restart was requested + * @IWL_MVM_STATUS_IN_HW_RESTART: HW restart is active + * @IWL_MVM_STATUS_ROC_AUX_RUNNING: AUX remain-on-channel is running + * @IWL_MVM_STATUS_FIRMWARE_RUNNING: firmware is running + * @IWL_MVM_STATUS_NEED_FLUSH_P2P: need to flush P2P bcast STA + * @IWL_MVM_STATUS_IN_D3: in D3 (or at least about to go into it) + */ +enum iwl_mvm_status { + IWL_MVM_STATUS_HW_RFKILL, + IWL_MVM_STATUS_HW_CTKILL, + IWL_MVM_STATUS_ROC_RUNNING, + IWL_MVM_STATUS_HW_RESTART_REQUESTED, + IWL_MVM_STATUS_IN_HW_RESTART, + IWL_MVM_STATUS_ROC_AUX_RUNNING, + IWL_MVM_STATUS_FIRMWARE_RUNNING, + IWL_MVM_STATUS_NEED_FLUSH_P2P, + IWL_MVM_STATUS_IN_D3, +}; + +/* Keep track of completed init configuration */ +enum iwl_mvm_init_status { + IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE = BIT(0), + IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE = BIT(1), +}; + +static inline bool iwl_mvm_is_radio_killed(struct iwl_mvm *mvm) +{ + return test_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status) || + test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status); +} + +static inline bool iwl_mvm_is_radio_hw_killed(struct iwl_mvm *mvm) +{ + return test_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); +} + +static inline bool iwl_mvm_firmware_running(struct iwl_mvm *mvm) +{ + return test_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); +} + +/* Must be called with rcu_read_lock() held and it can only be + * released when mvmsta is not needed anymore. + */ +static inline struct iwl_mvm_sta * +iwl_mvm_sta_from_staid_rcu(struct iwl_mvm *mvm, u8 sta_id) +{ + struct ieee80211_sta *sta; + + if (sta_id >= mvm->fw->ucode_capa.num_stations) + return NULL; + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + /* This can happen if the station has been removed right now */ + if (IS_ERR_OR_NULL(sta)) + return NULL; + + return iwl_mvm_sta_from_mac80211(sta); +} + +static inline struct iwl_mvm_sta * +iwl_mvm_sta_from_staid_protected(struct iwl_mvm *mvm, u8 sta_id) +{ + struct ieee80211_sta *sta; + + if (sta_id >= mvm->fw->ucode_capa.num_stations) + return NULL; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + /* This can happen if the station has been removed right now */ + if (IS_ERR_OR_NULL(sta)) + return NULL; + + return iwl_mvm_sta_from_mac80211(sta); +} + +static inline struct ieee80211_vif * +iwl_mvm_rcu_dereference_vif_id(struct iwl_mvm *mvm, u8 vif_id, bool rcu) +{ + if (WARN_ON(vif_id >= ARRAY_SIZE(mvm->vif_id_to_mac))) + return NULL; + + if (rcu) + return rcu_dereference(mvm->vif_id_to_mac[vif_id]); + + return rcu_dereference_protected(mvm->vif_id_to_mac[vif_id], + lockdep_is_held(&mvm->mutex)); +} + +static inline bool iwl_mvm_is_adaptive_dwell_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_ADAPTIVE_DWELL); +} + +static inline bool iwl_mvm_is_adaptive_dwell_v2_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_ADAPTIVE_DWELL_V2); +} + +static inline bool iwl_mvm_is_adwell_hb_ap_num_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_ADWELL_HB_DEF_N_AP); +} + +static inline bool iwl_mvm_is_oce_supported(struct iwl_mvm *mvm) +{ + /* OCE should never be enabled for LMAC scan FWs */ + return fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_OCE); +} + +static inline bool iwl_mvm_is_frag_ebs_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_FRAG_EBS); +} + +static inline bool iwl_mvm_is_short_beacon_notif_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_SHORT_BEACON_NOTIF); +} + +static inline bool iwl_mvm_is_dqa_data_queue(struct iwl_mvm *mvm, u8 queue) +{ + return (queue >= IWL_MVM_DQA_MIN_DATA_QUEUE) && + (queue <= IWL_MVM_DQA_MAX_DATA_QUEUE); +} + +static inline bool iwl_mvm_is_dqa_mgmt_queue(struct iwl_mvm *mvm, u8 queue) +{ + return (queue >= IWL_MVM_DQA_MIN_MGMT_QUEUE) && + (queue <= IWL_MVM_DQA_MAX_MGMT_QUEUE); +} + +static inline bool iwl_mvm_is_lar_supported(struct iwl_mvm *mvm) +{ + bool nvm_lar = mvm->nvm_data->lar_enabled; + bool tlv_lar = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT); + + /* + * Enable LAR only if it is supported by the FW (TLV) && + * enabled in the NVM + */ + if (mvm->cfg->nvm_type == IWL_NVM_EXT) + return nvm_lar && tlv_lar; + else + return tlv_lar; +} + +static inline bool iwl_mvm_is_wifi_mcc_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_WIFI_MCC_UPDATE) || + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LAR_MULTI_MCC); +} + +static inline bool iwl_mvm_bt_is_rrc_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BT_COEX_RRC) && + IWL_MVM_BT_COEX_RRC; +} + +static inline bool iwl_mvm_is_csum_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CSUM_SUPPORT) && + !IWL_MVM_HW_CSUM_DISABLE; +} + +static inline bool iwl_mvm_is_mplut_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BT_MPLUT_SUPPORT) && + IWL_MVM_BT_COEX_MPLUT; +} + +static inline +bool iwl_mvm_is_p2p_scm_uapsd_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_P2P_SCM_UAPSD) && + !(iwlwifi_mod_params.uapsd_disable & + IWL_DISABLE_UAPSD_P2P_CLIENT); +} + +static inline bool iwl_mvm_has_new_rx_api(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_MULTI_QUEUE_RX_SUPPORT); +} + +static inline bool iwl_mvm_has_new_tx_api(struct iwl_mvm *mvm) +{ + /* TODO - replace with TLV once defined */ + return mvm->trans->trans_cfg->use_tfh; +} + +static inline bool iwl_mvm_has_unified_ucode(struct iwl_mvm *mvm) +{ + /* TODO - better define this */ + return mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000; +} + +static inline bool iwl_mvm_is_cdb_supported(struct iwl_mvm *mvm) +{ + /* + * TODO: + * The issue of how to determine CDB APIs and usage is still not fully + * defined. + * There is a compilation for CDB and non-CDB FW, but there may + * be also runtime check. + * For now there is a TLV for checking compilation mode, but a + * runtime check will also have to be here - once defined. + */ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CDB_SUPPORT); +} + +static inline bool iwl_mvm_cdb_scan_api(struct iwl_mvm *mvm) +{ + /* + * TODO: should this be the same as iwl_mvm_is_cdb_supported()? + * but then there's a little bit of code in scan that won't make + * any sense... + */ + return mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000; +} + +static inline bool iwl_mvm_is_scan_ext_chan_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_SCAN_EXT_CHAN_VER); +} + + +static inline bool iwl_mvm_is_reduced_config_scan_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_REDUCED_SCAN_CONFIG); +} + +static inline bool iwl_mvm_is_band_in_rx_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_BAND_IN_RX_DATA); +} + +static inline bool iwl_mvm_has_new_rx_stats_api(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_NEW_RX_STATS); +} + +static inline bool iwl_mvm_has_quota_low_latency(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_QUOTA_LOW_LATENCY); +} + +static inline bool iwl_mvm_has_tlc_offload(const struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TLC_OFFLOAD); +} + +static inline struct agg_tx_status * +iwl_mvm_get_agg_status(struct iwl_mvm *mvm, void *tx_resp) +{ + if (iwl_mvm_has_new_tx_api(mvm)) + return &((struct iwl_mvm_tx_resp *)tx_resp)->status; + else + return ((struct iwl_mvm_tx_resp_v3 *)tx_resp)->status; +} + +static inline bool iwl_mvm_is_tt_in_fw(struct iwl_mvm *mvm) +{ + /* these two TLV are redundant since the responsibility to CT-kill by + * FW happens only after we send at least one command of + * temperature THs report. + */ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CT_KILL_BY_FW) && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TEMP_THS_REPORT_SUPPORT); +} + +static inline bool iwl_mvm_is_ctdp_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CTDP_SUPPORT); +} + +extern const u8 iwl_mvm_ac_to_tx_fifo[]; +extern const u8 iwl_mvm_ac_to_gen2_tx_fifo[]; + +static inline u8 iwl_mvm_mac_ac_to_tx_fifo(struct iwl_mvm *mvm, + enum ieee80211_ac_numbers ac) +{ + return iwl_mvm_has_new_tx_api(mvm) ? + iwl_mvm_ac_to_gen2_tx_fifo[ac] : iwl_mvm_ac_to_tx_fifo[ac]; +} + +struct iwl_rate_info { + u8 plcp; /* uCode API: IWL_RATE_6M_PLCP, etc. */ + u8 plcp_siso; /* uCode API: IWL_RATE_SISO_6M_PLCP, etc. */ + u8 plcp_mimo2; /* uCode API: IWL_RATE_MIMO2_6M_PLCP, etc. */ + u8 plcp_mimo3; /* uCode API: IWL_RATE_MIMO3_6M_PLCP, etc. */ + u8 ieee; /* MAC header: IWL_RATE_6M_IEEE, etc. */ +}; + +void __iwl_mvm_mac_stop(struct iwl_mvm *mvm); +int __iwl_mvm_mac_start(struct iwl_mvm *mvm); + +/****************** + * MVM Methods + ******************/ +/* uCode */ +int iwl_run_init_mvm_ucode(struct iwl_mvm *mvm); + +/* Utils */ +int iwl_mvm_legacy_hw_idx_to_mac80211_idx(u32 rate_n_flags, + enum nl80211_band band); +int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags, + enum nl80211_band band); +void iwl_mvm_hwrate_to_tx_rate(u32 rate_n_flags, + enum nl80211_band band, + struct ieee80211_tx_rate *r); +void iwl_mvm_hwrate_to_tx_rate_v1(u32 rate_n_flags, + enum nl80211_band band, + struct ieee80211_tx_rate *r); +u8 iwl_mvm_mac80211_idx_to_hwrate(const struct iwl_fw *fw, int rate_idx); +u8 iwl_mvm_mac80211_ac_to_ucode_ac(enum ieee80211_ac_numbers ac); + +static inline void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) +{ + iwl_fwrt_dump_error_logs(&mvm->fwrt); +} + +u8 first_antenna(u8 mask); +u8 iwl_mvm_next_antenna(struct iwl_mvm *mvm, u8 valid, u8 last_idx); +void iwl_mvm_get_sync_time(struct iwl_mvm *mvm, int clock_type, u32 *gp2, + u64 *boottime, ktime_t *realtime); +u32 iwl_mvm_get_systime(struct iwl_mvm *mvm); + +/* Tx / Host Commands */ +int __must_check iwl_mvm_send_cmd(struct iwl_mvm *mvm, + struct iwl_host_cmd *cmd); +int __must_check iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, + u32 flags, u16 len, const void *data); +int __must_check iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, + struct iwl_host_cmd *cmd, + u32 *status); +int __must_check iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, + u16 len, const void *data, + u32 *status); +int iwl_mvm_tx_skb_sta(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_sta *sta); +int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb); +void iwl_mvm_set_tx_cmd(struct iwl_mvm *mvm, struct sk_buff *skb, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, u8 sta_id); +void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm *mvm, struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc); +void iwl_mvm_mac_itxq_xmit(struct ieee80211_hw *hw, struct ieee80211_txq *txq); +unsigned int iwl_mvm_max_amsdu_size(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + unsigned int tid); + +#ifdef CONFIG_IWLWIFI_DEBUG +const char *iwl_mvm_get_tx_fail_reason(u32 status); +#else +static inline const char *iwl_mvm_get_tx_fail_reason(u32 status) { return ""; } +#endif +int iwl_mvm_flush_tx_path(struct iwl_mvm *mvm, u32 tfd_msk); +int iwl_mvm_flush_sta(struct iwl_mvm *mvm, void *sta, bool internal); +int iwl_mvm_flush_sta_tids(struct iwl_mvm *mvm, u32 sta_id, u16 tids); + +void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm); + +static inline void iwl_mvm_set_tx_cmd_ccmp(struct ieee80211_tx_info *info, + struct iwl_tx_cmd *tx_cmd) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + + tx_cmd->sec_ctl = TX_CMD_SEC_CCM; + memcpy(tx_cmd->key, keyconf->key, keyconf->keylen); +} + +static inline void iwl_mvm_wait_for_async_handlers(struct iwl_mvm *mvm) +{ + flush_work(&mvm->async_handlers_wk); +} + +/* Statistics */ +void iwl_mvm_handle_rx_statistics(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt); +void iwl_mvm_rx_statistics(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +int iwl_mvm_request_statistics(struct iwl_mvm *mvm, bool clear); +void iwl_mvm_accu_radio_stats(struct iwl_mvm *mvm); + +/* NVM */ +int iwl_nvm_init(struct iwl_mvm *mvm); +int iwl_mvm_load_nvm_to_nic(struct iwl_mvm *mvm); + +static inline u8 iwl_mvm_get_valid_tx_ant(struct iwl_mvm *mvm) +{ + return mvm->nvm_data && mvm->nvm_data->valid_tx_ant ? + mvm->fw->valid_tx_ant & mvm->nvm_data->valid_tx_ant : + mvm->fw->valid_tx_ant; +} + +static inline u8 iwl_mvm_get_valid_rx_ant(struct iwl_mvm *mvm) +{ + return mvm->nvm_data && mvm->nvm_data->valid_rx_ant ? + mvm->fw->valid_rx_ant & mvm->nvm_data->valid_rx_ant : + mvm->fw->valid_rx_ant; +} + +static inline void iwl_mvm_toggle_tx_ant(struct iwl_mvm *mvm, u8 *ant) +{ + *ant = iwl_mvm_next_antenna(mvm, iwl_mvm_get_valid_tx_ant(mvm), *ant); +} + +static inline u32 iwl_mvm_get_phy_config(struct iwl_mvm *mvm) +{ + u32 phy_config = ~(FW_PHY_CFG_TX_CHAIN | + FW_PHY_CFG_RX_CHAIN); + u32 valid_rx_ant = iwl_mvm_get_valid_rx_ant(mvm); + u32 valid_tx_ant = iwl_mvm_get_valid_tx_ant(mvm); + + phy_config |= valid_tx_ant << FW_PHY_CFG_TX_CHAIN_POS | + valid_rx_ant << FW_PHY_CFG_RX_CHAIN_POS; + + return mvm->fw->phy_config & phy_config; +} + +int iwl_mvm_up(struct iwl_mvm *mvm); +int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm); + +int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm); +bool iwl_mvm_bcast_filter_build_cmd(struct iwl_mvm *mvm, + struct iwl_bcast_filter_cmd *cmd); + +/* + * FW notifications / CMD responses handlers + * Convention: iwl_mvm_rx_ + */ +void iwl_mvm_rx_mq(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_rx_phy_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_monitor_no_data(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_frame_release(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_bar_frame_release(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_queue_notif(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_mfu_assert_dump_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_send_recovery_cmd(struct iwl_mvm *mvm, u32 flags); +void iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_ant_coupling_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_fw_error(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_card_state_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_mfuart_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_shared_mem_cfg_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/* MVM PHY */ +int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic); +int iwl_mvm_phy_ctxt_changed(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic); +void iwl_mvm_phy_ctxt_ref(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt); +void iwl_mvm_phy_ctxt_unref(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt); +int iwl_mvm_phy_ctx_count(struct iwl_mvm *mvm); +u8 iwl_mvm_get_channel_width(struct cfg80211_chan_def *chandef); +u8 iwl_mvm_get_ctrl_pos(struct cfg80211_chan_def *chandef); + +/* MAC (virtual interface) programming */ +int iwl_mvm_mac_ctxt_init(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_add(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool force_assoc_off, const u8 *bssid_override); +int iwl_mvm_mac_ctxt_remove(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_beacon_changed(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_send_beacon(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon); +int iwl_mvm_mac_ctxt_send_beacon_cmd(struct iwl_mvm *mvm, + struct sk_buff *beacon, + void *data, int len); +u8 iwl_mvm_mac_ctxt_get_lowest_rate(struct ieee80211_tx_info *info, + struct ieee80211_vif *vif); +u16 iwl_mvm_mac_ctxt_get_beacon_flags(const struct iwl_fw *fw, + u8 rate_idx); +void iwl_mvm_mac_ctxt_set_tim(struct iwl_mvm *mvm, + __le32 *tim_index, __le32 *tim_size, + u8 *beacon, u32 frame_size); +void iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_missed_beacons_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_stored_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_mu_mimo_grp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_sta_pm_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_window_status_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_mac_ctxt_recalc_tsf_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +void iwl_mvm_probe_resp_data_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_missed_vap_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_channel_switch_start_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +/* Bindings */ +int iwl_mvm_binding_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_binding_remove_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); + +/* Quota management */ +static inline size_t iwl_mvm_quota_cmd_size(struct iwl_mvm *mvm) +{ + return iwl_mvm_has_quota_low_latency(mvm) ? + sizeof(struct iwl_time_quota_cmd) : + sizeof(struct iwl_time_quota_cmd_v1); +} + +static inline struct iwl_time_quota_data +*iwl_mvm_quota_cmd_get_quota(struct iwl_mvm *mvm, + struct iwl_time_quota_cmd *cmd, + int i) +{ + struct iwl_time_quota_data_v1 *quotas; + + if (iwl_mvm_has_quota_low_latency(mvm)) + return &cmd->quotas[i]; + + quotas = (struct iwl_time_quota_data_v1 *)cmd->quotas; + return (struct iwl_time_quota_data *)"as[i]; +} + +int iwl_mvm_update_quotas(struct iwl_mvm *mvm, bool force_upload, + struct ieee80211_vif *disabled_vif); + +/* Scanning */ +int iwl_mvm_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_scan_request *req, + struct ieee80211_scan_ies *ies); +int iwl_mvm_scan_size(struct iwl_mvm *mvm); +int iwl_mvm_scan_stop(struct iwl_mvm *mvm, int type, bool notify); +int iwl_mvm_max_scan_ie_len(struct iwl_mvm *mvm); +void iwl_mvm_report_scan_aborted(struct iwl_mvm *mvm); +void iwl_mvm_scan_timeout_wk(struct work_struct *work); + +/* Scheduled scan */ +void iwl_mvm_rx_lmac_scan_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_lmac_scan_iter_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +int iwl_mvm_sched_scan_start(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_scan_ies *ies, + int type); +void iwl_mvm_rx_scan_match_found(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/* UMAC scan */ +int iwl_mvm_config_scan(struct iwl_mvm *mvm); +void iwl_mvm_rx_umac_scan_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_umac_scan_iter_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/* MVM debugfs */ +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_mvm_dbgfs_register(struct iwl_mvm *mvm); +void iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_vif_dbgfs_clean(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +#else +static inline void iwl_mvm_dbgfs_register(struct iwl_mvm *mvm) +{ +} +static inline void +iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +} +static inline void +iwl_mvm_vif_dbgfs_clean(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +} +#endif /* CONFIG_IWLWIFI_DEBUGFS */ + +/* rate scaling */ +int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq); +void iwl_mvm_update_frame_stats(struct iwl_mvm *mvm, u32 rate, bool agg); +int rs_pretty_print_rate_v1(char *buf, int bufsz, const u32 rate); +void rs_update_last_rssi(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, + struct ieee80211_rx_status *rx_status); + +/* power management */ +int iwl_mvm_power_update_device(struct iwl_mvm *mvm); +int iwl_mvm_power_update_mac(struct iwl_mvm *mvm); +int iwl_mvm_power_update_ps(struct iwl_mvm *mvm); +int iwl_mvm_power_mac_dbgfs_read(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + char *buf, int bufsz); + +void iwl_mvm_power_vif_assoc(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_power_uapsd_misbehaving_ap_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +#ifdef CONFIG_IWLWIFI_LEDS +int iwl_mvm_leds_init(struct iwl_mvm *mvm); +void iwl_mvm_leds_exit(struct iwl_mvm *mvm); +void iwl_mvm_leds_sync(struct iwl_mvm *mvm); +#else +static inline int iwl_mvm_leds_init(struct iwl_mvm *mvm) +{ + return 0; +} +static inline void iwl_mvm_leds_exit(struct iwl_mvm *mvm) +{ +} +static inline void iwl_mvm_leds_sync(struct iwl_mvm *mvm) +{ +} +#endif + +/* D3 (WoWLAN, NetDetect) */ +int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan); +int iwl_mvm_resume(struct ieee80211_hw *hw); +void iwl_mvm_set_wakeup(struct ieee80211_hw *hw, bool enabled); +void iwl_mvm_set_rekey_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_gtk_rekey_data *data); +void iwl_mvm_ipv6_addr_change(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct inet6_dev *idev); +void iwl_mvm_set_default_unicast_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, int idx); +extern const struct file_operations iwl_dbgfs_d3_test_ops; +#ifdef CONFIG_PM +void iwl_mvm_set_last_nonqos_seq(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +#else +static inline void +iwl_mvm_set_last_nonqos_seq(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +} +#endif +void iwl_mvm_set_wowlan_qos_seq(struct iwl_mvm_sta *mvm_ap_sta, + struct iwl_wowlan_config_cmd *cmd); +int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool disable_offloading, + bool offload_ns, + u32 cmd_flags); + +/* BT Coex */ +int iwl_mvm_send_bt_init_conf(struct iwl_mvm *mvm); +void iwl_mvm_rx_bt_coex_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_bt_rssi_event(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum ieee80211_rssi_event_data); +void iwl_mvm_bt_coex_vif_change(struct iwl_mvm *mvm); +u16 iwl_mvm_coex_agg_time_limit(struct iwl_mvm *mvm, + struct ieee80211_sta *sta); +bool iwl_mvm_bt_coex_is_mimo_allowed(struct iwl_mvm *mvm, + struct ieee80211_sta *sta); +bool iwl_mvm_bt_coex_is_ant_avail(struct iwl_mvm *mvm, u8 ant); +bool iwl_mvm_bt_coex_is_shared_ant_avail(struct iwl_mvm *mvm); +bool iwl_mvm_bt_coex_is_tpc_allowed(struct iwl_mvm *mvm, + enum nl80211_band band); +u8 iwl_mvm_bt_coex_get_single_ant_msk(struct iwl_mvm *mvm, u8 enabled_ants); +u8 iwl_mvm_bt_coex_tx_prio(struct iwl_mvm *mvm, struct ieee80211_hdr *hdr, + struct ieee80211_tx_info *info, u8 ac); + +/* beacon filtering */ +#ifdef CONFIG_IWLWIFI_DEBUGFS +void +iwl_mvm_beacon_filter_debugfs_parameters(struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd); +#else +static inline void +iwl_mvm_beacon_filter_debugfs_parameters(struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd) +{} +#endif +int iwl_mvm_enable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags); +int iwl_mvm_disable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags); +/* SMPS */ +void iwl_mvm_update_smps(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum iwl_mvm_smps_type_request req_type, + enum ieee80211_smps_mode smps_request); +bool iwl_mvm_rx_diversity_allowed(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt); +void iwl_mvm_apply_fw_smps_request(struct ieee80211_vif *vif); + +/* Low latency */ +int iwl_mvm_update_low_latency(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool low_latency, + enum iwl_mvm_low_latency_cause cause); +/* get SystemLowLatencyMode - only needed for beacon threshold? */ +bool iwl_mvm_low_latency(struct iwl_mvm *mvm); +bool iwl_mvm_low_latency_band(struct iwl_mvm *mvm, enum nl80211_band band); +void iwl_mvm_send_low_latency_cmd(struct iwl_mvm *mvm, bool low_latency, + u16 mac_id); + +/* get VMACLowLatencyMode */ +static inline bool iwl_mvm_vif_low_latency(struct iwl_mvm_vif *mvmvif) +{ + /* + * should this consider associated/active/... state? + * + * Normally low-latency should only be active on interfaces + * that are active, but at least with debugfs it can also be + * enabled on interfaces that aren't active. However, when + * interface aren't active then they aren't added into the + * binding, so this has no real impact. For now, just return + * the current desired low-latency state. + */ + return mvmvif->low_latency_actual; +} + +static inline +void iwl_mvm_vif_set_low_latency(struct iwl_mvm_vif *mvmvif, bool set, + enum iwl_mvm_low_latency_cause cause) +{ + u8 new_state; + + if (set) + mvmvif->low_latency |= cause; + else + mvmvif->low_latency &= ~cause; + + /* + * if LOW_LATENCY_DEBUGFS_FORCE_ENABLE is enabled no changes are + * allowed to actual mode. + */ + if (mvmvif->low_latency & LOW_LATENCY_DEBUGFS_FORCE_ENABLE && + cause != LOW_LATENCY_DEBUGFS_FORCE_ENABLE) + return; + + if (cause == LOW_LATENCY_DEBUGFS_FORCE_ENABLE && set) + /* + * We enter force state + */ + new_state = !!(mvmvif->low_latency & + LOW_LATENCY_DEBUGFS_FORCE); + else + /* + * Check if any other one set low latency + */ + new_state = !!(mvmvif->low_latency & + ~(LOW_LATENCY_DEBUGFS_FORCE_ENABLE | + LOW_LATENCY_DEBUGFS_FORCE)); + + mvmvif->low_latency_actual = new_state; +} + +/* Return a bitmask with all the hw supported queues, except for the + * command queue, which can't be flushed. + */ +static inline u32 iwl_mvm_flushable_queues(struct iwl_mvm *mvm) +{ + return ((BIT(mvm->trans->trans_cfg->base_params->num_of_queues) - 1) & + ~BIT(IWL_MVM_DQA_CMD_QUEUE)); +} + +void iwl_mvm_stop_device(struct iwl_mvm *mvm); + +/* Re-configure the SCD for a queue that has already been configured */ +int iwl_mvm_reconfig_scd(struct iwl_mvm *mvm, int queue, int fifo, int sta_id, + int tid, int frame_limit, u16 ssn); + +/* Thermal management and CT-kill */ +void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff); +void iwl_mvm_temp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_tt_handler(struct iwl_mvm *mvm); +void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff); +void iwl_mvm_thermal_exit(struct iwl_mvm *mvm); +void iwl_mvm_set_hw_ctkill_state(struct iwl_mvm *mvm, bool state); +int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp); +void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm); +int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm); +int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 budget); + +/* Location Aware Regulatory */ +struct iwl_mcc_update_resp * +iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, + enum iwl_mcc_source src_id); +int iwl_mvm_init_mcc(struct iwl_mvm *mvm); +void iwl_mvm_rx_chub_update_mcc(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +struct ieee80211_regdomain *iwl_mvm_get_regdomain(struct wiphy *wiphy, + const char *alpha2, + enum iwl_mcc_source src_id, + bool *changed); +struct ieee80211_regdomain *iwl_mvm_get_current_regdomain(struct iwl_mvm *mvm, + bool *changed); +int iwl_mvm_init_fw_regd(struct iwl_mvm *mvm); +void iwl_mvm_update_changed_regdom(struct iwl_mvm *mvm); + +/* smart fifo */ +int iwl_mvm_sf_update(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool added_vif); + +/* FTM responder */ +int iwl_mvm_ftm_start_responder(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_ftm_restart_responder(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +void iwl_mvm_ftm_responder_stats(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +int iwl_mvm_ftm_resp_remove_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, u8 *addr); +int iwl_mvm_ftm_respoder_add_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 *addr, u32 cipher, u8 *tk, u32 tk_len, + u8 *hltk, u32 hltk_len); +void iwl_mvm_ftm_responder_clear(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); + +/* FTM initiator */ +void iwl_mvm_ftm_restart(struct iwl_mvm *mvm); +void iwl_mvm_ftm_range_resp(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_ftm_lc_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +int iwl_mvm_ftm_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *request); +void iwl_mvm_ftm_abort(struct iwl_mvm *mvm, struct cfg80211_pmsr_request *req); +void iwl_mvm_ftm_initiator_smooth_config(struct iwl_mvm *mvm); +void iwl_mvm_ftm_initiator_smooth_stop(struct iwl_mvm *mvm); +int iwl_mvm_ftm_add_pasn_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + u8 *addr, u32 cipher, u8 *tk, u32 tk_len, + u8 *hltk, u32 hltk_len); +void iwl_mvm_ftm_remove_pasn_sta(struct iwl_mvm *mvm, u8 *addr); + +/* TDLS */ + +/* + * We use TID 4 (VI) as a FW-used-only TID when TDLS connections are present. + * This TID is marked as used vs the AP and all connected TDLS peers. + */ +#define IWL_MVM_TDLS_FW_TID 4 + +int iwl_mvm_tdls_sta_count(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_teardown_tdls_peers(struct iwl_mvm *mvm); +void iwl_mvm_recalc_tdls_state(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool sta_added); +void iwl_mvm_mac_mgd_protect_tdls_discover(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); +int iwl_mvm_tdls_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u8 oper_class, + struct cfg80211_chan_def *chandef, + struct sk_buff *tmpl_skb, u32 ch_sw_tm_ie); +void iwl_mvm_tdls_recv_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_tdls_ch_sw_params *params); +void iwl_mvm_tdls_cancel_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +void iwl_mvm_rx_tdls_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_tdls_ch_switch_work(struct work_struct *work); + +void iwl_mvm_sync_rx_queues_internal(struct iwl_mvm *mvm, + enum iwl_mvm_rxq_notif_type type, + bool sync, + const void *data, u32 size); +void iwl_mvm_reorder_timer_expired(struct timer_list *t); +struct ieee80211_vif *iwl_mvm_get_bss_vif(struct iwl_mvm *mvm); +struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid); +bool iwl_mvm_is_vif_assoc(struct iwl_mvm *mvm); + +#define MVM_TCM_PERIOD_MSEC 500 +#define MVM_TCM_PERIOD (HZ * MVM_TCM_PERIOD_MSEC / 1000) +#define MVM_LL_PERIOD (10 * HZ) +void iwl_mvm_tcm_work(struct work_struct *work); +void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm); +void iwl_mvm_pause_tcm(struct iwl_mvm *mvm, bool with_cancel); +void iwl_mvm_resume_tcm(struct iwl_mvm *mvm); +void iwl_mvm_tcm_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_tcm_rm_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +u8 iwl_mvm_tcm_load_percentage(u32 airtime, u32 elapsed); + +void iwl_mvm_nic_restart(struct iwl_mvm *mvm, bool fw_error); +unsigned int iwl_mvm_get_wd_timeout(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool tdls, bool cmd_q); +void iwl_mvm_connection_loss(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + const char *errmsg); +void iwl_mvm_event_frame_timeout_callback(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_sta *sta, + u16 tid); + +int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, int prof_a, int prof_b); +int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm); +int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm); +void iwl_mvm_get_acpi_tables(struct iwl_mvm *mvm); +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_mvm_sta_add_debugfs(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct dentry *dir); +#endif + +int iwl_rfi_send_config_cmd(struct iwl_mvm *mvm, + struct iwl_rfi_lut_entry *rfi_table); +struct iwl_rfi_freq_table_resp_cmd *iwl_rfi_get_freq_table(struct iwl_mvm *mvm); + +static inline u8 iwl_mvm_phy_band_from_nl80211(enum nl80211_band band) +{ + switch (band) { + case NL80211_BAND_2GHZ: + return PHY_BAND_24; + case NL80211_BAND_5GHZ: + return PHY_BAND_5; + case NL80211_BAND_6GHZ: + return PHY_BAND_6; + default: + WARN_ONCE(1, "Unsupported band (%u)\n", band); + return PHY_BAND_5; + } +} + +/* Channel info utils */ +static inline bool iwl_mvm_has_ultra_hb_channel(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS); +} + +static inline void *iwl_mvm_chan_info_cmd_tail(struct iwl_mvm *mvm, + struct iwl_fw_channel_info *ci) +{ + return (u8 *)ci + (iwl_mvm_has_ultra_hb_channel(mvm) ? + sizeof(struct iwl_fw_channel_info) : + sizeof(struct iwl_fw_channel_info_v1)); +} + +static inline size_t iwl_mvm_chan_info_padding(struct iwl_mvm *mvm) +{ + return iwl_mvm_has_ultra_hb_channel(mvm) ? 0 : + sizeof(struct iwl_fw_channel_info) - + sizeof(struct iwl_fw_channel_info_v1); +} + +static inline void iwl_mvm_set_chan_info(struct iwl_mvm *mvm, + struct iwl_fw_channel_info *ci, + u32 chan, u8 band, u8 width, + u8 ctrl_pos) +{ + if (iwl_mvm_has_ultra_hb_channel(mvm)) { + ci->channel = cpu_to_le32(chan); + ci->band = band; + ci->width = width; + ci->ctrl_pos = ctrl_pos; + } else { + struct iwl_fw_channel_info_v1 *ci_v1 = + (struct iwl_fw_channel_info_v1 *)ci; + + ci_v1->channel = chan; + ci_v1->band = band; + ci_v1->width = width; + ci_v1->ctrl_pos = ctrl_pos; + } +} + +static inline void +iwl_mvm_set_chan_info_chandef(struct iwl_mvm *mvm, + struct iwl_fw_channel_info *ci, + struct cfg80211_chan_def *chandef) +{ + enum nl80211_band band = chandef->chan->band; + + iwl_mvm_set_chan_info(mvm, ci, chandef->chan->hw_value, + iwl_mvm_phy_band_from_nl80211(band), + iwl_mvm_get_channel_width(chandef), + iwl_mvm_get_ctrl_pos(chandef)); +} + +static inline int iwl_umac_scan_get_max_profiles(const struct iwl_fw *fw) +{ + u8 ver = iwl_fw_lookup_cmd_ver(fw, IWL_ALWAYS_LONG_GROUP, + SCAN_OFFLOAD_UPDATE_PROFILES_CMD, + IWL_FW_CMD_VER_UNKNOWN); + return (ver == IWL_FW_CMD_VER_UNKNOWN || ver < 3) ? + IWL_SCAN_MAX_PROFILES : IWL_SCAN_MAX_PROFILES_V2; +} + +static inline +enum iwl_location_cipher iwl_mvm_cipher_to_location_cipher(u32 cipher) +{ + switch (cipher) { + case WLAN_CIPHER_SUITE_CCMP: + return IWL_LOCATION_CIPHER_CCMP_128; + case WLAN_CIPHER_SUITE_GCMP: + return IWL_LOCATION_CIPHER_GCMP_128; + case WLAN_CIPHER_SUITE_GCMP_256: + return IWL_LOCATION_CIPHER_GCMP_256; + default: + return IWL_LOCATION_CIPHER_INVALID; + } +} +#endif /* __IWL_MVM_H__ */ diff --git a/sys/contrib/dev/iwlwifi/mvm/nvm.c b/sys/contrib/dev/iwlwifi/mvm/nvm.c new file mode 100644 index 00000000000..43f302450d3 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/nvm.c @@ -0,0 +1,598 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#if defined(__linux__) +#include +#endif +#include "iwl-trans.h" +#include "iwl-csr.h" +#include "mvm.h" +#include "iwl-eeprom-parse.h" +#include "iwl-eeprom-read.h" +#include "iwl-nvm-parse.h" +#include "iwl-prph.h" +#include "fw/acpi.h" + +/* Default NVM size to read */ +#define IWL_NVM_DEFAULT_CHUNK_SIZE (2 * 1024) + +#define NVM_WRITE_OPCODE 1 +#define NVM_READ_OPCODE 0 + +/* load nvm chunk response */ +enum { + READ_NVM_CHUNK_SUCCEED = 0, + READ_NVM_CHUNK_NOT_VALID_ADDRESS = 1 +}; + +/* + * prepare the NVM host command w/ the pointers to the nvm buffer + * and send it to fw + */ +static int iwl_nvm_write_chunk(struct iwl_mvm *mvm, u16 section, + u16 offset, u16 length, const u8 *data) +{ + struct iwl_nvm_access_cmd nvm_access_cmd = { + .offset = cpu_to_le16(offset), + .length = cpu_to_le16(length), + .type = cpu_to_le16(section), + .op_code = NVM_WRITE_OPCODE, + }; + struct iwl_host_cmd cmd = { + .id = NVM_ACCESS_CMD, + .len = { sizeof(struct iwl_nvm_access_cmd), length }, + .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + .data = { &nvm_access_cmd, data }, + /* data may come from vmalloc, so use _DUP */ + .dataflags = { 0, IWL_HCMD_DFL_DUP }, + }; + struct iwl_rx_packet *pkt; + struct iwl_nvm_access_resp *nvm_resp; + int ret; + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) + return ret; + + pkt = cmd.resp_pkt; + /* Extract & check NVM write response */ + nvm_resp = (void *)pkt->data; + if (le16_to_cpu(nvm_resp->status) != READ_NVM_CHUNK_SUCCEED) { + IWL_ERR(mvm, + "NVM access write command failed for section %u (status = 0x%x)\n", + section, le16_to_cpu(nvm_resp->status)); + ret = -EIO; + } + + iwl_free_resp(&cmd); + return ret; +} + +static int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, + u16 offset, u16 length, u8 *data) +{ + struct iwl_nvm_access_cmd nvm_access_cmd = { + .offset = cpu_to_le16(offset), + .length = cpu_to_le16(length), + .type = cpu_to_le16(section), + .op_code = NVM_READ_OPCODE, + }; + struct iwl_nvm_access_resp *nvm_resp; + struct iwl_rx_packet *pkt; + struct iwl_host_cmd cmd = { + .id = NVM_ACCESS_CMD, + .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + .data = { &nvm_access_cmd, }, + }; + int ret, bytes_read, offset_read; + u8 *resp_data; + + cmd.len[0] = sizeof(struct iwl_nvm_access_cmd); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) + return ret; + + pkt = cmd.resp_pkt; + + /* Extract NVM response */ + nvm_resp = (void *)pkt->data; + ret = le16_to_cpu(nvm_resp->status); + bytes_read = le16_to_cpu(nvm_resp->length); + offset_read = le16_to_cpu(nvm_resp->offset); + resp_data = nvm_resp->data; + if (ret) { + if ((offset != 0) && + (ret == READ_NVM_CHUNK_NOT_VALID_ADDRESS)) { + /* + * meaning of NOT_VALID_ADDRESS: + * driver try to read chunk from address that is + * multiple of 2K and got an error since addr is empty. + * meaning of (offset != 0): driver already + * read valid data from another chunk so this case + * is not an error. + */ + IWL_DEBUG_EEPROM(mvm->trans->dev, + "NVM access command failed on offset 0x%x since that section size is multiple 2K\n", + offset); + ret = 0; + } else { + IWL_DEBUG_EEPROM(mvm->trans->dev, + "NVM access command failed with status %d (device: %s)\n", + ret, mvm->trans->name); + ret = -ENODATA; + } + goto exit; + } + + if (offset_read != offset) { + IWL_ERR(mvm, "NVM ACCESS response with invalid offset %d\n", + offset_read); + ret = -EINVAL; + goto exit; + } + + /* Write data to NVM */ + memcpy(data + offset, resp_data, bytes_read); + ret = bytes_read; + +exit: + iwl_free_resp(&cmd); + return ret; +} + +static int iwl_nvm_write_section(struct iwl_mvm *mvm, u16 section, + const u8 *data, u16 length) +{ + int offset = 0; + + /* copy data in chunks of 2k (and remainder if any) */ + + while (offset < length) { + int chunk_size, ret; + + chunk_size = min(IWL_NVM_DEFAULT_CHUNK_SIZE, + length - offset); + + ret = iwl_nvm_write_chunk(mvm, section, offset, + chunk_size, data + offset); + if (ret < 0) + return ret; + + offset += chunk_size; + } + + return 0; +} + +/* + * Reads an NVM section completely. + * NICs prior to 7000 family doesn't have a real NVM, but just read + * section 0 which is the EEPROM. Because the EEPROM reading is unlimited + * by uCode, we need to manually check in this case that we don't + * overflow and try to read more than the EEPROM size. + * For 7000 family NICs, we supply the maximal size we can read, and + * the uCode fills the response with as much data as we can, + * without overflowing, so no check is needed. + */ +static int iwl_nvm_read_section(struct iwl_mvm *mvm, u16 section, + u8 *data, u32 size_read) +{ + u16 length, offset = 0; + int ret; + + /* Set nvm section read length */ + length = IWL_NVM_DEFAULT_CHUNK_SIZE; + + ret = length; + + /* Read the NVM until exhausted (reading less than requested) */ + while (ret == length) { + /* Check no memory assumptions fail and cause an overflow */ + if ((size_read + offset + length) > + mvm->trans->trans_cfg->base_params->eeprom_size) { + IWL_ERR(mvm, "EEPROM size is too small for NVM\n"); + return -ENOBUFS; + } + + ret = iwl_nvm_read_chunk(mvm, section, offset, length, data); + if (ret < 0) { + IWL_DEBUG_EEPROM(mvm->trans->dev, + "Cannot read NVM from section %d offset %d, length %d\n", + section, offset, length); + return ret; + } + offset += ret; + } + + iwl_nvm_fixups(mvm->trans->hw_id, section, data, offset); + + IWL_DEBUG_EEPROM(mvm->trans->dev, + "NVM section %d read completed\n", section); + return offset; +} + +static struct iwl_nvm_data * +iwl_parse_nvm_sections(struct iwl_mvm *mvm) +{ + struct iwl_nvm_section *sections = mvm->nvm_sections; + const __be16 *hw; + const __le16 *sw, *calib, *regulatory, *mac_override, *phy_sku; + int regulatory_type; + + /* Checking for required sections */ + if (mvm->trans->cfg->nvm_type == IWL_NVM) { + if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || + !mvm->nvm_sections[mvm->cfg->nvm_hw_section_num].data) { + IWL_ERR(mvm, "Can't parse empty OTP/NVM sections\n"); + return NULL; + } + } else { + if (mvm->trans->cfg->nvm_type == IWL_NVM_SDP) + regulatory_type = NVM_SECTION_TYPE_REGULATORY_SDP; + else + regulatory_type = NVM_SECTION_TYPE_REGULATORY; + + /* SW and REGULATORY sections are mandatory */ + if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || + !mvm->nvm_sections[regulatory_type].data) { + IWL_ERR(mvm, + "Can't parse empty family 8000 OTP/NVM sections\n"); + return NULL; + } + /* MAC_OVERRIDE or at least HW section must exist */ + if (!mvm->nvm_sections[mvm->cfg->nvm_hw_section_num].data && + !mvm->nvm_sections[NVM_SECTION_TYPE_MAC_OVERRIDE].data) { + IWL_ERR(mvm, + "Can't parse mac_address, empty sections\n"); + return NULL; + } + + /* PHY_SKU section is mandatory in B0 */ + if (mvm->trans->cfg->nvm_type == IWL_NVM_EXT && + !mvm->nvm_sections[NVM_SECTION_TYPE_PHY_SKU].data) { + IWL_ERR(mvm, + "Can't parse phy_sku in B0, empty sections\n"); + return NULL; + } + } + + hw = (const __be16 *)sections[mvm->cfg->nvm_hw_section_num].data; + sw = (const __le16 *)sections[NVM_SECTION_TYPE_SW].data; + calib = (const __le16 *)sections[NVM_SECTION_TYPE_CALIBRATION].data; + mac_override = + (const __le16 *)sections[NVM_SECTION_TYPE_MAC_OVERRIDE].data; + phy_sku = (const __le16 *)sections[NVM_SECTION_TYPE_PHY_SKU].data; + + regulatory = mvm->trans->cfg->nvm_type == IWL_NVM_SDP ? + (const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY_SDP].data : + (const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY].data; + + return iwl_parse_nvm_data(mvm->trans, mvm->cfg, mvm->fw, hw, sw, calib, + regulatory, mac_override, phy_sku, + mvm->fw->valid_tx_ant, mvm->fw->valid_rx_ant); +} + +/* Loads the NVM data stored in mvm->nvm_sections into the NIC */ +int iwl_mvm_load_nvm_to_nic(struct iwl_mvm *mvm) +{ + int i, ret = 0; + struct iwl_nvm_section *sections = mvm->nvm_sections; + + IWL_DEBUG_EEPROM(mvm->trans->dev, "'Write to NVM\n"); + + for (i = 0; i < ARRAY_SIZE(mvm->nvm_sections); i++) { + if (!mvm->nvm_sections[i].data || !mvm->nvm_sections[i].length) + continue; + ret = iwl_nvm_write_section(mvm, i, sections[i].data, + sections[i].length); + if (ret < 0) { + IWL_ERR(mvm, "iwl_mvm_send_cmd failed: %d\n", ret); + break; + } + } + return ret; +} + +int iwl_nvm_init(struct iwl_mvm *mvm) +{ + int ret, section; + u32 size_read = 0; + u8 *nvm_buffer, *temp; + const char *nvm_file_C = mvm->cfg->default_nvm_file_C_step; + + if (WARN_ON_ONCE(mvm->cfg->nvm_hw_section_num >= NVM_MAX_NUM_SECTIONS)) + return -EINVAL; + + /* load NVM values from nic */ + /* Read From FW NVM */ + IWL_DEBUG_EEPROM(mvm->trans->dev, "Read from NVM\n"); + + nvm_buffer = kmalloc(mvm->trans->trans_cfg->base_params->eeprom_size, + GFP_KERNEL); + if (!nvm_buffer) + return -ENOMEM; + for (section = 0; section < NVM_MAX_NUM_SECTIONS; section++) { + /* we override the constness for initial read */ + ret = iwl_nvm_read_section(mvm, section, nvm_buffer, + size_read); + if (ret == -ENODATA) { + ret = 0; + continue; + } + if (ret < 0) + break; + size_read += ret; + temp = kmemdup(nvm_buffer, ret, GFP_KERNEL); + if (!temp) { + ret = -ENOMEM; + break; + } + + iwl_nvm_fixups(mvm->trans->hw_id, section, temp, ret); + + mvm->nvm_sections[section].data = temp; + mvm->nvm_sections[section].length = ret; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + switch (section) { + case NVM_SECTION_TYPE_SW: + mvm->nvm_sw_blob.data = temp; + mvm->nvm_sw_blob.size = ret; + break; + case NVM_SECTION_TYPE_CALIBRATION: + mvm->nvm_calib_blob.data = temp; + mvm->nvm_calib_blob.size = ret; + break; + case NVM_SECTION_TYPE_PRODUCTION: + mvm->nvm_prod_blob.data = temp; + mvm->nvm_prod_blob.size = ret; + break; + case NVM_SECTION_TYPE_PHY_SKU: + mvm->nvm_phy_sku_blob.data = temp; + mvm->nvm_phy_sku_blob.size = ret; + break; + case NVM_SECTION_TYPE_REGULATORY_SDP: + case NVM_SECTION_TYPE_REGULATORY: + mvm->nvm_reg_blob.data = temp; + mvm->nvm_reg_blob.size = ret; + break; + default: + if (section == mvm->cfg->nvm_hw_section_num) { + mvm->nvm_hw_blob.data = temp; + mvm->nvm_hw_blob.size = ret; + break; + } + } +#endif + } + if (!size_read) + IWL_ERR(mvm, "OTP is blank\n"); + kfree(nvm_buffer); + + /* Only if PNVM selected in the mod param - load external NVM */ + if (mvm->nvm_file_name) { + /* read External NVM file from the mod param */ + ret = iwl_read_external_nvm(mvm->trans, mvm->nvm_file_name, + mvm->nvm_sections); + if (ret) { + mvm->nvm_file_name = nvm_file_C; + + if ((ret == -EFAULT || ret == -ENOENT) && + mvm->nvm_file_name) { + /* in case nvm file was failed try again */ + ret = iwl_read_external_nvm(mvm->trans, + mvm->nvm_file_name, + mvm->nvm_sections); + if (ret) + return ret; + } else { + return ret; + } + } + } + + /* parse the relevant nvm sections */ + mvm->nvm_data = iwl_parse_nvm_sections(mvm); + if (!mvm->nvm_data) + return -ENODATA; + IWL_DEBUG_EEPROM(mvm->trans->dev, "nvm version = %x\n", + mvm->nvm_data->nvm_version); + + return ret < 0 ? ret : 0; +} + +struct iwl_mcc_update_resp * +iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, + enum iwl_mcc_source src_id) +{ + struct iwl_mcc_update_cmd mcc_update_cmd = { + .mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]), + .source_id = (u8)src_id, + }; + struct iwl_mcc_update_resp *resp_cp; + struct iwl_rx_packet *pkt; + struct iwl_host_cmd cmd = { + .id = MCC_UPDATE_CMD, + .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + .data = { &mcc_update_cmd }, + }; + + int ret; + u32 status; + int resp_len, n_channels; + u16 mcc; + + if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm))) + return ERR_PTR(-EOPNOTSUPP); + + cmd.len[0] = sizeof(struct iwl_mcc_update_cmd); + + IWL_DEBUG_LAR(mvm, "send MCC update to FW with '%c%c' src = %d\n", + alpha2[0], alpha2[1], src_id); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) + return ERR_PTR(ret); + + pkt = cmd.resp_pkt; + + /* Extract MCC response */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT)) { + struct iwl_mcc_update_resp *mcc_resp = (void *)pkt->data; + + n_channels = __le32_to_cpu(mcc_resp->n_channels); + resp_len = sizeof(struct iwl_mcc_update_resp) + + n_channels * sizeof(__le32); + resp_cp = kmemdup(mcc_resp, resp_len, GFP_KERNEL); + if (!resp_cp) { + resp_cp = ERR_PTR(-ENOMEM); + goto exit; + } + } else { + struct iwl_mcc_update_resp_v3 *mcc_resp_v3 = (void *)pkt->data; + + n_channels = __le32_to_cpu(mcc_resp_v3->n_channels); + resp_len = sizeof(struct iwl_mcc_update_resp) + + n_channels * sizeof(__le32); + resp_cp = kzalloc(resp_len, GFP_KERNEL); + if (!resp_cp) { + resp_cp = ERR_PTR(-ENOMEM); + goto exit; + } + + resp_cp->status = mcc_resp_v3->status; + resp_cp->mcc = mcc_resp_v3->mcc; + resp_cp->cap = cpu_to_le16(mcc_resp_v3->cap); + resp_cp->source_id = mcc_resp_v3->source_id; + resp_cp->time = mcc_resp_v3->time; + resp_cp->geo_info = mcc_resp_v3->geo_info; + resp_cp->n_channels = mcc_resp_v3->n_channels; + memcpy(resp_cp->channels, mcc_resp_v3->channels, + n_channels * sizeof(__le32)); + } + + status = le32_to_cpu(resp_cp->status); + + mcc = le16_to_cpu(resp_cp->mcc); + + /* W/A for a FW/NVM issue - returns 0x00 for the world domain */ + if (mcc == 0) { + mcc = 0x3030; /* "00" - world */ + resp_cp->mcc = cpu_to_le16(mcc); + } + + IWL_DEBUG_LAR(mvm, + "MCC response status: 0x%x. new MCC: 0x%x ('%c%c') n_chans: %d\n", + status, mcc, mcc >> 8, mcc & 0xff, n_channels); + +exit: + iwl_free_resp(&cmd); + return resp_cp; +} + +int iwl_mvm_init_mcc(struct iwl_mvm *mvm) +{ + bool tlv_lar; + bool nvm_lar; + int retval; + struct ieee80211_regdomain *regd; + char mcc[3]; + + if (mvm->cfg->nvm_type == IWL_NVM_EXT) { + tlv_lar = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT); + nvm_lar = mvm->nvm_data->lar_enabled; + if (tlv_lar != nvm_lar) + IWL_INFO(mvm, + "Conflict between TLV & NVM regarding enabling LAR (TLV = %s NVM =%s)\n", + tlv_lar ? "enabled" : "disabled", + nvm_lar ? "enabled" : "disabled"); + } + + if (!iwl_mvm_is_lar_supported(mvm)) + return 0; + + /* + * try to replay the last set MCC to FW. If it doesn't exist, + * queue an update to cfg80211 to retrieve the default alpha2 from FW. + */ + retval = iwl_mvm_init_fw_regd(mvm); + if (retval != -ENOENT) + return retval; + + /* + * Driver regulatory hint for initial update, this also informs the + * firmware we support wifi location updates. + * Disallow scans that might crash the FW while the LAR regdomain + * is not set. + */ + mvm->lar_regdom_set = false; + + regd = iwl_mvm_get_current_regdomain(mvm, NULL); + if (IS_ERR_OR_NULL(regd)) + return -EIO; + + if (iwl_mvm_is_wifi_mcc_supported(mvm) && + !iwl_acpi_get_mcc(mvm->dev, mcc)) { + kfree(regd); + regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc, + MCC_SOURCE_BIOS, NULL); + if (IS_ERR_OR_NULL(regd)) + return -EIO; + } + + retval = regulatory_set_wiphy_regd_sync(mvm->hw->wiphy, regd); + kfree(regd); + return retval; +} + +void iwl_mvm_rx_chub_update_mcc(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mcc_chub_notif *notif = (void *)pkt->data; + enum iwl_mcc_source src; + char mcc[3]; + struct ieee80211_regdomain *regd; + int wgds_tbl_idx; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_is_vif_assoc(mvm) && notif->source_id == MCC_SOURCE_WIFI) { + IWL_DEBUG_LAR(mvm, "Ignore mcc update while associated\n"); + return; + } + + if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm))) + return; + + mcc[0] = le16_to_cpu(notif->mcc) >> 8; + mcc[1] = le16_to_cpu(notif->mcc) & 0xff; + mcc[2] = '\0'; + src = notif->source_id; + + IWL_DEBUG_LAR(mvm, + "RX: received chub update mcc cmd (mcc '%s' src %d)\n", + mcc, src); + regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc, src, NULL); + if (IS_ERR_OR_NULL(regd)) + return; + + wgds_tbl_idx = iwl_mvm_get_sar_geo_profile(mvm); + if (wgds_tbl_idx < 1) + IWL_DEBUG_INFO(mvm, + "SAR WGDS is disabled or error received (%d)\n", + wgds_tbl_idx); + else + IWL_DEBUG_INFO(mvm, "SAR WGDS: geo profile %d is configured\n", + wgds_tbl_idx); + + regulatory_set_wiphy_regd(mvm->hw->wiphy, regd); + kfree(regd); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/offloading.c b/sys/contrib/dev/iwlwifi/mvm/offloading.c new file mode 100644 index 00000000000..41880517e8b --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/offloading.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015 Intel Deutschland GmbH + */ +#include +#include +#include +#include "mvm.h" + +void iwl_mvm_set_wowlan_qos_seq(struct iwl_mvm_sta *mvm_ap_sta, + struct iwl_wowlan_config_cmd *cmd) +{ + int i; + + /* + * For QoS counters, we store the one to use next, so subtract 0x10 + * since the uCode will add 0x10 *before* using the value while we + * increment after using the value (i.e. store the next value to use). + */ + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + u16 seq = mvm_ap_sta->tid_data[i].seq_number; + seq -= 0x10; + cmd->qos_seq[i] = cpu_to_le16(seq); + } +} + +int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool disable_offloading, + bool offload_ns, + u32 cmd_flags) +{ + union { + struct iwl_proto_offload_cmd_v1 v1; + struct iwl_proto_offload_cmd_v2 v2; + struct iwl_proto_offload_cmd_v3_small v3s; + struct iwl_proto_offload_cmd_v4 v4; + } cmd = {}; + struct iwl_host_cmd hcmd = { + .id = PROT_OFFLOAD_CONFIG_CMD, + .flags = cmd_flags, + .data[0] = &cmd, + .dataflags[0] = IWL_HCMD_DFL_DUP, + }; + struct iwl_proto_offload_cmd_common *common; + u32 enabled = 0, size; + u32 capa_flags = mvm->fw->ucode_capa.flags; + int ver = iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + PROT_OFFLOAD_CONFIG_CMD, 0); + +#if IS_ENABLED(CONFIG_IPV6) + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int i; + /* + * Skip tentative address when ns offload is enabled to avoid + * violating RFC4862. + * Keep tentative address when ns offload is disabled so the NS packets + * will not be filtered out and will wake up the host. + */ + bool skip_tentative = offload_ns; + + if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL || + capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE) { + struct iwl_ns_config *nsc; + struct iwl_targ_addr *addrs; + int n_nsc, n_addrs; + int c; + int num_skipped = 0; + + if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL) { + nsc = cmd.v3s.ns_config; + n_nsc = IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3S; + addrs = cmd.v3s.targ_addrs; + n_addrs = IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3S; + } else { + nsc = cmd.v4.ns_config; + n_nsc = IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L; + addrs = cmd.v4.targ_addrs; + n_addrs = IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L; + } + + /* + * For each address we have (and that will fit) fill a target + * address struct and combine for NS offload structs with the + * solicited node addresses. + */ + for (i = 0, c = 0; + i < mvmvif->num_target_ipv6_addrs && + i < n_addrs && c < n_nsc; i++) { + struct in6_addr solicited_addr; + int j; + + if (skip_tentative && + test_bit(i, mvmvif->tentative_addrs)) { + num_skipped++; + continue; + } + + addrconf_addr_solict_mult(&mvmvif->target_ipv6_addrs[i], + &solicited_addr); + for (j = 0; j < c; j++) + if (ipv6_addr_cmp(&nsc[j].dest_ipv6_addr, + &solicited_addr) == 0) + break; + if (j == c) + c++; + addrs[i].addr = mvmvif->target_ipv6_addrs[i]; + addrs[i].config_num = cpu_to_le32(j); + nsc[j].dest_ipv6_addr = solicited_addr; + memcpy(nsc[j].target_mac_addr, vif->addr, ETH_ALEN); + } + + if (mvmvif->num_target_ipv6_addrs - num_skipped) + enabled |= IWL_D3_PROTO_IPV6_VALID; + + if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL) + cmd.v3s.num_valid_ipv6_addrs = + cpu_to_le32(i - num_skipped); + else + cmd.v4.num_valid_ipv6_addrs = + cpu_to_le32(i - num_skipped); + } else if (capa_flags & IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS) { + bool found = false; + + BUILD_BUG_ON(sizeof(cmd.v2.target_ipv6_addr[0]) != + sizeof(mvmvif->target_ipv6_addrs[0])); + + for (i = 0; i < min(mvmvif->num_target_ipv6_addrs, + IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V2); i++) { + if (skip_tentative && + test_bit(i, mvmvif->tentative_addrs)) + continue; + + memcpy(cmd.v2.target_ipv6_addr[i], + &mvmvif->target_ipv6_addrs[i], + sizeof(cmd.v2.target_ipv6_addr[i])); + + found = true; + } + if (found) { + enabled |= IWL_D3_PROTO_IPV6_VALID; + memcpy(cmd.v2.ndp_mac_addr, vif->addr, ETH_ALEN); + } + } else { + bool found = false; + BUILD_BUG_ON(sizeof(cmd.v1.target_ipv6_addr[0]) != + sizeof(mvmvif->target_ipv6_addrs[0])); + + for (i = 0; i < min(mvmvif->num_target_ipv6_addrs, + IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V1); i++) { + if (skip_tentative && + test_bit(i, mvmvif->tentative_addrs)) + continue; + + memcpy(cmd.v1.target_ipv6_addr[i], + &mvmvif->target_ipv6_addrs[i], + sizeof(cmd.v1.target_ipv6_addr[i])); + + found = true; + } + + if (found) { + enabled |= IWL_D3_PROTO_IPV6_VALID; + memcpy(cmd.v1.ndp_mac_addr, vif->addr, ETH_ALEN); + } + } + + if (offload_ns && (enabled & IWL_D3_PROTO_IPV6_VALID)) + enabled |= IWL_D3_PROTO_OFFLOAD_NS; +#endif + if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL) { + common = &cmd.v3s.common; + size = sizeof(cmd.v3s); + } else if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE) { + common = &cmd.v4.common; + size = sizeof(cmd.v4); + if (ver < 4) { + /* + * This basically uses iwl_proto_offload_cmd_v3_large + * which doesn't have the sta_id parameter before the + * common part. + */ + size -= sizeof(cmd.v4.sta_id); + hcmd.data[0] = common; + } + } else if (capa_flags & IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS) { + common = &cmd.v2.common; + size = sizeof(cmd.v2); + } else { + common = &cmd.v1.common; + size = sizeof(cmd.v1); + } + + if (vif->bss_conf.arp_addr_cnt) { + enabled |= IWL_D3_PROTO_OFFLOAD_ARP | IWL_D3_PROTO_IPV4_VALID; + common->host_ipv4_addr = vif->bss_conf.arp_addr_list[0]; + memcpy(common->arp_mac_addr, vif->addr, ETH_ALEN); + } + + if (!disable_offloading) + common->enabled = cpu_to_le32(enabled); + + hcmd.len[0] = size; + return iwl_mvm_send_cmd(mvm, &hcmd); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/ops.c b/sys/contrib/dev/iwlwifi/mvm/ops.c new file mode 100644 index 00000000000..1928a0ba689 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/ops.c @@ -0,0 +1,1764 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#if defined(__FreeBSD__) +#define LINUXKPI_PARAM_PREFIX iwlwifi_mvm_ +#endif +#include +#include +#include + +#include "fw/notif-wait.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "fw/img.h" +#include "iwl-debug.h" +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "mvm.h" +#include "iwl-phy-db.h" +#include "iwl-eeprom-parse.h" +#include "iwl-csr.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "rs.h" +#include "fw/api/scan.h" +#include "time-event.h" +#include "fw-api.h" +#include "fw/acpi.h" + +#if defined(__linux__) +#define DRV_DESCRIPTION "The new Intel(R) wireless AGN driver for Linux" +MODULE_LICENSE("GPL"); +#elif defined(__FreeBSD__) +#define DRV_DESCRIPTION "The new Intel(R) wireless AGN/AC/AX based driver for FreeBSD" +MODULE_LICENSE("BSD"); +#endif +MODULE_DESCRIPTION(DRV_DESCRIPTION); + +static const struct iwl_op_mode_ops iwl_mvm_ops; +static const struct iwl_op_mode_ops iwl_mvm_ops_mq; + +struct iwl_mvm_mod_params iwlmvm_mod_params = { +#if defined(__FreeBSD__) + .power_scheme = IWL_POWER_SCHEME_CAM, /* disable default PS */ +#else + .power_scheme = IWL_POWER_SCHEME_BPS, +#endif + /* rest of fields are 0 by default */ +}; + +module_param_named(init_dbg, iwlmvm_mod_params.init_dbg, bool, 0444); +MODULE_PARM_DESC(init_dbg, + "set to true to debug an ASSERT in INIT fw (default: false"); +module_param_named(power_scheme, iwlmvm_mod_params.power_scheme, int, 0444); +MODULE_PARM_DESC(power_scheme, + "power management scheme: 1-active, 2-balanced, 3-low power, default: 2"); + +/* + * module init and exit functions + */ +static int __init iwl_mvm_init(void) +{ + int ret; + + ret = iwl_mvm_rate_control_register(); + if (ret) { + pr_err("Unable to register rate control algorithm: %d\n", ret); + return ret; + } + + ret = iwl_opmode_register("iwlmvm", &iwl_mvm_ops); + if (ret) + pr_err("Unable to register MVM op_mode: %d\n", ret); + + return ret; +} +#if defined(__linux__) +module_init(iwl_mvm_init); +#elif defined(__FreeBSD__) +module_init_order(iwl_mvm_init, SI_ORDER_SECOND); +#endif + +static void __exit iwl_mvm_exit(void) +{ + iwl_opmode_deregister("iwlmvm"); + iwl_mvm_rate_control_unregister(); +} +module_exit(iwl_mvm_exit); + +static void iwl_mvm_nic_config(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + u8 radio_cfg_type, radio_cfg_step, radio_cfg_dash; + u32 reg_val = 0; + u32 phy_config = iwl_mvm_get_phy_config(mvm); + + radio_cfg_type = (phy_config & FW_PHY_CFG_RADIO_TYPE) >> + FW_PHY_CFG_RADIO_TYPE_POS; + radio_cfg_step = (phy_config & FW_PHY_CFG_RADIO_STEP) >> + FW_PHY_CFG_RADIO_STEP_POS; + radio_cfg_dash = (phy_config & FW_PHY_CFG_RADIO_DASH) >> + FW_PHY_CFG_RADIO_DASH_POS; + + /* SKU control */ + reg_val |= CSR_HW_REV_STEP(mvm->trans->hw_rev) << + CSR_HW_IF_CONFIG_REG_POS_MAC_STEP; + reg_val |= CSR_HW_REV_DASH(mvm->trans->hw_rev) << + CSR_HW_IF_CONFIG_REG_POS_MAC_DASH; + + /* radio configuration */ + reg_val |= radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE; + reg_val |= radio_cfg_step << CSR_HW_IF_CONFIG_REG_POS_PHY_STEP; + reg_val |= radio_cfg_dash << CSR_HW_IF_CONFIG_REG_POS_PHY_DASH; + + WARN_ON((radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE) & + ~CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE); + + /* + * TODO: Bits 7-8 of CSR in 8000 HW family and higher set the ADC + * sampling, and shouldn't be set to any non-zero value. + * The same is supposed to be true of the other HW, but unsetting + * them (such as the 7260) causes automatic tests to fail on seemingly + * unrelated errors. Need to further investigate this, but for now + * we'll separate cases. + */ + if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) + reg_val |= CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI; + + if (iwl_fw_dbg_is_d3_debug_enabled(&mvm->fwrt)) + reg_val |= CSR_HW_IF_CONFIG_REG_D3_DEBUG; + + iwl_trans_set_bits_mask(mvm->trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH | + CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP | + CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE | + CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP | + CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH | + CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI | + CSR_HW_IF_CONFIG_REG_BIT_MAC_SI | + CSR_HW_IF_CONFIG_REG_D3_DEBUG, + reg_val); + + IWL_DEBUG_INFO(mvm, "Radio type=0x%x-0x%x-0x%x\n", radio_cfg_type, + radio_cfg_step, radio_cfg_dash); + + /* + * W/A : NIC is stuck in a reset state after Early PCIe power off + * (PCIe power is lost before PERST# is asserted), causing ME FW + * to lose ownership and not being able to obtain it back. + */ + if (!mvm->trans->cfg->apmg_not_supported) + iwl_set_bits_mask_prph(mvm->trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS, + ~APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS); +} + +static void iwl_mvm_rx_monitor_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_datapath_monitor_notif *notif = (void *)pkt->data; + struct ieee80211_supported_band *sband; + const struct ieee80211_sta_he_cap *he_cap; + struct ieee80211_vif *vif; + + if (notif->type != cpu_to_le32(IWL_DP_MON_NOTIF_TYPE_EXT_CCA)) + return; + + vif = iwl_mvm_get_vif_by_macid(mvm, notif->mac_id); + if (!vif || vif->type != NL80211_IFTYPE_STATION) + return; + + if (!vif->bss_conf.chandef.chan || + vif->bss_conf.chandef.chan->band != NL80211_BAND_2GHZ || + vif->bss_conf.chandef.width < NL80211_CHAN_WIDTH_40) + return; + + if (!vif->bss_conf.assoc) + return; + + /* this shouldn't happen *again*, ignore it */ + if (mvm->cca_40mhz_workaround) + return; + + /* + * We'll decrement this on disconnect - so set to 2 since we'll + * still have to disconnect from the current AP first. + */ + mvm->cca_40mhz_workaround = 2; + + /* + * This capability manipulation isn't really ideal, but it's the + * easiest choice - otherwise we'd have to do some major changes + * in mac80211 to support this, which isn't worth it. This does + * mean that userspace may have outdated information, but that's + * actually not an issue at all. + */ + sband = mvm->hw->wiphy->bands[NL80211_BAND_2GHZ]; + + WARN_ON(!sband->ht_cap.ht_supported); + WARN_ON(!(sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)); + sband->ht_cap.cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + he_cap = ieee80211_get_he_iftype_cap(sband, + ieee80211_vif_type_p2p(vif)); + + if (he_cap) { + /* we know that ours is writable */ + struct ieee80211_sta_he_cap *he = (void *)(uintptr_t)he_cap; + + WARN_ON(!he->has_he); + WARN_ON(!(he->he_cap_elem.phy_cap_info[0] & + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G)); + he->he_cap_elem.phy_cap_info[0] &= + ~IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G; + } + + ieee80211_disconnect(vif, true); +} + +void iwl_mvm_apply_fw_smps_request(struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + enum ieee80211_smps_mode mode = IEEE80211_SMPS_AUTOMATIC; + + if (mvm->fw_static_smps_request && + vif->bss_conf.chandef.width == NL80211_CHAN_WIDTH_160 && + vif->bss_conf.he_support) + mode = IEEE80211_SMPS_STATIC; + + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_FW, mode); +} + +static void iwl_mvm_intf_dual_chain_req(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + iwl_mvm_apply_fw_smps_request(vif); +} + +static void iwl_mvm_rx_thermal_dual_chain_req(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_thermal_dual_chain_request *req = (void *)pkt->data; + + /* + * We could pass it to the iterator data, but also need to remember + * it for new interfaces that are added while in this state. + */ + mvm->fw_static_smps_request = + req->event == cpu_to_le32(THERMAL_DUAL_CHAIN_REQ_DISABLE); + ieee80211_iterate_interfaces(mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_intf_dual_chain_req, NULL); +} + +/** + * enum iwl_rx_handler_context context for Rx handler + * @RX_HANDLER_SYNC : this means that it will be called in the Rx path + * which can't acquire mvm->mutex. + * @RX_HANDLER_ASYNC_LOCKED : If the handler needs to hold mvm->mutex + * (and only in this case!), it should be set as ASYNC. In that case, + * it will be called from a worker with mvm->mutex held. + * @RX_HANDLER_ASYNC_UNLOCKED : in case the handler needs to lock the + * mutex itself, it will be called from a worker without mvm->mutex held. + */ +enum iwl_rx_handler_context { + RX_HANDLER_SYNC, + RX_HANDLER_ASYNC_LOCKED, + RX_HANDLER_ASYNC_UNLOCKED, +}; + +/** + * struct iwl_rx_handlers handler for FW notification + * @cmd_id: command id + * @context: see &iwl_rx_handler_context + * @fn: the function is called when notification is received + */ +struct iwl_rx_handlers { + u16 cmd_id, min_size; + enum iwl_rx_handler_context context; + void (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +}; + +#define RX_HANDLER_NO_SIZE(_cmd_id, _fn, _context) \ + { .cmd_id = _cmd_id, .fn = _fn, .context = _context, } +#define RX_HANDLER_GRP_NO_SIZE(_grp, _cmd, _fn, _context) \ + { .cmd_id = WIDE_ID(_grp, _cmd), .fn = _fn, .context = _context, } +#define RX_HANDLER(_cmd_id, _fn, _context, _struct) \ + { .cmd_id = _cmd_id, .fn = _fn, \ + .context = _context, .min_size = sizeof(_struct), } +#define RX_HANDLER_GRP(_grp, _cmd, _fn, _context, _struct) \ + { .cmd_id = WIDE_ID(_grp, _cmd), .fn = _fn, \ + .context = _context, .min_size = sizeof(_struct), } + +/* + * Handlers for fw notifications + * Convention: RX_HANDLER(CMD_NAME, iwl_mvm_rx_CMD_NAME + * This list should be in order of frequency for performance purposes. + * + * The handler can be one from three contexts, see &iwl_rx_handler_context + */ +static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = { + RX_HANDLER(TX_CMD, iwl_mvm_rx_tx_cmd, RX_HANDLER_SYNC, + struct iwl_mvm_tx_resp), + RX_HANDLER(BA_NOTIF, iwl_mvm_rx_ba_notif, RX_HANDLER_SYNC, + struct iwl_mvm_ba_notif), + + RX_HANDLER_GRP(DATA_PATH_GROUP, TLC_MNG_UPDATE_NOTIF, + iwl_mvm_tlc_update_notif, RX_HANDLER_SYNC, + struct iwl_tlc_update_notif), + + RX_HANDLER(BT_PROFILE_NOTIFICATION, iwl_mvm_rx_bt_coex_notif, + RX_HANDLER_ASYNC_LOCKED, struct iwl_bt_coex_profile_notif), + RX_HANDLER_NO_SIZE(BEACON_NOTIFICATION, iwl_mvm_rx_beacon_notif, + RX_HANDLER_ASYNC_LOCKED), + RX_HANDLER_NO_SIZE(STATISTICS_NOTIFICATION, iwl_mvm_rx_statistics, + RX_HANDLER_ASYNC_LOCKED), + + RX_HANDLER(BA_WINDOW_STATUS_NOTIFICATION_ID, + iwl_mvm_window_status_notif, RX_HANDLER_SYNC, + struct iwl_ba_window_status_notif), + + RX_HANDLER(TIME_EVENT_NOTIFICATION, iwl_mvm_rx_time_event_notif, + RX_HANDLER_SYNC, struct iwl_time_event_notif), + RX_HANDLER_GRP(MAC_CONF_GROUP, SESSION_PROTECTION_NOTIF, + iwl_mvm_rx_session_protect_notif, RX_HANDLER_SYNC, + struct iwl_mvm_session_prot_notif), + RX_HANDLER(MCC_CHUB_UPDATE_CMD, iwl_mvm_rx_chub_update_mcc, + RX_HANDLER_ASYNC_LOCKED, struct iwl_mcc_chub_notif), + + RX_HANDLER(EOSP_NOTIFICATION, iwl_mvm_rx_eosp_notif, RX_HANDLER_SYNC, + struct iwl_mvm_eosp_notification), + + RX_HANDLER(SCAN_ITERATION_COMPLETE, + iwl_mvm_rx_lmac_scan_iter_complete_notif, RX_HANDLER_SYNC, + struct iwl_lmac_scan_complete_notif), + RX_HANDLER(SCAN_OFFLOAD_COMPLETE, + iwl_mvm_rx_lmac_scan_complete_notif, + RX_HANDLER_ASYNC_LOCKED, struct iwl_periodic_scan_complete), + RX_HANDLER_NO_SIZE(MATCH_FOUND_NOTIFICATION, + iwl_mvm_rx_scan_match_found, + RX_HANDLER_SYNC), + RX_HANDLER(SCAN_COMPLETE_UMAC, iwl_mvm_rx_umac_scan_complete_notif, + RX_HANDLER_ASYNC_LOCKED, struct iwl_umac_scan_complete), + RX_HANDLER(SCAN_ITERATION_COMPLETE_UMAC, + iwl_mvm_rx_umac_scan_iter_complete_notif, RX_HANDLER_SYNC, + struct iwl_umac_scan_iter_complete_notif), + + RX_HANDLER(CARD_STATE_NOTIFICATION, iwl_mvm_rx_card_state_notif, + RX_HANDLER_SYNC, struct iwl_card_state_notif), + + RX_HANDLER(MISSED_BEACONS_NOTIFICATION, iwl_mvm_rx_missed_beacons_notif, + RX_HANDLER_SYNC, struct iwl_missed_beacons_notif), + + RX_HANDLER(REPLY_ERROR, iwl_mvm_rx_fw_error, RX_HANDLER_SYNC, + struct iwl_error_resp), + RX_HANDLER(PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION, + iwl_mvm_power_uapsd_misbehaving_ap_notif, RX_HANDLER_SYNC, + struct iwl_uapsd_misbehaving_ap_notif), + RX_HANDLER_NO_SIZE(DTS_MEASUREMENT_NOTIFICATION, iwl_mvm_temp_notif, + RX_HANDLER_ASYNC_LOCKED), + RX_HANDLER_GRP_NO_SIZE(PHY_OPS_GROUP, DTS_MEASUREMENT_NOTIF_WIDE, + iwl_mvm_temp_notif, RX_HANDLER_ASYNC_UNLOCKED), + RX_HANDLER_GRP(PHY_OPS_GROUP, CT_KILL_NOTIFICATION, + iwl_mvm_ct_kill_notif, RX_HANDLER_SYNC, + struct ct_kill_notif), + + RX_HANDLER(TDLS_CHANNEL_SWITCH_NOTIFICATION, iwl_mvm_rx_tdls_notif, + RX_HANDLER_ASYNC_LOCKED, + struct iwl_tdls_channel_switch_notif), + RX_HANDLER(MFUART_LOAD_NOTIFICATION, iwl_mvm_rx_mfuart_notif, + RX_HANDLER_SYNC, struct iwl_mfuart_load_notif_v1), + RX_HANDLER_GRP(LOCATION_GROUP, TOF_RESPONDER_STATS, + iwl_mvm_ftm_responder_stats, RX_HANDLER_ASYNC_LOCKED, + struct iwl_ftm_responder_stats), + + RX_HANDLER_GRP_NO_SIZE(LOCATION_GROUP, TOF_RANGE_RESPONSE_NOTIF, + iwl_mvm_ftm_range_resp, RX_HANDLER_ASYNC_LOCKED), + RX_HANDLER_GRP_NO_SIZE(LOCATION_GROUP, TOF_LC_NOTIF, + iwl_mvm_ftm_lc_notif, RX_HANDLER_ASYNC_LOCKED), + + RX_HANDLER_GRP(DEBUG_GROUP, MFU_ASSERT_DUMP_NTF, + iwl_mvm_mfu_assert_dump_notif, RX_HANDLER_SYNC, + struct iwl_mfu_assert_dump_notif), + RX_HANDLER_GRP(PROT_OFFLOAD_GROUP, STORED_BEACON_NTF, + iwl_mvm_rx_stored_beacon_notif, RX_HANDLER_SYNC, + struct iwl_stored_beacon_notif_v2), + RX_HANDLER_GRP(DATA_PATH_GROUP, MU_GROUP_MGMT_NOTIF, + iwl_mvm_mu_mimo_grp_notif, RX_HANDLER_SYNC, + struct iwl_mu_group_mgmt_notif), + RX_HANDLER_GRP(DATA_PATH_GROUP, STA_PM_NOTIF, + iwl_mvm_sta_pm_notif, RX_HANDLER_SYNC, + struct iwl_mvm_pm_state_notification), + RX_HANDLER_GRP(MAC_CONF_GROUP, PROBE_RESPONSE_DATA_NOTIF, + iwl_mvm_probe_resp_data_notif, + RX_HANDLER_ASYNC_LOCKED, + struct iwl_probe_resp_data_notif), + RX_HANDLER_GRP(MAC_CONF_GROUP, CHANNEL_SWITCH_START_NOTIF, + iwl_mvm_channel_switch_start_notif, + RX_HANDLER_SYNC, struct iwl_channel_switch_start_notif), + RX_HANDLER_GRP(DATA_PATH_GROUP, MONITOR_NOTIF, + iwl_mvm_rx_monitor_notif, RX_HANDLER_ASYNC_LOCKED, + struct iwl_datapath_monitor_notif), + + RX_HANDLER_GRP(DATA_PATH_GROUP, THERMAL_DUAL_CHAIN_REQUEST, + iwl_mvm_rx_thermal_dual_chain_req, + RX_HANDLER_ASYNC_LOCKED, + struct iwl_thermal_dual_chain_request), +}; +#undef RX_HANDLER +#undef RX_HANDLER_GRP + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_legacy_names[] = { + HCMD_NAME(UCODE_ALIVE_NTFY), + HCMD_NAME(REPLY_ERROR), + HCMD_NAME(ECHO_CMD), + HCMD_NAME(INIT_COMPLETE_NOTIF), + HCMD_NAME(PHY_CONTEXT_CMD), + HCMD_NAME(DBG_CFG), + HCMD_NAME(SCAN_CFG_CMD), + HCMD_NAME(SCAN_REQ_UMAC), + HCMD_NAME(SCAN_ABORT_UMAC), + HCMD_NAME(SCAN_COMPLETE_UMAC), + HCMD_NAME(BA_WINDOW_STATUS_NOTIFICATION_ID), + HCMD_NAME(ADD_STA_KEY), + HCMD_NAME(ADD_STA), + HCMD_NAME(REMOVE_STA), + HCMD_NAME(FW_GET_ITEM_CMD), + HCMD_NAME(TX_CMD), + HCMD_NAME(SCD_QUEUE_CFG), + HCMD_NAME(TXPATH_FLUSH), + HCMD_NAME(MGMT_MCAST_KEY), + HCMD_NAME(WEP_KEY), + HCMD_NAME(SHARED_MEM_CFG), + HCMD_NAME(TDLS_CHANNEL_SWITCH_CMD), + HCMD_NAME(MAC_CONTEXT_CMD), + HCMD_NAME(TIME_EVENT_CMD), + HCMD_NAME(TIME_EVENT_NOTIFICATION), + HCMD_NAME(BINDING_CONTEXT_CMD), + HCMD_NAME(TIME_QUOTA_CMD), + HCMD_NAME(NON_QOS_TX_COUNTER_CMD), + HCMD_NAME(LEDS_CMD), + HCMD_NAME(LQ_CMD), + HCMD_NAME(FW_PAGING_BLOCK_CMD), + HCMD_NAME(SCAN_OFFLOAD_REQUEST_CMD), + HCMD_NAME(SCAN_OFFLOAD_ABORT_CMD), + HCMD_NAME(HOT_SPOT_CMD), + HCMD_NAME(SCAN_OFFLOAD_PROFILES_QUERY_CMD), + HCMD_NAME(BT_COEX_UPDATE_REDUCED_TXP), + HCMD_NAME(BT_COEX_CI), + HCMD_NAME(PHY_CONFIGURATION_CMD), + HCMD_NAME(CALIB_RES_NOTIF_PHY_DB), + HCMD_NAME(PHY_DB_CMD), + HCMD_NAME(SCAN_OFFLOAD_COMPLETE), + HCMD_NAME(SCAN_OFFLOAD_UPDATE_PROFILES_CMD), + HCMD_NAME(POWER_TABLE_CMD), + HCMD_NAME(PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION), + HCMD_NAME(REPLY_THERMAL_MNG_BACKOFF), + HCMD_NAME(DC2DC_CONFIG_CMD), + HCMD_NAME(NVM_ACCESS_CMD), + HCMD_NAME(BEACON_NOTIFICATION), + HCMD_NAME(BEACON_TEMPLATE_CMD), + HCMD_NAME(TX_ANT_CONFIGURATION_CMD), + HCMD_NAME(BT_CONFIG), + HCMD_NAME(STATISTICS_CMD), + HCMD_NAME(STATISTICS_NOTIFICATION), + HCMD_NAME(EOSP_NOTIFICATION), + HCMD_NAME(REDUCE_TX_POWER_CMD), + HCMD_NAME(CARD_STATE_NOTIFICATION), + HCMD_NAME(MISSED_BEACONS_NOTIFICATION), + HCMD_NAME(TDLS_CONFIG_CMD), + HCMD_NAME(MAC_PM_POWER_TABLE), + HCMD_NAME(TDLS_CHANNEL_SWITCH_NOTIFICATION), + HCMD_NAME(MFUART_LOAD_NOTIFICATION), + HCMD_NAME(RSS_CONFIG_CMD), + HCMD_NAME(SCAN_ITERATION_COMPLETE_UMAC), + HCMD_NAME(REPLY_RX_PHY_CMD), + HCMD_NAME(REPLY_RX_MPDU_CMD), + HCMD_NAME(BAR_FRAME_RELEASE), + HCMD_NAME(FRAME_RELEASE), + HCMD_NAME(BA_NOTIF), + HCMD_NAME(MCC_UPDATE_CMD), + HCMD_NAME(MCC_CHUB_UPDATE_CMD), + HCMD_NAME(MARKER_CMD), + HCMD_NAME(BT_PROFILE_NOTIFICATION), + HCMD_NAME(BCAST_FILTER_CMD), + HCMD_NAME(MCAST_FILTER_CMD), + HCMD_NAME(REPLY_SF_CFG_CMD), + HCMD_NAME(REPLY_BEACON_FILTERING_CMD), + HCMD_NAME(D3_CONFIG_CMD), + HCMD_NAME(PROT_OFFLOAD_CONFIG_CMD), + HCMD_NAME(OFFLOADS_QUERY_CMD), + HCMD_NAME(MATCH_FOUND_NOTIFICATION), + HCMD_NAME(DTS_MEASUREMENT_NOTIFICATION), + HCMD_NAME(WOWLAN_PATTERNS), + HCMD_NAME(WOWLAN_CONFIGURATION), + HCMD_NAME(WOWLAN_TSC_RSC_PARAM), + HCMD_NAME(WOWLAN_TKIP_PARAM), + HCMD_NAME(WOWLAN_KEK_KCK_MATERIAL), + HCMD_NAME(WOWLAN_GET_STATUSES), + HCMD_NAME(SCAN_ITERATION_COMPLETE), + HCMD_NAME(D0I3_END_CMD), + HCMD_NAME(LTR_CONFIG), + HCMD_NAME(LDBG_CONFIG_CMD), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_system_names[] = { + HCMD_NAME(SHARED_MEM_CFG_CMD), + HCMD_NAME(INIT_EXTENDED_CFG_CMD), + HCMD_NAME(FW_ERROR_RECOVERY_CMD), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_mac_conf_names[] = { + HCMD_NAME(CHANNEL_SWITCH_TIME_EVENT_CMD), + HCMD_NAME(SESSION_PROTECTION_CMD), + HCMD_NAME(SESSION_PROTECTION_NOTIF), + HCMD_NAME(CHANNEL_SWITCH_START_NOTIF), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_phy_names[] = { + HCMD_NAME(CMD_DTS_MEASUREMENT_TRIGGER_WIDE), + HCMD_NAME(CTDP_CONFIG_CMD), + HCMD_NAME(TEMP_REPORTING_THRESHOLDS_CMD), + HCMD_NAME(PER_CHAIN_LIMIT_OFFSET_CMD), + HCMD_NAME(CT_KILL_NOTIFICATION), + HCMD_NAME(DTS_MEASUREMENT_NOTIF_WIDE), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_data_path_names[] = { + HCMD_NAME(DQA_ENABLE_CMD), + HCMD_NAME(UPDATE_MU_GROUPS_CMD), + HCMD_NAME(TRIGGER_RX_QUEUES_NOTIF_CMD), + HCMD_NAME(STA_HE_CTXT_CMD), + HCMD_NAME(RFH_QUEUE_CONFIG_CMD), + HCMD_NAME(TLC_MNG_CONFIG_CMD), + HCMD_NAME(CHEST_COLLECTOR_FILTER_CONFIG_CMD), + HCMD_NAME(MONITOR_NOTIF), + HCMD_NAME(THERMAL_DUAL_CHAIN_REQUEST), + HCMD_NAME(STA_PM_NOTIF), + HCMD_NAME(MU_GROUP_MGMT_NOTIF), + HCMD_NAME(RX_QUEUES_NOTIFICATION), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_location_names[] = { + HCMD_NAME(TOF_RANGE_REQ_CMD), + HCMD_NAME(TOF_CONFIG_CMD), + HCMD_NAME(TOF_RANGE_ABORT_CMD), + HCMD_NAME(TOF_RANGE_REQ_EXT_CMD), + HCMD_NAME(TOF_RESPONDER_CONFIG_CMD), + HCMD_NAME(TOF_RESPONDER_DYN_CONFIG_CMD), + HCMD_NAME(TOF_LC_NOTIF), + HCMD_NAME(TOF_RESPONDER_STATS), + HCMD_NAME(TOF_MCSI_DEBUG_NOTIF), + HCMD_NAME(TOF_RANGE_RESPONSE_NOTIF), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_prot_offload_names[] = { + HCMD_NAME(STORED_BEACON_NTF), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_regulatory_and_nvm_names[] = { + HCMD_NAME(NVM_ACCESS_COMPLETE), + HCMD_NAME(NVM_GET_INFO), + HCMD_NAME(TAS_CONFIG), +}; + +static const struct iwl_hcmd_arr iwl_mvm_groups[] = { + [LEGACY_GROUP] = HCMD_ARR(iwl_mvm_legacy_names), + [LONG_GROUP] = HCMD_ARR(iwl_mvm_legacy_names), + [SYSTEM_GROUP] = HCMD_ARR(iwl_mvm_system_names), + [MAC_CONF_GROUP] = HCMD_ARR(iwl_mvm_mac_conf_names), + [PHY_OPS_GROUP] = HCMD_ARR(iwl_mvm_phy_names), + [DATA_PATH_GROUP] = HCMD_ARR(iwl_mvm_data_path_names), + [LOCATION_GROUP] = HCMD_ARR(iwl_mvm_location_names), + [PROT_OFFLOAD_GROUP] = HCMD_ARR(iwl_mvm_prot_offload_names), + [REGULATORY_AND_NVM_GROUP] = + HCMD_ARR(iwl_mvm_regulatory_and_nvm_names), +}; + +/* this forward declaration can avoid to export the function */ +static void iwl_mvm_async_handlers_wk(struct work_struct *wk); + +static u32 iwl_mvm_min_backoff(struct iwl_mvm *mvm) +{ + const struct iwl_pwr_tx_backoff *backoff = mvm->cfg->pwr_tx_backoffs; + u64 dflt_pwr_limit; + + if (!backoff) + return 0; + + dflt_pwr_limit = iwl_acpi_get_pwr_limit(mvm->dev); + + while (backoff->pwr) { + if (dflt_pwr_limit >= backoff->pwr) + return backoff->backoff; + + backoff++; + } + + return 0; +} + +static void iwl_mvm_tx_unblock_dwork(struct work_struct *work) +{ + struct iwl_mvm *mvm = + container_of(work, struct iwl_mvm, cs_tx_unblock_dwork.work); + struct ieee80211_vif *tx_blocked_vif; + struct iwl_mvm_vif *mvmvif; + + mutex_lock(&mvm->mutex); + + tx_blocked_vif = + rcu_dereference_protected(mvm->csa_tx_blocked_vif, + lockdep_is_held(&mvm->mutex)); + + if (!tx_blocked_vif) + goto unlock; + + mvmvif = iwl_mvm_vif_from_mac80211(tx_blocked_vif); + iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, false); + RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); +unlock: + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_fwrt_dump_start(void *ctx) +{ + struct iwl_mvm *mvm = ctx; + + mutex_lock(&mvm->mutex); + + return 0; +} + +static void iwl_mvm_fwrt_dump_end(void *ctx) +{ + struct iwl_mvm *mvm = ctx; + + mutex_unlock(&mvm->mutex); +} + +static bool iwl_mvm_fwrt_fw_running(void *ctx) +{ + return iwl_mvm_firmware_running(ctx); +} + +static int iwl_mvm_fwrt_send_hcmd(void *ctx, struct iwl_host_cmd *host_cmd) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)ctx; + int ret; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_send_cmd(mvm, host_cmd); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static bool iwl_mvm_d3_debug_enable(void *ctx) +{ + return IWL_MVM_D3_DEBUG; +} + +static const struct iwl_fw_runtime_ops iwl_mvm_fwrt_ops = { + .dump_start = iwl_mvm_fwrt_dump_start, + .dump_end = iwl_mvm_fwrt_dump_end, + .fw_running = iwl_mvm_fwrt_fw_running, + .send_hcmd = iwl_mvm_fwrt_send_hcmd, + .d3_debug_enable = iwl_mvm_d3_debug_enable, +}; + +static int iwl_mvm_start_get_nvm(struct iwl_mvm *mvm) +{ + int ret; + + rtnl_lock(); + mutex_lock(&mvm->mutex); + + ret = iwl_run_init_mvm_ucode(mvm); + + if (ret && ret != -ERFKILL) + iwl_fw_dbg_error_collect(&mvm->fwrt, FW_DBG_TRIGGER_DRIVER); + if (!ret && iwl_mvm_is_lar_supported(mvm)) { + mvm->hw->wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED; + ret = iwl_mvm_init_mcc(mvm); + } + + if (!iwlmvm_mod_params.init_dbg || !ret) + iwl_mvm_stop_device(mvm); + + mutex_unlock(&mvm->mutex); + rtnl_unlock(); + + if (ret < 0) + IWL_ERR(mvm, "Failed to run INIT ucode: %d\n", ret); + + return ret; +} + +static int iwl_mvm_start_post_nvm(struct iwl_mvm *mvm) +{ + int ret; + + iwl_mvm_toggle_tx_ant(mvm, &mvm->mgmt_last_antenna_idx); + + ret = iwl_mvm_mac_setup_register(mvm); + if (ret) + return ret; + mvm->hw_registered = true; + + iwl_mvm_dbgfs_register(mvm); + + return 0; +} + +struct iwl_mvm_frob_txf_data { + u8 *buf; + size_t buflen; +}; + +static void iwl_mvm_frob_txf_key_iter(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *data) +{ + struct iwl_mvm_frob_txf_data *txf = data; + u8 keylen, match, matchend; + u8 *keydata; + size_t i; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + keydata = key->key; + keylen = key->keylen; + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + case WLAN_CIPHER_SUITE_TKIP: + /* + * WEP has short keys which might show up in the payload, + * and then you can deduce the key, so in this case just + * remove all FIFO data. + * For TKIP, we don't know the phase 2 keys here, so same. + */ + memset(txf->buf, 0xBB, txf->buflen); + return; + default: + return; + } + + /* scan for key material and clear it out */ + match = 0; + for (i = 0; i < txf->buflen; i++) { + if (txf->buf[i] != keydata[match]) { + match = 0; + continue; + } + match++; + if (match == keylen) { + memset(txf->buf + i - keylen, 0xAA, keylen); + match = 0; + } + } + + /* we're dealing with a FIFO, so check wrapped around data */ + matchend = match; + for (i = 0; match && i < keylen - match; i++) { + if (txf->buf[i] != keydata[match]) + break; + match++; + if (match == keylen) { + memset(txf->buf, 0xAA, i + 1); + memset(txf->buf + txf->buflen - matchend, 0xAA, + matchend); + break; + } + } +} + +static void iwl_mvm_frob_txf(void *ctx, void *buf, size_t buflen) +{ + struct iwl_mvm_frob_txf_data txf = { + .buf = buf, + .buflen = buflen, + }; + struct iwl_mvm *mvm = ctx; + + /* embedded key material exists only on old API */ + if (iwl_mvm_has_new_tx_api(mvm)) + return; + + rcu_read_lock(); + ieee80211_iter_keys_rcu(mvm->hw, NULL, iwl_mvm_frob_txf_key_iter, &txf); + rcu_read_unlock(); +} + +static void iwl_mvm_frob_hcmd(void *ctx, void *hcmd, size_t len) +{ + /* we only use wide headers for commands */ + struct iwl_cmd_header_wide *hdr = hcmd; + unsigned int frob_start = sizeof(*hdr), frob_end = 0; + + if (len < sizeof(hdr)) + return; + + /* all the commands we care about are in LONG_GROUP */ + if (hdr->group_id != LONG_GROUP) + return; + + switch (hdr->cmd) { + case WEP_KEY: + case WOWLAN_TKIP_PARAM: + case WOWLAN_KEK_KCK_MATERIAL: + case ADD_STA_KEY: + /* + * blank out everything here, easier than dealing + * with the various versions of the command + */ + frob_end = INT_MAX; + break; + case MGMT_MCAST_KEY: + frob_start = offsetof(struct iwl_mvm_mgmt_mcast_key_cmd, igtk); + BUILD_BUG_ON(offsetof(struct iwl_mvm_mgmt_mcast_key_cmd, igtk) != + offsetof(struct iwl_mvm_mgmt_mcast_key_cmd_v1, igtk)); + + frob_end = offsetofend(struct iwl_mvm_mgmt_mcast_key_cmd, igtk); + BUILD_BUG_ON(offsetof(struct iwl_mvm_mgmt_mcast_key_cmd, igtk) < + offsetof(struct iwl_mvm_mgmt_mcast_key_cmd_v1, igtk)); + break; + } + + if (frob_start >= frob_end) + return; + + if (frob_end > len) + frob_end = len; + + memset((u8 *)hcmd + frob_start, 0xAA, frob_end - frob_start); +} + +static void iwl_mvm_frob_mem(void *ctx, u32 mem_addr, void *mem, size_t buflen) +{ + const struct iwl_dump_exclude *excl; + struct iwl_mvm *mvm = ctx; + int i; + + switch (mvm->fwrt.cur_fw_img) { + case IWL_UCODE_INIT: + default: + /* not relevant */ + return; + case IWL_UCODE_REGULAR: + case IWL_UCODE_REGULAR_USNIFFER: + excl = mvm->fw->dump_excl; + break; + case IWL_UCODE_WOWLAN: + excl = mvm->fw->dump_excl_wowlan; + break; + } + + BUILD_BUG_ON(sizeof(mvm->fw->dump_excl) != + sizeof(mvm->fw->dump_excl_wowlan)); + + for (i = 0; i < ARRAY_SIZE(mvm->fw->dump_excl); i++) { + u32 start, end; + + if (!excl[i].addr || !excl[i].size) + continue; + + start = excl[i].addr; + end = start + excl[i].size; + + if (end <= mem_addr || start >= mem_addr + buflen) + continue; + + if (start < mem_addr) + start = mem_addr; + + if (end > mem_addr + buflen) + end = mem_addr + buflen; + + memset((u8 *)mem + start - mem_addr, 0xAA, end - start); + } +} + +static const struct iwl_dump_sanitize_ops iwl_mvm_sanitize_ops = { + .frob_txf = iwl_mvm_frob_txf, + .frob_hcmd = iwl_mvm_frob_hcmd, + .frob_mem = iwl_mvm_frob_mem, +}; + +static struct iwl_op_mode * +iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const struct iwl_fw *fw, struct dentry *dbgfs_dir) +{ + struct ieee80211_hw *hw; + struct iwl_op_mode *op_mode; + struct iwl_mvm *mvm; + struct iwl_trans_config trans_cfg = {}; + static const u8 no_reclaim_cmds[] = { + TX_CMD, + }; + int err, scan_size; + u32 min_backoff; + enum iwl_amsdu_size rb_size_default; + + /* + * We use IWL_MVM_STATION_COUNT_MAX to check the validity of the station + * index all over the driver - check that its value corresponds to the + * array size. + */ + BUILD_BUG_ON(ARRAY_SIZE(mvm->fw_id_to_mac_id) != + IWL_MVM_STATION_COUNT_MAX); + + /******************************** + * 1. Allocating and configuring HW data + ********************************/ + hw = ieee80211_alloc_hw(sizeof(struct iwl_op_mode) + + sizeof(struct iwl_mvm), + &iwl_mvm_hw_ops); + if (!hw) + return NULL; + + hw->max_rx_aggregation_subframes = IEEE80211_MAX_AMPDU_BUF; + + if (cfg->max_tx_agg_size) + hw->max_tx_aggregation_subframes = cfg->max_tx_agg_size; + else + hw->max_tx_aggregation_subframes = IEEE80211_MAX_AMPDU_BUF; + + op_mode = hw->priv; + + mvm = IWL_OP_MODE_GET_MVM(op_mode); + mvm->dev = trans->dev; + mvm->trans = trans; + mvm->cfg = cfg; + mvm->fw = fw; + mvm->hw = hw; + + iwl_fw_runtime_init(&mvm->fwrt, trans, fw, &iwl_mvm_fwrt_ops, mvm, + &iwl_mvm_sanitize_ops, mvm, dbgfs_dir); + + iwl_mvm_get_acpi_tables(mvm); + + mvm->init_status = 0; + + if (iwl_mvm_has_new_rx_api(mvm)) { + op_mode->ops = &iwl_mvm_ops_mq; + trans->rx_mpdu_cmd_hdr_size = + (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210) ? + sizeof(struct iwl_rx_mpdu_desc) : + IWL_RX_DESC_SIZE_V1; + } else { + op_mode->ops = &iwl_mvm_ops; + trans->rx_mpdu_cmd_hdr_size = + sizeof(struct iwl_rx_mpdu_res_start); + + if (WARN_ON(trans->num_rx_queues > 1)) + goto out_free; + } + + mvm->fw_restart = iwlwifi_mod_params.fw_restart ? -1 : 0; + + if (iwl_mvm_has_new_tx_api(mvm)) { + /* + * If we have the new TX/queue allocation API initialize them + * all to invalid numbers. We'll rewrite the ones that we need + * later, but that doesn't happen for all of them all of the + * time (e.g. P2P Device is optional), and if a dynamic queue + * ends up getting number 2 (IWL_MVM_DQA_P2P_DEVICE_QUEUE) then + * iwl_mvm_is_static_queue() erroneously returns true, and we + * might have things getting stuck. + */ + mvm->aux_queue = IWL_MVM_INVALID_QUEUE; + mvm->snif_queue = IWL_MVM_INVALID_QUEUE; + mvm->probe_queue = IWL_MVM_INVALID_QUEUE; + mvm->p2p_dev_queue = IWL_MVM_INVALID_QUEUE; + } else { + mvm->aux_queue = IWL_MVM_DQA_AUX_QUEUE; + mvm->snif_queue = IWL_MVM_DQA_INJECT_MONITOR_QUEUE; + mvm->probe_queue = IWL_MVM_DQA_AP_PROBE_RESP_QUEUE; + mvm->p2p_dev_queue = IWL_MVM_DQA_P2P_DEVICE_QUEUE; + } + + mvm->sf_state = SF_UNINIT; + if (iwl_mvm_has_unified_ucode(mvm)) + iwl_fw_set_current_image(&mvm->fwrt, IWL_UCODE_REGULAR); + else + iwl_fw_set_current_image(&mvm->fwrt, IWL_UCODE_INIT); + mvm->drop_bcn_ap_mode = true; + + mutex_init(&mvm->mutex); + spin_lock_init(&mvm->async_handlers_lock); + INIT_LIST_HEAD(&mvm->time_event_list); + INIT_LIST_HEAD(&mvm->aux_roc_te_list); + INIT_LIST_HEAD(&mvm->async_handlers_list); + spin_lock_init(&mvm->time_event_lock); + INIT_LIST_HEAD(&mvm->ftm_initiator.loc_list); + INIT_LIST_HEAD(&mvm->ftm_initiator.pasn_list); + INIT_LIST_HEAD(&mvm->resp_pasn_list); + + INIT_WORK(&mvm->async_handlers_wk, iwl_mvm_async_handlers_wk); + INIT_WORK(&mvm->roc_done_wk, iwl_mvm_roc_done_wk); + INIT_DELAYED_WORK(&mvm->tdls_cs.dwork, iwl_mvm_tdls_ch_switch_work); + INIT_DELAYED_WORK(&mvm->scan_timeout_dwork, iwl_mvm_scan_timeout_wk); + INIT_WORK(&mvm->add_stream_wk, iwl_mvm_add_new_dqa_stream_wk); + INIT_LIST_HEAD(&mvm->add_stream_txqs); + + init_waitqueue_head(&mvm->rx_sync_waitq); + + mvm->queue_sync_state = 0; + + SET_IEEE80211_DEV(mvm->hw, mvm->trans->dev); + + spin_lock_init(&mvm->tcm.lock); + INIT_DELAYED_WORK(&mvm->tcm.work, iwl_mvm_tcm_work); + mvm->tcm.ts = jiffies; + mvm->tcm.ll_ts = jiffies; + mvm->tcm.uapsd_nonagg_ts = jiffies; + + INIT_DELAYED_WORK(&mvm->cs_tx_unblock_dwork, iwl_mvm_tx_unblock_dwork); + + mvm->cmd_ver.d0i3_resp = + iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, D0I3_END_CMD, + 0); + /* we only support version 1 */ + if (WARN_ON_ONCE(mvm->cmd_ver.d0i3_resp > 1)) + goto out_free; + + mvm->cmd_ver.range_resp = + iwl_fw_lookup_notif_ver(mvm->fw, LOCATION_GROUP, + TOF_RANGE_RESPONSE_NOTIF, 5); + /* we only support up to version 9 */ + if (WARN_ON_ONCE(mvm->cmd_ver.range_resp > 9)) + goto out_free; + + /* + * Populate the state variables that the transport layer needs + * to know about. + */ + trans_cfg.op_mode = op_mode; + trans_cfg.no_reclaim_cmds = no_reclaim_cmds; + trans_cfg.n_no_reclaim_cmds = ARRAY_SIZE(no_reclaim_cmds); + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + rb_size_default = IWL_AMSDU_2K; + else + rb_size_default = IWL_AMSDU_4K; + + switch (iwlwifi_mod_params.amsdu_size) { + case IWL_AMSDU_DEF: + trans_cfg.rx_buf_size = rb_size_default; + break; + case IWL_AMSDU_4K: + trans_cfg.rx_buf_size = IWL_AMSDU_4K; + break; + case IWL_AMSDU_8K: + trans_cfg.rx_buf_size = IWL_AMSDU_8K; + break; + case IWL_AMSDU_12K: + trans_cfg.rx_buf_size = IWL_AMSDU_12K; + break; + default: + pr_err("%s: Unsupported amsdu_size: %d\n", KBUILD_MODNAME, + iwlwifi_mod_params.amsdu_size); + trans_cfg.rx_buf_size = rb_size_default; + } + + trans->wide_cmd_header = true; + trans_cfg.bc_table_dword = + mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210; + + trans_cfg.command_groups = iwl_mvm_groups; + trans_cfg.command_groups_size = ARRAY_SIZE(iwl_mvm_groups); + + trans_cfg.cmd_queue = IWL_MVM_DQA_CMD_QUEUE; + trans_cfg.cmd_fifo = IWL_MVM_TX_FIFO_CMD; + trans_cfg.scd_set_active = true; + + trans_cfg.cb_data_offs = offsetof(struct ieee80211_tx_info, + driver_data[2]); + + /* Set a short watchdog for the command queue */ + trans_cfg.cmd_q_wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, NULL, false, true); + + snprintf(mvm->hw->wiphy->fw_version, + sizeof(mvm->hw->wiphy->fw_version), + "%s", fw->fw_version); + + trans_cfg.fw_reset_handshake = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_FW_RESET_HANDSHAKE); + + /* Configure transport layer */ + iwl_trans_configure(mvm->trans, &trans_cfg); + + trans->rx_mpdu_cmd = REPLY_RX_MPDU_CMD; + trans->dbg.dest_tlv = mvm->fw->dbg.dest_tlv; + trans->dbg.n_dest_reg = mvm->fw->dbg.n_dest_reg; + memcpy(trans->dbg.conf_tlv, mvm->fw->dbg.conf_tlv, + sizeof(trans->dbg.conf_tlv)); + trans->dbg.trigger_tlv = mvm->fw->dbg.trigger_tlv; + + trans->iml = mvm->fw->iml; + trans->iml_len = mvm->fw->iml_len; + + /* set up notification wait support */ + iwl_notification_wait_init(&mvm->notif_wait); + + /* Init phy db */ + mvm->phy_db = iwl_phy_db_init(trans); + if (!mvm->phy_db) { + IWL_ERR(mvm, "Cannot init phy_db\n"); + goto out_free; + } + + IWL_INFO(mvm, "Detected %s, REV=0x%X\n", + mvm->trans->name, mvm->trans->hw_rev); + + if (iwlwifi_mod_params.nvm_file) + mvm->nvm_file_name = iwlwifi_mod_params.nvm_file; + else + IWL_DEBUG_EEPROM(mvm->trans->dev, + "working without external nvm file\n"); + + err = iwl_trans_start_hw(mvm->trans); + if (err) + goto out_free; + + scan_size = iwl_mvm_scan_size(mvm); + + mvm->scan_cmd = kmalloc(scan_size, GFP_KERNEL); + if (!mvm->scan_cmd) + goto out_free; + + /* invalidate ids to prevent accidental removal of sta_id 0 */ + mvm->aux_sta.sta_id = IWL_MVM_INVALID_STA; + mvm->snif_sta.sta_id = IWL_MVM_INVALID_STA; + + /* Set EBS as successful as long as not stated otherwise by the FW. */ + mvm->last_ebs_successful = true; + + min_backoff = iwl_mvm_min_backoff(mvm); + iwl_mvm_thermal_initialize(mvm, min_backoff); + + if (!iwl_mvm_has_new_rx_stats_api(mvm)) + memset(&mvm->rx_stats_v3, 0, + sizeof(struct mvm_statistics_rx_v3)); + else + memset(&mvm->rx_stats, 0, sizeof(struct mvm_statistics_rx)); + + mvm->debugfs_dir = dbgfs_dir; + + if (iwl_mvm_start_get_nvm(mvm)) + goto out_thermal_exit; + + if (iwl_mvm_start_post_nvm(mvm)) + goto out_thermal_exit; + + return op_mode; + + out_thermal_exit: + iwl_mvm_thermal_exit(mvm); + out_free: + iwl_fw_flush_dumps(&mvm->fwrt); + iwl_fw_runtime_free(&mvm->fwrt); + + if (iwlmvm_mod_params.init_dbg) + return op_mode; + iwl_phy_db_free(mvm->phy_db); + kfree(mvm->scan_cmd); + iwl_trans_op_mode_leave(trans); + + ieee80211_free_hw(mvm->hw); + return NULL; +} + +void iwl_mvm_stop_device(struct iwl_mvm *mvm) +{ + lockdep_assert_held(&mvm->mutex); + + iwl_fw_cancel_timestamp(&mvm->fwrt); + + clear_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); + + iwl_fw_dbg_stop_sync(&mvm->fwrt); + iwl_trans_stop_device(mvm->trans); + iwl_free_fw_paging(&mvm->fwrt); + iwl_fw_dump_conf_clear(&mvm->fwrt); +} + +static void iwl_op_mode_mvm_stop(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + int i; + + iwl_mvm_leds_exit(mvm); + + iwl_mvm_thermal_exit(mvm); + + ieee80211_unregister_hw(mvm->hw); + + kfree(mvm->scan_cmd); + kfree(mvm->mcast_filter_cmd); + mvm->mcast_filter_cmd = NULL; + + kfree(mvm->error_recovery_buf); + mvm->error_recovery_buf = NULL; + + iwl_trans_op_mode_leave(mvm->trans); + + iwl_phy_db_free(mvm->phy_db); + mvm->phy_db = NULL; + + kfree(mvm->nvm_data); + for (i = 0; i < NVM_MAX_NUM_SECTIONS; i++) + kfree(mvm->nvm_sections[i].data); + + cancel_delayed_work_sync(&mvm->tcm.work); + + iwl_fw_runtime_free(&mvm->fwrt); + mutex_destroy(&mvm->mutex); + + ieee80211_free_hw(mvm->hw); +} + +struct iwl_async_handler_entry { + struct list_head list; + struct iwl_rx_cmd_buffer rxb; + enum iwl_rx_handler_context context; + void (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +}; + +void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm) +{ + struct iwl_async_handler_entry *entry, *tmp; + + spin_lock_bh(&mvm->async_handlers_lock); + list_for_each_entry_safe(entry, tmp, &mvm->async_handlers_list, list) { + iwl_free_rxb(&entry->rxb); + list_del(&entry->list); + kfree(entry); + } + spin_unlock_bh(&mvm->async_handlers_lock); +} + +static void iwl_mvm_async_handlers_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = + container_of(wk, struct iwl_mvm, async_handlers_wk); + struct iwl_async_handler_entry *entry, *tmp; + LIST_HEAD(local_list); + + /* Ensure that we are not in stop flow (check iwl_mvm_mac_stop) */ + + /* + * Sync with Rx path with a lock. Remove all the entries from this list, + * add them to a local one (lock free), and then handle them. + */ + spin_lock_bh(&mvm->async_handlers_lock); + list_splice_init(&mvm->async_handlers_list, &local_list); + spin_unlock_bh(&mvm->async_handlers_lock); + + list_for_each_entry_safe(entry, tmp, &local_list, list) { + if (entry->context == RX_HANDLER_ASYNC_LOCKED) + mutex_lock(&mvm->mutex); + entry->fn(mvm, &entry->rxb); + iwl_free_rxb(&entry->rxb); + list_del(&entry->list); + if (entry->context == RX_HANDLER_ASYNC_LOCKED) + mutex_unlock(&mvm->mutex); + kfree(entry); + } +} + +static inline void iwl_mvm_rx_check_trigger(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_cmd *cmds_trig; + int i; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, NULL, + FW_DBG_TRIGGER_FW_NOTIF); + if (!trig) + return; + + cmds_trig = (void *)trig->data; + + for (i = 0; i < ARRAY_SIZE(cmds_trig->cmds); i++) { + /* don't collect on CMD 0 */ + if (!cmds_trig->cmds[i].cmd_id) + break; + + if (cmds_trig->cmds[i].cmd_id != pkt->hdr.cmd || + cmds_trig->cmds[i].group_id != pkt->hdr.group_id) + continue; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "CMD 0x%02x.%02x received", + pkt->hdr.group_id, pkt->hdr.cmd); + break; + } +} + +static void iwl_mvm_rx_common(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_rx_packet *pkt) +{ + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + int i; + union iwl_dbg_tlv_tp_data tp_data = { .fw_pkt = pkt }; + + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_FW_RSP_OR_NOTIF, &tp_data); + iwl_mvm_rx_check_trigger(mvm, pkt); + + /* + * Do the notification wait before RX handlers so + * even if the RX handler consumes the RXB we have + * access to it in the notification wait entry. + */ + iwl_notification_wait_notify(&mvm->notif_wait, pkt); + + for (i = 0; i < ARRAY_SIZE(iwl_mvm_rx_handlers); i++) { + const struct iwl_rx_handlers *rx_h = &iwl_mvm_rx_handlers[i]; + struct iwl_async_handler_entry *entry; + + if (rx_h->cmd_id != WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd)) + continue; + + if (unlikely(pkt_len < rx_h->min_size)) + return; + + if (rx_h->context == RX_HANDLER_SYNC) { + rx_h->fn(mvm, rxb); + return; + } + + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); + /* we can't do much... */ + if (!entry) + return; + + entry->rxb._page = rxb_steal_page(rxb); + entry->rxb._offset = rxb->_offset; + entry->rxb._rx_page_order = rxb->_rx_page_order; + entry->fn = rx_h->fn; + entry->context = rx_h->context; + spin_lock(&mvm->async_handlers_lock); + list_add_tail(&entry->list, &mvm->async_handlers_list); + spin_unlock(&mvm->async_handlers_lock); + schedule_work(&mvm->async_handlers_wk); + break; + } +} + +static void iwl_mvm_rx(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + u16 cmd = WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd); + + if (likely(cmd == WIDE_ID(LEGACY_GROUP, REPLY_RX_MPDU_CMD))) + iwl_mvm_rx_rx_mpdu(mvm, napi, rxb); + else if (cmd == WIDE_ID(LEGACY_GROUP, REPLY_RX_PHY_CMD)) + iwl_mvm_rx_rx_phy_cmd(mvm, rxb); + else + iwl_mvm_rx_common(mvm, rxb, pkt); +} + +void iwl_mvm_rx_mq(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + u16 cmd = WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd); + + if (likely(cmd == WIDE_ID(LEGACY_GROUP, REPLY_RX_MPDU_CMD))) + iwl_mvm_rx_mpdu_mq(mvm, napi, rxb, 0); + else if (unlikely(cmd == WIDE_ID(DATA_PATH_GROUP, + RX_QUEUES_NOTIFICATION))) + iwl_mvm_rx_queue_notif(mvm, napi, rxb, 0); + else if (cmd == WIDE_ID(LEGACY_GROUP, FRAME_RELEASE)) + iwl_mvm_rx_frame_release(mvm, napi, rxb, 0); + else if (cmd == WIDE_ID(LEGACY_GROUP, BAR_FRAME_RELEASE)) + iwl_mvm_rx_bar_frame_release(mvm, napi, rxb, 0); + else if (cmd == WIDE_ID(DATA_PATH_GROUP, RX_NO_DATA_NOTIF)) + iwl_mvm_rx_monitor_no_data(mvm, napi, rxb, 0); + else + iwl_mvm_rx_common(mvm, rxb, pkt); +} + +static void iwl_mvm_async_cb(struct iwl_op_mode *op_mode, + const struct iwl_device_cmd *cmd) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + /* + * For now, we only set the CMD_WANT_ASYNC_CALLBACK for ADD_STA + * commands that need to block the Tx queues. + */ + iwl_trans_block_txq_ptrs(mvm->trans, false); +} + +static int iwl_mvm_is_static_queue(struct iwl_mvm *mvm, int queue) +{ + return queue == mvm->aux_queue || queue == mvm->probe_queue || + queue == mvm->p2p_dev_queue || queue == mvm->snif_queue; +} + +static void iwl_mvm_queue_state_change(struct iwl_op_mode *op_mode, + int hw_queue, bool start) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct ieee80211_sta *sta; + struct ieee80211_txq *txq; + struct iwl_mvm_txq *mvmtxq; + int i; + unsigned long tid_bitmap; + struct iwl_mvm_sta *mvmsta; + u8 sta_id; + + sta_id = iwl_mvm_has_new_tx_api(mvm) ? + mvm->tvqm_info[hw_queue].sta_id : + mvm->queue_info[hw_queue].ra_sta_id; + + if (WARN_ON_ONCE(sta_id >= mvm->fw->ucode_capa.num_stations)) + return; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (IS_ERR_OR_NULL(sta)) + goto out; + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (iwl_mvm_is_static_queue(mvm, hw_queue)) { + if (!start) + ieee80211_stop_queues(mvm->hw); + else if (mvmsta->sta_state != IEEE80211_STA_NOTEXIST) + ieee80211_wake_queues(mvm->hw); + + goto out; + } + + if (iwl_mvm_has_new_tx_api(mvm)) { + int tid = mvm->tvqm_info[hw_queue].txq_tid; + + tid_bitmap = BIT(tid); + } else { + tid_bitmap = mvm->queue_info[hw_queue].tid_bitmap; + } + + for_each_set_bit(i, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + int tid = i; + + if (tid == IWL_MAX_TID_COUNT) + tid = IEEE80211_NUM_TIDS; + + txq = sta->txq[tid]; + mvmtxq = iwl_mvm_txq_from_mac80211(txq); + mvmtxq->stopped = !start; + + if (start && mvmsta->sta_state != IEEE80211_STA_NOTEXIST) + iwl_mvm_mac_itxq_xmit(mvm->hw, txq); + } + +out: + rcu_read_unlock(); +} + +static void iwl_mvm_stop_sw_queue(struct iwl_op_mode *op_mode, int hw_queue) +{ + iwl_mvm_queue_state_change(op_mode, hw_queue, false); +} + +static void iwl_mvm_wake_sw_queue(struct iwl_op_mode *op_mode, int hw_queue) +{ + iwl_mvm_queue_state_change(op_mode, hw_queue, true); +} + +static void iwl_mvm_set_rfkill_state(struct iwl_mvm *mvm) +{ + bool state = iwl_mvm_is_radio_killed(mvm); + + if (state) + wake_up(&mvm->rx_sync_waitq); + + wiphy_rfkill_set_hw_state(mvm->hw->wiphy, state); +} + +void iwl_mvm_set_hw_ctkill_state(struct iwl_mvm *mvm, bool state) +{ + if (state) + set_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status); + else + clear_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status); + + iwl_mvm_set_rfkill_state(mvm); +} + +static bool iwl_mvm_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + bool rfkill_safe_init_done = READ_ONCE(mvm->rfkill_safe_init_done); + bool unified = iwl_mvm_has_unified_ucode(mvm); + + if (state) + set_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); + else + clear_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); + + iwl_mvm_set_rfkill_state(mvm); + + /* iwl_run_init_mvm_ucode is waiting for results, abort it. */ + if (rfkill_safe_init_done) + iwl_abort_notification_waits(&mvm->notif_wait); + + /* + * Don't ask the transport to stop the firmware. We'll do it + * after cfg80211 takes us down. + */ + if (unified) + return false; + + /* + * Stop the device if we run OPERATIONAL firmware or if we are in the + * middle of the calibrations. + */ + return state && rfkill_safe_init_done; +} + +static void iwl_mvm_free_skb(struct iwl_op_mode *op_mode, struct sk_buff *skb) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct ieee80211_tx_info *info; + + info = IEEE80211_SKB_CB(skb); + iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + ieee80211_free_txskb(mvm->hw, skb); +} + +struct iwl_mvm_reprobe { + struct device *dev; + struct work_struct work; +}; + +static void iwl_mvm_reprobe_wk(struct work_struct *wk) +{ + struct iwl_mvm_reprobe *reprobe; + + reprobe = container_of(wk, struct iwl_mvm_reprobe, work); + if (device_reprobe(reprobe->dev)) + dev_err(reprobe->dev, "reprobe failed!\n"); + put_device(reprobe->dev); + kfree(reprobe); + module_put(THIS_MODULE); +} + +void iwl_mvm_nic_restart(struct iwl_mvm *mvm, bool fw_error) +{ + iwl_abort_notification_waits(&mvm->notif_wait); + iwl_dbg_tlv_del_timers(mvm->trans); + + /* + * This is a bit racy, but worst case we tell mac80211 about + * a stopped/aborted scan when that was already done which + * is not a problem. It is necessary to abort any os scan + * here because mac80211 requires having the scan cleared + * before restarting. + * We'll reset the scan_status to NONE in restart cleanup in + * the next start() call from mac80211. If restart isn't called + * (no fw restart) scan status will stay busy. + */ + iwl_mvm_report_scan_aborted(mvm); + + /* + * If we're restarting already, don't cycle restarts. + * If INIT fw asserted, it will likely fail again. + * If WoWLAN fw asserted, don't restart either, mac80211 + * can't recover this since we're already half suspended. + */ + if (!mvm->fw_restart && fw_error) { + iwl_fw_error_collect(&mvm->fwrt, false); + } else if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + struct iwl_mvm_reprobe *reprobe; + + IWL_ERR(mvm, + "Firmware error during reconfiguration - reprobe!\n"); + + /* + * get a module reference to avoid doing this while unloading + * anyway and to avoid scheduling a work with code that's + * being removed. + */ + if (!try_module_get(THIS_MODULE)) { + IWL_ERR(mvm, "Module is being unloaded - abort\n"); + return; + } + + reprobe = kzalloc(sizeof(*reprobe), GFP_ATOMIC); + if (!reprobe) { + module_put(THIS_MODULE); + return; + } + reprobe->dev = get_device(mvm->trans->dev); + INIT_WORK(&reprobe->work, iwl_mvm_reprobe_wk); + schedule_work(&reprobe->work); + } else if (test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status)) { + IWL_ERR(mvm, "HW restart already requested, but not started\n"); + } else if (mvm->fwrt.cur_fw_img == IWL_UCODE_REGULAR && + mvm->hw_registered && + !test_bit(STATUS_TRANS_DEAD, &mvm->trans->status)) { + /* This should be first thing before trying to collect any + * data to avoid endless loops if any HW error happens while + * collecting debug data. + */ + set_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status); + + if (mvm->fw->ucode_capa.error_log_size) { + u32 src_size = mvm->fw->ucode_capa.error_log_size; + u32 src_addr = mvm->fw->ucode_capa.error_log_addr; + u8 *recover_buf = kzalloc(src_size, GFP_ATOMIC); + + if (recover_buf) { + mvm->error_recovery_buf = recover_buf; + iwl_trans_read_mem_bytes(mvm->trans, + src_addr, + recover_buf, + src_size); + } + } + + iwl_fw_error_collect(&mvm->fwrt, false); + + if (fw_error && mvm->fw_restart > 0) + mvm->fw_restart--; + ieee80211_restart_hw(mvm->hw); + } +} + +static void iwl_mvm_nic_error(struct iwl_op_mode *op_mode, bool sync) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + if (!test_bit(STATUS_TRANS_DEAD, &mvm->trans->status)) + iwl_mvm_dump_nic_error_log(mvm); + + if (sync) { + iwl_fw_error_collect(&mvm->fwrt, true); + /* + * Currently, the only case for sync=true is during + * shutdown, so just stop in this case. If/when that + * changes, we need to be a bit smarter here. + */ + return; + } + + /* + * If the firmware crashes while we're already considering it + * to be dead then don't ask for a restart, that cannot do + * anything useful anyway. + */ + if (!test_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status)) + return; + + iwl_mvm_nic_restart(mvm, true); +} + +static void iwl_mvm_cmd_queue_full(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + WARN_ON(1); + iwl_mvm_nic_restart(mvm, true); +} + +static void iwl_op_mode_mvm_time_point(struct iwl_op_mode *op_mode, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + iwl_dbg_tlv_time_point(&mvm->fwrt, tp_id, tp_data); +} + +#define IWL_MVM_COMMON_OPS \ + /* these could be differentiated */ \ + .async_cb = iwl_mvm_async_cb, \ + .queue_full = iwl_mvm_stop_sw_queue, \ + .queue_not_full = iwl_mvm_wake_sw_queue, \ + .hw_rf_kill = iwl_mvm_set_hw_rfkill_state, \ + .free_skb = iwl_mvm_free_skb, \ + .nic_error = iwl_mvm_nic_error, \ + .cmd_queue_full = iwl_mvm_cmd_queue_full, \ + .nic_config = iwl_mvm_nic_config, \ + /* as we only register one, these MUST be common! */ \ + .start = iwl_op_mode_mvm_start, \ + .stop = iwl_op_mode_mvm_stop, \ + .time_point = iwl_op_mode_mvm_time_point + +static const struct iwl_op_mode_ops iwl_mvm_ops = { + IWL_MVM_COMMON_OPS, + .rx = iwl_mvm_rx, +}; + +static void iwl_mvm_rx_mq_rss(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, + unsigned int queue) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u16 cmd = WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd); + + if (unlikely(cmd == WIDE_ID(LEGACY_GROUP, FRAME_RELEASE))) + iwl_mvm_rx_frame_release(mvm, napi, rxb, queue); + else if (unlikely(cmd == WIDE_ID(DATA_PATH_GROUP, + RX_QUEUES_NOTIFICATION))) + iwl_mvm_rx_queue_notif(mvm, napi, rxb, queue); + else if (likely(cmd == WIDE_ID(LEGACY_GROUP, REPLY_RX_MPDU_CMD))) + iwl_mvm_rx_mpdu_mq(mvm, napi, rxb, queue); +} + +static const struct iwl_op_mode_ops iwl_mvm_ops_mq = { + IWL_MVM_COMMON_OPS, + .rx = iwl_mvm_rx_mq, + .rx_rss = iwl_mvm_rx_mq_rss, +}; diff --git a/sys/contrib/dev/iwlwifi/mvm/phy-ctxt.c b/sys/contrib/dev/iwlwifi/mvm/phy-ctxt.c new file mode 100644 index 00000000000..035336a9e75 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/phy-ctxt.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#include +#include "fw-api.h" +#include "mvm.h" + +/* Maps the driver specific channel width definition to the fw values */ +u8 iwl_mvm_get_channel_width(struct cfg80211_chan_def *chandef) +{ + switch (chandef->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + return PHY_VHT_CHANNEL_MODE20; + case NL80211_CHAN_WIDTH_40: + return PHY_VHT_CHANNEL_MODE40; + case NL80211_CHAN_WIDTH_80: + return PHY_VHT_CHANNEL_MODE80; + case NL80211_CHAN_WIDTH_160: + return PHY_VHT_CHANNEL_MODE160; + default: + WARN(1, "Invalid channel width=%u", chandef->width); + return PHY_VHT_CHANNEL_MODE20; + } +} + +/* + * Maps the driver specific control channel position (relative to the center + * freq) definitions to the the fw values + */ +u8 iwl_mvm_get_ctrl_pos(struct cfg80211_chan_def *chandef) +{ + switch (chandef->chan->center_freq - chandef->center_freq1) { + case -70: + return PHY_VHT_CTRL_POS_4_BELOW; + case -50: + return PHY_VHT_CTRL_POS_3_BELOW; + case -30: + return PHY_VHT_CTRL_POS_2_BELOW; + case -10: + return PHY_VHT_CTRL_POS_1_BELOW; + case 10: + return PHY_VHT_CTRL_POS_1_ABOVE; + case 30: + return PHY_VHT_CTRL_POS_2_ABOVE; + case 50: + return PHY_VHT_CTRL_POS_3_ABOVE; + case 70: + return PHY_VHT_CTRL_POS_4_ABOVE; + default: + WARN(1, "Invalid channel definition"); + fallthrough; + case 0: + /* + * The FW is expected to check the control channel position only + * when in HT/VHT and the channel width is not 20MHz. Return + * this value as the default one. + */ + return PHY_VHT_CTRL_POS_1_BELOW; + } +} + +/* + * Construct the generic fields of the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_hdr(struct iwl_mvm_phy_ctxt *ctxt, + struct iwl_phy_context_cmd *cmd, + u32 action) +{ + cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(ctxt->id, + ctxt->color)); + cmd->action = cpu_to_le32(action); +} + +static void iwl_mvm_phy_ctxt_set_rxchain(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + __le32 *rxchain_info, + u8 chains_static, + u8 chains_dynamic) +{ + u8 active_cnt, idle_cnt; + + /* Set rx the chains */ + idle_cnt = chains_static; + active_cnt = chains_dynamic; + + /* In scenarios where we only ever use a single-stream rates, + * i.e. legacy 11b/g/a associations, single-stream APs or even + * static SMPS, enable both chains to get diversity, improving + * the case where we're far enough from the AP that attenuation + * between the two antennas is sufficiently different to impact + * performance. + */ + if (active_cnt == 1 && iwl_mvm_rx_diversity_allowed(mvm, ctxt)) { + idle_cnt = 2; + active_cnt = 2; + } + + /* + * If the firmware requested it, then we know that it supports + * getting zero for the values to indicate "use one, but pick + * which one yourself", which means it can dynamically pick one + * that e.g. has better RSSI. + */ + if (mvm->fw_static_smps_request && active_cnt == 1 && idle_cnt == 1) { + idle_cnt = 0; + active_cnt = 0; + } + + *rxchain_info = cpu_to_le32(iwl_mvm_get_valid_rx_ant(mvm) << + PHY_RX_CHAIN_VALID_POS); + *rxchain_info |= cpu_to_le32(idle_cnt << PHY_RX_CHAIN_CNT_POS); + *rxchain_info |= cpu_to_le32(active_cnt << + PHY_RX_CHAIN_MIMO_CNT_POS); +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (unlikely(mvm->dbgfs_rx_phyinfo)) + *rxchain_info = cpu_to_le32(mvm->dbgfs_rx_phyinfo); +#endif +} + +/* + * Add the phy configuration to the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_data_v1(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + struct iwl_phy_context_cmd_v1 *cmd, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + struct iwl_phy_context_cmd_tail *tail = + iwl_mvm_chan_info_cmd_tail(mvm, &cmd->ci); + + /* Set the channel info data */ + iwl_mvm_set_chan_info_chandef(mvm, &cmd->ci, chandef); + + iwl_mvm_phy_ctxt_set_rxchain(mvm, ctxt, &tail->rxchain_info, + chains_static, chains_dynamic); + + tail->txchain_info = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); +} + +/* + * Add the phy configuration to the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_data(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + struct iwl_phy_context_cmd *cmd, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + cmd->lmac_id = cpu_to_le32(iwl_mvm_get_lmac_id(mvm->fw, + chandef->chan->band)); + + /* Set the channel info data */ + iwl_mvm_set_chan_info_chandef(mvm, &cmd->ci, chandef); + + iwl_mvm_phy_ctxt_set_rxchain(mvm, ctxt, &cmd->rxchain_info, + chains_static, chains_dynamic); +} + +/* + * Send a command to apply the current phy configuration. The command is send + * only if something in the configuration changed: in case that this is the + * first time that the phy configuration is applied or in case that the phy + * configuration changed from the previous apply. + */ +static int iwl_mvm_phy_ctxt_apply(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic, + u32 action) +{ + int ret; + int ver = iwl_fw_lookup_cmd_ver(mvm->fw, IWL_ALWAYS_LONG_GROUP, + PHY_CONTEXT_CMD, 1); + + if (ver == 3) { + struct iwl_phy_context_cmd cmd = {}; + + /* Set the command header fields */ + iwl_mvm_phy_ctxt_cmd_hdr(ctxt, &cmd, action); + + /* Set the command data */ + iwl_mvm_phy_ctxt_cmd_data(mvm, ctxt, &cmd, chandef, + chains_static, + chains_dynamic); + + ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, + 0, sizeof(cmd), &cmd); + } else if (ver < 3) { + struct iwl_phy_context_cmd_v1 cmd = {}; + u16 len = sizeof(cmd) - iwl_mvm_chan_info_padding(mvm); + + /* Set the command header fields */ + iwl_mvm_phy_ctxt_cmd_hdr(ctxt, + (struct iwl_phy_context_cmd *)&cmd, + action); + + /* Set the command data */ + iwl_mvm_phy_ctxt_cmd_data_v1(mvm, ctxt, &cmd, chandef, + chains_static, + chains_dynamic); + ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, + 0, len, &cmd); + } else { + IWL_ERR(mvm, "PHY ctxt cmd error ver %d not supported\n", ver); + return -EOPNOTSUPP; + } + + + if (ret) + IWL_ERR(mvm, "PHY ctxt cmd error. ret=%d\n", ret); + return ret; +} + +/* + * Send a command to add a PHY context based on the current HW configuration. + */ +int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + WARN_ON(!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + ctxt->ref); + lockdep_assert_held(&mvm->mutex); + + ctxt->channel = chandef->chan; + + return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, + chains_static, chains_dynamic, + FW_CTXT_ACTION_ADD); +} + +/* + * Update the number of references to the given PHY context. This is valid only + * in case the PHY context was already created, i.e., its reference count > 0. + */ +void iwl_mvm_phy_ctxt_ref(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt) +{ + lockdep_assert_held(&mvm->mutex); + ctxt->ref++; +} + +/* + * Send a command to modify the PHY context based on the current HW + * configuration. Note that the function does not check that the configuration + * changed. + */ +int iwl_mvm_phy_ctxt_changed(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + enum iwl_ctxt_action action = FW_CTXT_ACTION_MODIFY; + + lockdep_assert_held(&mvm->mutex); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT) && + ctxt->channel->band != chandef->chan->band) { + int ret; + + /* ... remove it here ...*/ + ret = iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, + chains_static, chains_dynamic, + FW_CTXT_ACTION_REMOVE); + if (ret) + return ret; + + /* ... and proceed to add it again */ + action = FW_CTXT_ACTION_ADD; + } + + ctxt->channel = chandef->chan; + ctxt->width = chandef->width; + return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, + chains_static, chains_dynamic, + action); +} + +void iwl_mvm_phy_ctxt_unref(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt) +{ + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(!ctxt)) + return; + + ctxt->ref--; + + /* + * Move unused phy's to a default channel. When the phy is moved the, + * fw will cleanup immediate quiet bit if it was previously set, + * otherwise we might not be able to reuse this phy. + */ + if (ctxt->ref == 0) { + struct ieee80211_channel *chan; + struct cfg80211_chan_def chandef; + struct ieee80211_supported_band *sband = NULL; + enum nl80211_band band = NL80211_BAND_2GHZ; + + while (!sband && band < NUM_NL80211_BANDS) + sband = mvm->hw->wiphy->bands[band++]; + + if (WARN_ON(!sband)) + return; + + chan = &sband->channels[0]; + + cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT); + iwl_mvm_phy_ctxt_changed(mvm, ctxt, &chandef, 1, 1); + } +} + +static void iwl_mvm_binding_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + unsigned long *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (!mvmvif->phy_ctxt) + return; + + if (vif->type == NL80211_IFTYPE_STATION || + vif->type == NL80211_IFTYPE_AP) + __set_bit(mvmvif->phy_ctxt->id, data); +} + +int iwl_mvm_phy_ctx_count(struct iwl_mvm *mvm) +{ + unsigned long phy_ctxt_counter = 0; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_binding_iterator, + &phy_ctxt_counter); + + return hweight8(phy_ctxt_counter); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/power.c b/sys/contrib/dev/iwlwifi/mvm/power.c new file mode 100644 index 00000000000..b2ea2fca537 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/power.c @@ -0,0 +1,956 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include +#include +#include +#include + +#include + +#include "iwl-debug.h" +#include "mvm.h" +#include "iwl-modparams.h" +#include "fw/api/power.h" + +#define POWER_KEEP_ALIVE_PERIOD_SEC 25 + +static +int iwl_mvm_beacon_filter_send_cmd(struct iwl_mvm *mvm, + struct iwl_beacon_filter_cmd *cmd, + u32 flags) +{ + u16 len; + + IWL_DEBUG_POWER(mvm, "ba_enable_beacon_abort is: %d\n", + le32_to_cpu(cmd->ba_enable_beacon_abort)); + IWL_DEBUG_POWER(mvm, "ba_escape_timer is: %d\n", + le32_to_cpu(cmd->ba_escape_timer)); + IWL_DEBUG_POWER(mvm, "bf_debug_flag is: %d\n", + le32_to_cpu(cmd->bf_debug_flag)); + IWL_DEBUG_POWER(mvm, "bf_enable_beacon_filter is: %d\n", + le32_to_cpu(cmd->bf_enable_beacon_filter)); + IWL_DEBUG_POWER(mvm, "bf_energy_delta is: %d\n", + le32_to_cpu(cmd->bf_energy_delta)); + IWL_DEBUG_POWER(mvm, "bf_escape_timer is: %d\n", + le32_to_cpu(cmd->bf_escape_timer)); + IWL_DEBUG_POWER(mvm, "bf_roaming_energy_delta is: %d\n", + le32_to_cpu(cmd->bf_roaming_energy_delta)); + IWL_DEBUG_POWER(mvm, "bf_roaming_state is: %d\n", + le32_to_cpu(cmd->bf_roaming_state)); + IWL_DEBUG_POWER(mvm, "bf_temp_threshold is: %d\n", + le32_to_cpu(cmd->bf_temp_threshold)); + IWL_DEBUG_POWER(mvm, "bf_temp_fast_filter is: %d\n", + le32_to_cpu(cmd->bf_temp_fast_filter)); + IWL_DEBUG_POWER(mvm, "bf_temp_slow_filter is: %d\n", + le32_to_cpu(cmd->bf_temp_slow_filter)); + IWL_DEBUG_POWER(mvm, "bf_threshold_absolute_low is: %d, %d\n", + le32_to_cpu(cmd->bf_threshold_absolute_low[0]), + le32_to_cpu(cmd->bf_threshold_absolute_low[1])); + + IWL_DEBUG_POWER(mvm, "bf_threshold_absolute_high is: %d, %d\n", + le32_to_cpu(cmd->bf_threshold_absolute_high[0]), + le32_to_cpu(cmd->bf_threshold_absolute_high[1])); + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_BEACON_FILTER_V4)) + len = sizeof(struct iwl_beacon_filter_cmd); + else + len = offsetof(struct iwl_beacon_filter_cmd, + bf_threshold_absolute_low); + + return iwl_mvm_send_cmd_pdu(mvm, REPLY_BEACON_FILTERING_CMD, flags, + len, cmd); +} + +static +void iwl_mvm_beacon_filter_set_cqm_params(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->bss_conf.cqm_rssi_thold) { + cmd->bf_energy_delta = + cpu_to_le32(vif->bss_conf.cqm_rssi_hyst); + /* fw uses an absolute value for this */ + cmd->bf_roaming_state = + cpu_to_le32(-vif->bss_conf.cqm_rssi_thold); + } + cmd->ba_enable_beacon_abort = cpu_to_le32(mvmvif->bf_data.ba_enabled); +} + +static void iwl_mvm_power_log(struct iwl_mvm *mvm, + struct iwl_mac_power_cmd *cmd) +{ + IWL_DEBUG_POWER(mvm, + "Sending power table command on mac id 0x%X for power level %d, flags = 0x%X\n", + cmd->id_and_color, iwlmvm_mod_params.power_scheme, + le16_to_cpu(cmd->flags)); + IWL_DEBUG_POWER(mvm, "Keep alive = %u sec\n", + le16_to_cpu(cmd->keep_alive_seconds)); + + if (!(cmd->flags & cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK))) { + IWL_DEBUG_POWER(mvm, "Disable power management\n"); + return; + } + + IWL_DEBUG_POWER(mvm, "Rx timeout = %u usec\n", + le32_to_cpu(cmd->rx_data_timeout)); + IWL_DEBUG_POWER(mvm, "Tx timeout = %u usec\n", + le32_to_cpu(cmd->tx_data_timeout)); + if (cmd->flags & cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK)) + IWL_DEBUG_POWER(mvm, "DTIM periods to skip = %u\n", + cmd->skip_dtim_periods); + if (cmd->flags & cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK)) + IWL_DEBUG_POWER(mvm, "LP RX RSSI threshold = %u\n", + cmd->lprx_rssi_threshold); + if (cmd->flags & cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK)) { + IWL_DEBUG_POWER(mvm, "uAPSD enabled\n"); + IWL_DEBUG_POWER(mvm, "Rx timeout (uAPSD) = %u usec\n", + le32_to_cpu(cmd->rx_data_timeout_uapsd)); + IWL_DEBUG_POWER(mvm, "Tx timeout (uAPSD) = %u usec\n", + le32_to_cpu(cmd->tx_data_timeout_uapsd)); + IWL_DEBUG_POWER(mvm, "QNDP TID = %d\n", cmd->qndp_tid); + IWL_DEBUG_POWER(mvm, "ACs flags = 0x%x\n", cmd->uapsd_ac_flags); + IWL_DEBUG_POWER(mvm, "Max SP = %d\n", cmd->uapsd_max_sp); + } +} + +static void iwl_mvm_power_configure_uapsd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd *cmd) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + enum ieee80211_ac_numbers ac; + bool tid_found = false; + + if (test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status) || + cmd->flags & cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK)) { + cmd->rx_data_timeout_uapsd = + cpu_to_le32(IWL_MVM_WOWLAN_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout_uapsd = + cpu_to_le32(IWL_MVM_WOWLAN_PS_TX_DATA_TIMEOUT); + } else { + cmd->rx_data_timeout_uapsd = + cpu_to_le32(IWL_MVM_UAPSD_RX_DATA_TIMEOUT); + cmd->tx_data_timeout_uapsd = + cpu_to_le32(IWL_MVM_UAPSD_TX_DATA_TIMEOUT); + } + +#ifdef CONFIG_IWLWIFI_DEBUGFS + /* set advanced pm flag with no uapsd ACs to enable ps-poll */ + if (mvmvif->dbgfs_pm.use_ps_poll) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK); + return; + } +#endif + + for (ac = IEEE80211_AC_VO; ac <= IEEE80211_AC_BK; ac++) { + if (!mvmvif->queue_params[ac].uapsd) + continue; + + if (!test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) + cmd->flags |= + cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK); + + cmd->uapsd_ac_flags |= BIT(ac); + + /* QNDP TID - the highest TID with no admission control */ + if (!tid_found && !mvmvif->queue_params[ac].acm) { + tid_found = true; + switch (ac) { + case IEEE80211_AC_VO: + cmd->qndp_tid = 6; + break; + case IEEE80211_AC_VI: + cmd->qndp_tid = 5; + break; + case IEEE80211_AC_BE: + cmd->qndp_tid = 0; + break; + case IEEE80211_AC_BK: + cmd->qndp_tid = 1; + break; + } + } + } + + cmd->flags |= cpu_to_le16(POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK); + + if (cmd->uapsd_ac_flags == (BIT(IEEE80211_AC_VO) | + BIT(IEEE80211_AC_VI) | + BIT(IEEE80211_AC_BE) | + BIT(IEEE80211_AC_BK))) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK); + cmd->snooze_interval = cpu_to_le16(IWL_MVM_PS_SNOOZE_INTERVAL); + cmd->snooze_window = + test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status) ? + cpu_to_le16(IWL_MVM_WOWLAN_PS_SNOOZE_WINDOW) : + cpu_to_le16(IWL_MVM_PS_SNOOZE_WINDOW); + } + + cmd->uapsd_max_sp = mvm->hw->uapsd_max_sp_len; + + if (cmd->flags & cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK)) { + cmd->heavy_tx_thld_packets = + IWL_MVM_PS_SNOOZE_HEAVY_TX_THLD_PACKETS; + cmd->heavy_rx_thld_packets = + IWL_MVM_PS_SNOOZE_HEAVY_RX_THLD_PACKETS; + } else { + cmd->heavy_tx_thld_packets = + IWL_MVM_PS_HEAVY_TX_THLD_PACKETS; + cmd->heavy_rx_thld_packets = + IWL_MVM_PS_HEAVY_RX_THLD_PACKETS; + } + cmd->heavy_tx_thld_percentage = + IWL_MVM_PS_HEAVY_TX_THLD_PERCENT; + cmd->heavy_rx_thld_percentage = + IWL_MVM_PS_HEAVY_RX_THLD_PERCENT; +} + +static void iwl_mvm_p2p_standalone_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + bool *is_p2p_standalone = _data; + + switch (ieee80211_vif_type_p2p(vif)) { + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_AP: + *is_p2p_standalone = false; + break; + case NL80211_IFTYPE_STATION: + if (vif->bss_conf.assoc) + *is_p2p_standalone = false; + break; + + default: + break; + } +} + +static bool iwl_mvm_power_allow_uapsd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (!memcmp(mvmvif->uapsd_misbehaving_bssid, vif->bss_conf.bssid, + ETH_ALEN)) + return false; + + /* + * Avoid using uAPSD if P2P client is associated to GO that uses + * opportunistic power save. This is due to current FW limitation. + */ + if (vif->p2p && + (vif->bss_conf.p2p_noa_attr.oppps_ctwindow & + IEEE80211_P2P_OPPPS_ENABLE_BIT)) + return false; + + /* + * Avoid using uAPSD if client is in DCM - + * low latency issue in Miracast + */ + if (iwl_mvm_phy_ctx_count(mvm) >= 2) + return false; + + if (vif->p2p) { + /* Allow U-APSD only if p2p is stand alone */ + bool is_p2p_standalone = true; + + if (!iwl_mvm_is_p2p_scm_uapsd_supported(mvm)) + return false; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_p2p_standalone_iterator, + &is_p2p_standalone); + + if (!is_p2p_standalone) + return false; + } + + return true; +} + +static bool iwl_mvm_power_is_radar(struct ieee80211_vif *vif) +{ + struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_channel *chan; + bool radar_detect = false; + + rcu_read_lock(); + chanctx_conf = rcu_dereference(vif->chanctx_conf); + WARN_ON(!chanctx_conf); + if (chanctx_conf) { + chan = chanctx_conf->def.chan; + radar_detect = chan->flags & IEEE80211_CHAN_RADAR; + } + rcu_read_unlock(); + + return radar_detect; +} + +static void iwl_mvm_power_config_skip_dtim(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd *cmd) +{ + int dtimper = vif->bss_conf.dtim_period ?: 1; + int skip; + + /* disable, in case we're supposed to override */ + cmd->skip_dtim_periods = 0; + cmd->flags &= ~cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); + + if (iwl_mvm_power_is_radar(vif)) + return; + + if (dtimper >= 10) + return; + + if (!test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) { + if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_LP) + return; + skip = 2; + } else { + int dtimper_tu = dtimper * vif->bss_conf.beacon_int; + + if (WARN_ON(!dtimper_tu)) + return; + /* configure skip over dtim up to 306TU - 314 msec */ + skip = max_t(u8, 1, 306 / dtimper_tu); + } + + /* the firmware really expects "look at every X DTIMs", so add 1 */ + cmd->skip_dtim_periods = 1 + skip; + cmd->flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); +} + +static void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd *cmd) +{ + int dtimper, bi; + int keep_alive; + struct iwl_mvm_vif *mvmvif __maybe_unused = + iwl_mvm_vif_from_mac80211(vif); + + cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + dtimper = vif->bss_conf.dtim_period; + bi = vif->bss_conf.beacon_int; + + /* + * Regardless of power management state the driver must set + * keep alive period. FW will use it for sending keep alive NDPs + * immediately after association. Check that keep alive period + * is at least 3 * DTIM + */ + keep_alive = DIV_ROUND_UP(ieee80211_tu_to_usec(3 * dtimper * bi), + USEC_PER_SEC); + keep_alive = max(keep_alive, POWER_KEEP_ALIVE_PERIOD_SEC); + cmd->keep_alive_seconds = cpu_to_le16(keep_alive); + + if (mvm->ps_disabled) + return; + + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK); + + if (!vif->bss_conf.ps || !mvmvif->pm_enabled) + return; + + if (iwl_mvm_vif_low_latency(mvmvif) && vif->p2p && + (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS) || + !IWL_MVM_P2P_LOWLATENCY_PS_ENABLE)) + return; + + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + + if (vif->bss_conf.beacon_rate && + (vif->bss_conf.beacon_rate->bitrate == 10 || + vif->bss_conf.beacon_rate->bitrate == 60)) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK); + cmd->lprx_rssi_threshold = POWER_LPRX_RSSI_THRESHOLD; + } + + iwl_mvm_power_config_skip_dtim(mvm, vif, cmd); + + if (test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) { + cmd->rx_data_timeout = + cpu_to_le32(IWL_MVM_WOWLAN_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout = + cpu_to_le32(IWL_MVM_WOWLAN_PS_TX_DATA_TIMEOUT); + } else if (iwl_mvm_vif_low_latency(mvmvif) && vif->p2p && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS)) { + cmd->tx_data_timeout = + cpu_to_le32(IWL_MVM_SHORT_PS_TX_DATA_TIMEOUT); + cmd->rx_data_timeout = + cpu_to_le32(IWL_MVM_SHORT_PS_RX_DATA_TIMEOUT); + } else { + cmd->rx_data_timeout = + cpu_to_le32(IWL_MVM_DEFAULT_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout = + cpu_to_le32(IWL_MVM_DEFAULT_PS_TX_DATA_TIMEOUT); + } + + if (iwl_mvm_power_allow_uapsd(mvm, vif)) + iwl_mvm_power_configure_uapsd(mvm, vif, cmd); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_KEEP_ALIVE) + cmd->keep_alive_seconds = + cpu_to_le16(mvmvif->dbgfs_pm.keep_alive_seconds); + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_SKIP_OVER_DTIM) { + if (mvmvif->dbgfs_pm.skip_over_dtim) + cmd->flags |= + cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); + else + cmd->flags &= + cpu_to_le16(~POWER_FLAGS_SKIP_OVER_DTIM_MSK); + } + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_RX_DATA_TIMEOUT) + cmd->rx_data_timeout = + cpu_to_le32(mvmvif->dbgfs_pm.rx_data_timeout); + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_TX_DATA_TIMEOUT) + cmd->tx_data_timeout = + cpu_to_le32(mvmvif->dbgfs_pm.tx_data_timeout); + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_SKIP_DTIM_PERIODS) + cmd->skip_dtim_periods = mvmvif->dbgfs_pm.skip_dtim_periods; + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_LPRX_ENA) { + if (mvmvif->dbgfs_pm.lprx_ena) + cmd->flags |= cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK); + else + cmd->flags &= cpu_to_le16(~POWER_FLAGS_LPRX_ENA_MSK); + } + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_LPRX_RSSI_THRESHOLD) + cmd->lprx_rssi_threshold = mvmvif->dbgfs_pm.lprx_rssi_threshold; + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_SNOOZE_ENABLE) { + if (mvmvif->dbgfs_pm.snooze_ena) + cmd->flags |= + cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK); + else + cmd->flags &= + cpu_to_le16(~POWER_FLAGS_SNOOZE_ENA_MSK); + } + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_UAPSD_MISBEHAVING) { + u16 flag = POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK; + if (mvmvif->dbgfs_pm.uapsd_misbehaving) + cmd->flags |= cpu_to_le16(flag); + else + cmd->flags &= cpu_to_le16(flag); + } +#endif /* CONFIG_IWLWIFI_DEBUGFS */ +} + +static int iwl_mvm_power_send_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mac_power_cmd cmd = {}; + + iwl_mvm_power_build_cmd(mvm, vif, &cmd); + iwl_mvm_power_log(mvm, &cmd); +#ifdef CONFIG_IWLWIFI_DEBUGFS + memcpy(&iwl_mvm_vif_from_mac80211(vif)->mac_pwr_cmd, &cmd, sizeof(cmd)); +#endif + + return iwl_mvm_send_cmd_pdu(mvm, MAC_PM_POWER_TABLE, 0, + sizeof(cmd), &cmd); +} + +int iwl_mvm_power_update_device(struct iwl_mvm *mvm) +{ + struct iwl_device_power_cmd cmd = { + .flags = 0, + }; + + if (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_CAM) + mvm->ps_disabled = true; + + if (!mvm->ps_disabled) + cmd.flags |= cpu_to_le16(DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status) ? + mvm->disable_power_off_d3 : mvm->disable_power_off) + cmd.flags &= + cpu_to_le16(~DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK); +#endif + if (mvm->ext_clock_valid) + cmd.flags |= cpu_to_le16(DEVICE_POWER_FLAGS_32K_CLK_VALID_MSK); + + IWL_DEBUG_POWER(mvm, + "Sending device power command with flags = 0x%X\n", + cmd.flags); + + return iwl_mvm_send_cmd_pdu(mvm, POWER_TABLE_CMD, 0, sizeof(cmd), + &cmd); +} + +void iwl_mvm_power_vif_assoc(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (memcmp(vif->bss_conf.bssid, mvmvif->uapsd_misbehaving_bssid, + ETH_ALEN)) + eth_zero_addr(mvmvif->uapsd_misbehaving_bssid); +} + +static void iwl_mvm_power_uapsd_misbehav_ap_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + u8 *ap_sta_id = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* The ap_sta_id is not expected to change during current association + * so no explicit protection is needed + */ + if (mvmvif->ap_sta_id == *ap_sta_id) + memcpy(mvmvif->uapsd_misbehaving_bssid, vif->bss_conf.bssid, + ETH_ALEN); +} + +void iwl_mvm_power_uapsd_misbehaving_ap_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_uapsd_misbehaving_ap_notif *notif = (void *)pkt->data; + u8 ap_sta_id = le32_to_cpu(notif->sta_id); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_uapsd_misbehav_ap_iterator, &ap_sta_id); +} + +struct iwl_power_vifs { + struct iwl_mvm *mvm; + struct ieee80211_vif *bss_vif; + struct ieee80211_vif *p2p_vif; + struct ieee80211_vif *ap_vif; + struct ieee80211_vif *monitor_vif; + bool p2p_active; + bool bss_active; + bool ap_active; + bool monitor_active; +}; + +static void iwl_mvm_power_disable_pm_iterator(void *_data, u8* mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->pm_enabled = false; +} + +static void iwl_mvm_power_ps_disabled_iterator(void *_data, u8* mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + bool *disable_ps = _data; + + if (mvmvif->phy_ctxt && mvmvif->phy_ctxt->id < NUM_PHY_CTX) + *disable_ps |= mvmvif->ps_disabled; +} + +static void iwl_mvm_power_get_vifs_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_power_vifs *power_iterator = _data; + bool active = mvmvif->phy_ctxt && mvmvif->phy_ctxt->id < NUM_PHY_CTX; + + switch (ieee80211_vif_type_p2p(vif)) { + case NL80211_IFTYPE_P2P_DEVICE: + break; + + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_AP: + /* only a single MAC of the same type */ + WARN_ON(power_iterator->ap_vif); + power_iterator->ap_vif = vif; + if (active) + power_iterator->ap_active = true; + break; + + case NL80211_IFTYPE_MONITOR: + /* only a single MAC of the same type */ + WARN_ON(power_iterator->monitor_vif); + power_iterator->monitor_vif = vif; + if (active) + power_iterator->monitor_active = true; + break; + + case NL80211_IFTYPE_P2P_CLIENT: + /* only a single MAC of the same type */ + WARN_ON(power_iterator->p2p_vif); + power_iterator->p2p_vif = vif; + if (active) + power_iterator->p2p_active = true; + break; + + case NL80211_IFTYPE_STATION: + power_iterator->bss_vif = vif; + if (active) + power_iterator->bss_active = true; + break; + + default: + break; + } +} + +static void iwl_mvm_power_set_pm(struct iwl_mvm *mvm, + struct iwl_power_vifs *vifs) +{ + struct iwl_mvm_vif *bss_mvmvif = NULL; + struct iwl_mvm_vif *p2p_mvmvif = NULL; + struct iwl_mvm_vif *ap_mvmvif = NULL; + bool client_same_channel = false; + bool ap_same_channel = false; + + lockdep_assert_held(&mvm->mutex); + + /* set pm_enable to false */ + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_disable_pm_iterator, + NULL); + + if (vifs->bss_vif) + bss_mvmvif = iwl_mvm_vif_from_mac80211(vifs->bss_vif); + + if (vifs->p2p_vif) + p2p_mvmvif = iwl_mvm_vif_from_mac80211(vifs->p2p_vif); + + if (vifs->ap_vif) + ap_mvmvif = iwl_mvm_vif_from_mac80211(vifs->ap_vif); + + /* don't allow PM if any TDLS stations exist */ + if (iwl_mvm_tdls_sta_count(mvm, NULL)) + return; + + /* enable PM on bss if bss stand alone */ + if (vifs->bss_active && !vifs->p2p_active && !vifs->ap_active) { + bss_mvmvif->pm_enabled = true; + return; + } + + /* enable PM on p2p if p2p stand alone */ + if (vifs->p2p_active && !vifs->bss_active && !vifs->ap_active) { + p2p_mvmvif->pm_enabled = true; + return; + } + + if (vifs->bss_active && vifs->p2p_active) + client_same_channel = (bss_mvmvif->phy_ctxt->id == + p2p_mvmvif->phy_ctxt->id); + if (vifs->bss_active && vifs->ap_active) + ap_same_channel = (bss_mvmvif->phy_ctxt->id == + ap_mvmvif->phy_ctxt->id); + + /* clients are not stand alone: enable PM if DCM */ + if (!(client_same_channel || ap_same_channel)) { + if (vifs->bss_active) + bss_mvmvif->pm_enabled = true; + if (vifs->p2p_active) + p2p_mvmvif->pm_enabled = true; + return; + } + + /* + * There is only one channel in the system and there are only + * bss and p2p clients that share it + */ + if (client_same_channel && !vifs->ap_active) { + /* share same channel*/ + bss_mvmvif->pm_enabled = true; + p2p_mvmvif->pm_enabled = true; + } +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +int iwl_mvm_power_mac_dbgfs_read(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, char *buf, + int bufsz) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_power_cmd cmd = {}; + int pos = 0; + + mutex_lock(&mvm->mutex); + memcpy(&cmd, &mvmvif->mac_pwr_cmd, sizeof(cmd)); + mutex_unlock(&mvm->mutex); + + pos += scnprintf(buf+pos, bufsz-pos, "power_scheme = %d\n", + iwlmvm_mod_params.power_scheme); + pos += scnprintf(buf+pos, bufsz-pos, "flags = 0x%x\n", + le16_to_cpu(cmd.flags)); + pos += scnprintf(buf+pos, bufsz-pos, "keep_alive = %d\n", + le16_to_cpu(cmd.keep_alive_seconds)); + + if (!(cmd.flags & cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK))) + return pos; + + pos += scnprintf(buf+pos, bufsz-pos, "skip_over_dtim = %d\n", + (cmd.flags & + cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK)) ? 1 : 0); + pos += scnprintf(buf+pos, bufsz-pos, "skip_dtim_periods = %d\n", + cmd.skip_dtim_periods); + if (!(cmd.flags & cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK))) { + pos += scnprintf(buf+pos, bufsz-pos, "rx_data_timeout = %d\n", + le32_to_cpu(cmd.rx_data_timeout)); + pos += scnprintf(buf+pos, bufsz-pos, "tx_data_timeout = %d\n", + le32_to_cpu(cmd.tx_data_timeout)); + } + if (cmd.flags & cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK)) + pos += scnprintf(buf+pos, bufsz-pos, + "lprx_rssi_threshold = %d\n", + cmd.lprx_rssi_threshold); + + if (!(cmd.flags & cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK))) + return pos; + + pos += scnprintf(buf+pos, bufsz-pos, "rx_data_timeout_uapsd = %d\n", + le32_to_cpu(cmd.rx_data_timeout_uapsd)); + pos += scnprintf(buf+pos, bufsz-pos, "tx_data_timeout_uapsd = %d\n", + le32_to_cpu(cmd.tx_data_timeout_uapsd)); + pos += scnprintf(buf+pos, bufsz-pos, "qndp_tid = %d\n", cmd.qndp_tid); + pos += scnprintf(buf+pos, bufsz-pos, "uapsd_ac_flags = 0x%x\n", + cmd.uapsd_ac_flags); + pos += scnprintf(buf+pos, bufsz-pos, "uapsd_max_sp = %d\n", + cmd.uapsd_max_sp); + pos += scnprintf(buf+pos, bufsz-pos, "heavy_tx_thld_packets = %d\n", + cmd.heavy_tx_thld_packets); + pos += scnprintf(buf+pos, bufsz-pos, "heavy_rx_thld_packets = %d\n", + cmd.heavy_rx_thld_packets); + pos += scnprintf(buf+pos, bufsz-pos, "heavy_tx_thld_percentage = %d\n", + cmd.heavy_tx_thld_percentage); + pos += scnprintf(buf+pos, bufsz-pos, "heavy_rx_thld_percentage = %d\n", + cmd.heavy_rx_thld_percentage); + pos += scnprintf(buf+pos, bufsz-pos, "uapsd_misbehaving_enable = %d\n", + (cmd.flags & + cpu_to_le16(POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK)) ? + 1 : 0); + + if (!(cmd.flags & cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK))) + return pos; + + pos += scnprintf(buf+pos, bufsz-pos, "snooze_interval = %d\n", + cmd.snooze_interval); + pos += scnprintf(buf+pos, bufsz-pos, "snooze_window = %d\n", + cmd.snooze_window); + + return pos; +} + +void +iwl_mvm_beacon_filter_debugfs_parameters(struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_dbgfs_bf *dbgfs_bf = &mvmvif->dbgfs_bf; + + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_ENERGY_DELTA) + cmd->bf_energy_delta = cpu_to_le32(dbgfs_bf->bf_energy_delta); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_ROAMING_ENERGY_DELTA) + cmd->bf_roaming_energy_delta = + cpu_to_le32(dbgfs_bf->bf_roaming_energy_delta); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_ROAMING_STATE) + cmd->bf_roaming_state = cpu_to_le32(dbgfs_bf->bf_roaming_state); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_TEMP_THRESHOLD) + cmd->bf_temp_threshold = + cpu_to_le32(dbgfs_bf->bf_temp_threshold); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_TEMP_FAST_FILTER) + cmd->bf_temp_fast_filter = + cpu_to_le32(dbgfs_bf->bf_temp_fast_filter); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_TEMP_SLOW_FILTER) + cmd->bf_temp_slow_filter = + cpu_to_le32(dbgfs_bf->bf_temp_slow_filter); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_DEBUG_FLAG) + cmd->bf_debug_flag = cpu_to_le32(dbgfs_bf->bf_debug_flag); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_ESCAPE_TIMER) + cmd->bf_escape_timer = cpu_to_le32(dbgfs_bf->bf_escape_timer); + if (dbgfs_bf->mask & MVM_DEBUGFS_BA_ESCAPE_TIMER) + cmd->ba_escape_timer = cpu_to_le32(dbgfs_bf->ba_escape_timer); + if (dbgfs_bf->mask & MVM_DEBUGFS_BA_ENABLE_BEACON_ABORT) + cmd->ba_enable_beacon_abort = + cpu_to_le32(dbgfs_bf->ba_enable_beacon_abort); +} +#endif + +static int _iwl_mvm_enable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd, + u32 cmd_flags) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + if (mvmvif != mvm->bf_allowed_vif || !vif->bss_conf.dtim_period || + vif->type != NL80211_IFTYPE_STATION || vif->p2p) + return 0; + + iwl_mvm_beacon_filter_set_cqm_params(mvm, vif, cmd); + iwl_mvm_beacon_filter_debugfs_parameters(vif, cmd); + ret = iwl_mvm_beacon_filter_send_cmd(mvm, cmd, cmd_flags); + + if (!ret) + mvmvif->bf_data.bf_enabled = true; + + return ret; +} + +int iwl_mvm_enable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags) +{ + struct iwl_beacon_filter_cmd cmd = { + IWL_BF_CMD_CONFIG_DEFAULTS, + .bf_enable_beacon_filter = cpu_to_le32(1), + }; + + return _iwl_mvm_enable_beacon_filter(mvm, vif, &cmd, flags); +} + +static int _iwl_mvm_disable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags) +{ + struct iwl_beacon_filter_cmd cmd = {}; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) + return 0; + + ret = iwl_mvm_beacon_filter_send_cmd(mvm, &cmd, flags); + + if (!ret) + mvmvif->bf_data.bf_enabled = false; + + return ret; +} + +int iwl_mvm_disable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags) +{ + return _iwl_mvm_disable_beacon_filter(mvm, vif, flags); +} + +static int iwl_mvm_power_set_ps(struct iwl_mvm *mvm) +{ + bool disable_ps; + int ret; + + /* disable PS if CAM */ + disable_ps = (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_CAM); + /* ...or if any of the vifs require PS to be off */ + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_ps_disabled_iterator, + &disable_ps); + + /* update device power state if it has changed */ + if (mvm->ps_disabled != disable_ps) { + bool old_ps_disabled = mvm->ps_disabled; + + mvm->ps_disabled = disable_ps; + ret = iwl_mvm_power_update_device(mvm); + if (ret) { + mvm->ps_disabled = old_ps_disabled; + return ret; + } + } + + return 0; +} + +static int iwl_mvm_power_set_ba(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_beacon_filter_cmd cmd = { + IWL_BF_CMD_CONFIG_DEFAULTS, + .bf_enable_beacon_filter = cpu_to_le32(1), + }; + + if (!mvmvif->bf_data.bf_enabled) + return 0; + + if (test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) + cmd.ba_escape_timer = cpu_to_le32(IWL_BA_ESCAPE_TIMER_D3); + + mvmvif->bf_data.ba_enabled = !(!mvmvif->pm_enabled || + mvm->ps_disabled || + !vif->bss_conf.ps || + iwl_mvm_vif_low_latency(mvmvif)); + + return _iwl_mvm_enable_beacon_filter(mvm, vif, &cmd, 0); +} + +int iwl_mvm_power_update_ps(struct iwl_mvm *mvm) +{ + struct iwl_power_vifs vifs = { + .mvm = mvm, + }; + int ret; + + lockdep_assert_held(&mvm->mutex); + + /* get vifs info */ + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_get_vifs_iterator, &vifs); + + ret = iwl_mvm_power_set_ps(mvm); + if (ret) + return ret; + + if (vifs.bss_vif) + return iwl_mvm_power_set_ba(mvm, vifs.bss_vif); + + return 0; +} + +int iwl_mvm_power_update_mac(struct iwl_mvm *mvm) +{ + struct iwl_power_vifs vifs = { + .mvm = mvm, + }; + int ret; + + lockdep_assert_held(&mvm->mutex); + + /* get vifs info */ + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_get_vifs_iterator, &vifs); + + iwl_mvm_power_set_pm(mvm, &vifs); + + ret = iwl_mvm_power_set_ps(mvm); + if (ret) + return ret; + + if (vifs.bss_vif) { + ret = iwl_mvm_power_send_cmd(mvm, vifs.bss_vif); + if (ret) + return ret; + } + + if (vifs.p2p_vif) { + ret = iwl_mvm_power_send_cmd(mvm, vifs.p2p_vif); + if (ret) + return ret; + } + + if (vifs.bss_vif) + return iwl_mvm_power_set_ba(mvm, vifs.bss_vif); + + return 0; +} diff --git a/sys/contrib/dev/iwlwifi/mvm/quota.c b/sys/contrib/dev/iwlwifi/mvm/quota.c new file mode 100644 index 00000000000..3d0166df200 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/quota.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include "fw-api.h" +#include "mvm.h" + +#define QUOTA_100 IWL_MVM_MAX_QUOTA +#define QUOTA_LOWLAT_MIN ((QUOTA_100 * IWL_MVM_LOWLAT_QUOTA_MIN_PERCENT) / 100) + +struct iwl_mvm_quota_iterator_data { + int n_interfaces[MAX_BINDINGS]; + int colors[MAX_BINDINGS]; + int low_latency[MAX_BINDINGS]; +#ifdef CONFIG_IWLWIFI_DEBUGFS + int dbgfs_min[MAX_BINDINGS]; +#endif + int n_low_latency_bindings; + struct ieee80211_vif *disabled_vif; +}; + +static void iwl_mvm_quota_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_quota_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 id; + + /* skip disabled interfaces here immediately */ + if (vif == data->disabled_vif) + return; + + if (!mvmvif->phy_ctxt) + return; + + /* currently, PHY ID == binding ID */ + id = mvmvif->phy_ctxt->id; + + /* need at least one binding per PHY */ + BUILD_BUG_ON(NUM_PHY_CTX > MAX_BINDINGS); + + if (WARN_ON_ONCE(id >= MAX_BINDINGS)) + return; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (vif->bss_conf.assoc) + break; + return; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + if (mvmvif->ap_ibss_active) + break; + return; + case NL80211_IFTYPE_MONITOR: + if (mvmvif->monitor_active) + break; + return; + case NL80211_IFTYPE_P2P_DEVICE: + return; + default: + WARN_ON_ONCE(1); + return; + } + + if (data->colors[id] < 0) + data->colors[id] = mvmvif->phy_ctxt->color; + else + WARN_ON_ONCE(data->colors[id] != mvmvif->phy_ctxt->color); + + data->n_interfaces[id]++; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvmvif->dbgfs_quota_min) + data->dbgfs_min[id] = max(data->dbgfs_min[id], + mvmvif->dbgfs_quota_min); +#endif + + if (iwl_mvm_vif_low_latency(mvmvif) && !data->low_latency[id]) { + data->n_low_latency_bindings++; + data->low_latency[id] = true; + } +} + +static void iwl_mvm_adjust_quota_for_noa(struct iwl_mvm *mvm, + struct iwl_time_quota_cmd *cmd) +{ +#ifdef CONFIG_NL80211_TESTMODE + struct iwl_mvm_vif *mvmvif; + int i, phy_id = -1, beacon_int = 0; + + if (!mvm->noa_duration || !mvm->noa_vif) + return; + + mvmvif = iwl_mvm_vif_from_mac80211(mvm->noa_vif); + if (!mvmvif->ap_ibss_active) + return; + + phy_id = mvmvif->phy_ctxt->id; + beacon_int = mvm->noa_vif->bss_conf.beacon_int; + + for (i = 0; i < MAX_BINDINGS; i++) { + struct iwl_time_quota_data *data = + iwl_mvm_quota_cmd_get_quota(mvm, cmd, + i); + u32 id_n_c = le32_to_cpu(data->id_and_color); + u32 id = (id_n_c & FW_CTXT_ID_MSK) >> FW_CTXT_ID_POS; + u32 quota = le32_to_cpu(data->quota); + + if (id != phy_id) + continue; + + quota *= (beacon_int - mvm->noa_duration); + quota /= beacon_int; + + IWL_DEBUG_QUOTA(mvm, "quota: adjust for NoA from %d to %d\n", + le32_to_cpu(data->quota), quota); + + data->quota = cpu_to_le32(quota); + } +#endif +} + +int iwl_mvm_update_quotas(struct iwl_mvm *mvm, + bool force_update, + struct ieee80211_vif *disabled_vif) +{ + struct iwl_time_quota_cmd cmd = {}; + int i, idx, err, num_active_macs, quota, quota_rem, n_non_lowlat; + struct iwl_mvm_quota_iterator_data data = { + .n_interfaces = {}, + .colors = { -1, -1, -1, -1 }, + .disabled_vif = disabled_vif, + }; + struct iwl_time_quota_cmd *last = &mvm->last_quota_cmd; + struct iwl_time_quota_data *qdata, *last_data; + bool send = false; + + lockdep_assert_held(&mvm->mutex); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DYNAMIC_QUOTA)) + return 0; + + /* update all upon completion */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + return 0; + + /* iterator data above must match */ + BUILD_BUG_ON(MAX_BINDINGS != 4); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_quota_iterator, &data); + + /* + * The FW's scheduling session consists of + * IWL_MVM_MAX_QUOTA fragments. Divide these fragments + * equally between all the bindings that require quota + */ + num_active_macs = 0; + for (i = 0; i < MAX_BINDINGS; i++) { + qdata = iwl_mvm_quota_cmd_get_quota(mvm, &cmd, i); + qdata->id_and_color = cpu_to_le32(FW_CTXT_INVALID); + num_active_macs += data.n_interfaces[i]; + } + + n_non_lowlat = num_active_macs; + + if (data.n_low_latency_bindings == 1) { + for (i = 0; i < MAX_BINDINGS; i++) { + if (data.low_latency[i]) { + n_non_lowlat -= data.n_interfaces[i]; + break; + } + } + } + + if (data.n_low_latency_bindings == 1 && n_non_lowlat) { + /* + * Reserve quota for the low latency binding in case that + * there are several data bindings but only a single + * low latency one. Split the rest of the quota equally + * between the other data interfaces. + */ + quota = (QUOTA_100 - QUOTA_LOWLAT_MIN) / n_non_lowlat; + quota_rem = QUOTA_100 - n_non_lowlat * quota - + QUOTA_LOWLAT_MIN; + IWL_DEBUG_QUOTA(mvm, + "quota: low-latency binding active, remaining quota per other binding: %d\n", + quota); + } else if (num_active_macs) { + /* + * There are 0 or more than 1 low latency bindings, or all the + * data interfaces belong to the single low latency binding. + * Split the quota equally between the data interfaces. + */ + quota = QUOTA_100 / num_active_macs; + quota_rem = QUOTA_100 % num_active_macs; + IWL_DEBUG_QUOTA(mvm, + "quota: splitting evenly per binding: %d\n", + quota); + } else { + /* values don't really matter - won't be used */ + quota = 0; + quota_rem = 0; + } + + for (idx = 0, i = 0; i < MAX_BINDINGS; i++) { + if (data.colors[i] < 0) + continue; + + qdata = iwl_mvm_quota_cmd_get_quota(mvm, &cmd, idx); + + qdata->id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(i, data.colors[i])); + + if (data.n_interfaces[i] <= 0) + qdata->quota = cpu_to_le32(0); +#ifdef CONFIG_IWLWIFI_DEBUGFS + else if (data.dbgfs_min[i]) + qdata->quota = + cpu_to_le32(data.dbgfs_min[i] * QUOTA_100 / 100); +#endif + else if (data.n_low_latency_bindings == 1 && n_non_lowlat && + data.low_latency[i]) + /* + * There is more than one binding, but only one of the + * bindings is in low latency. For this case, allocate + * the minimal required quota for the low latency + * binding. + */ + qdata->quota = cpu_to_le32(QUOTA_LOWLAT_MIN); + else + qdata->quota = + cpu_to_le32(quota * data.n_interfaces[i]); + + WARN_ONCE(le32_to_cpu(qdata->quota) > QUOTA_100, + "Binding=%d, quota=%u > max=%u\n", + idx, le32_to_cpu(qdata->quota), QUOTA_100); + + qdata->max_duration = cpu_to_le32(0); + + idx++; + } + + /* Give the remainder of the session to the first data binding */ + for (i = 0; i < MAX_BINDINGS; i++) { + qdata = iwl_mvm_quota_cmd_get_quota(mvm, &cmd, i); + if (le32_to_cpu(qdata->quota) != 0) { + le32_add_cpu(&qdata->quota, quota_rem); + IWL_DEBUG_QUOTA(mvm, + "quota: giving remainder of %d to binding %d\n", + quota_rem, i); + break; + } + } + + iwl_mvm_adjust_quota_for_noa(mvm, &cmd); + + /* check that we have non-zero quota for all valid bindings */ + for (i = 0; i < MAX_BINDINGS; i++) { + qdata = iwl_mvm_quota_cmd_get_quota(mvm, &cmd, i); + last_data = iwl_mvm_quota_cmd_get_quota(mvm, last, i); + if (qdata->id_and_color != last_data->id_and_color) + send = true; + if (qdata->max_duration != last_data->max_duration) + send = true; + if (abs((int)le32_to_cpu(qdata->quota) - + (int)le32_to_cpu(last_data->quota)) + > IWL_MVM_QUOTA_THRESHOLD) + send = true; + if (qdata->id_and_color == cpu_to_le32(FW_CTXT_INVALID)) + continue; + WARN_ONCE(qdata->quota == 0, + "zero quota on binding %d\n", i); + } + + if (!send && !force_update) { + /* don't send a practically unchanged command, the firmware has + * to re-initialize a lot of state and that can have an adverse + * impact on it + */ + return 0; + } + + err = iwl_mvm_send_cmd_pdu(mvm, TIME_QUOTA_CMD, 0, + iwl_mvm_quota_cmd_size(mvm), &cmd); + + if (err) + IWL_ERR(mvm, "Failed to send quota: %d\n", err); + else + mvm->last_quota_cmd = cmd; + return err; +} diff --git a/sys/contrib/dev/iwlwifi/mvm/rfi.c b/sys/contrib/dev/iwlwifi/mvm/rfi.c new file mode 100644 index 00000000000..44344216a1a --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/rfi.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2020 - 2021 Intel Corporation + */ + +#include "mvm.h" +#include "fw/api/commands.h" +#include "fw/api/phy-ctxt.h" + +/** + * DDR needs frequency in units of 16.666MHz, so provide FW with the + * frequency values in the adjusted format. + */ +static const struct iwl_rfi_lut_entry iwl_rfi_table[IWL_RFI_LUT_SIZE] = { + /* LPDDR4 */ + + /* frequency 3733MHz */ + {cpu_to_le16(223), {114, 116, 118, 120, 122,}, + {PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5,}}, + + /* frequency 4267MHz */ + {cpu_to_le16(256), {79, 83, 85, 87, 89, 91, 93,}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6,}}, + + /* DDR5ePOR */ + + /* frequency 4000MHz */ + {cpu_to_le16(240), {3, 5, 7, 9, 11, 13, 15,}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6,}}, + + /* frequency 4400MHz */ + {cpu_to_le16(264), {111, 119, 123, 125, 129, 131, 133, 135, 143,}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6,}}, + + /* LPDDR5iPOR */ + + /* frequency 5200MHz */ + {cpu_to_le16(312), {36, 38, 40, 42, 50,}, + {PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5,}}, + + /* frequency 6000MHz */ + {cpu_to_le16(360), {3, 5, 7, 9, 11, 13, 15,}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6,}}, + + /* frequency 6400MHz */ + {cpu_to_le16(384), {79, 83, 85, 87, 89, 91, 93,}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6,}}, +}; + +int iwl_rfi_send_config_cmd(struct iwl_mvm *mvm, struct iwl_rfi_lut_entry *rfi_table) +{ + int ret; + struct iwl_rfi_config_cmd cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(SYSTEM_GROUP, RFI_CONFIG_CMD), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_RFIM_SUPPORT)) + return -EOPNOTSUPP; + + lockdep_assert_held(&mvm->mutex); + + /* in case no table is passed, use the default one */ + if (!rfi_table) { + memcpy(cmd.table, iwl_rfi_table, sizeof(cmd.table)); + } else { + memcpy(cmd.table, rfi_table, sizeof(cmd.table)); + /* notify FW the table is not the default one */ + cmd.oem = 1; + } + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + + if (ret) + IWL_ERR(mvm, "Failed to send RFI config cmd %d\n", ret); + + return ret; +} + +struct iwl_rfi_freq_table_resp_cmd *iwl_rfi_get_freq_table(struct iwl_mvm *mvm) +{ + struct iwl_rfi_freq_table_resp_cmd *resp; + int resp_size = sizeof(*resp); + int ret; + struct iwl_host_cmd cmd = { + .id = WIDE_ID(SYSTEM_GROUP, RFI_GET_FREQ_TABLE_CMD), + .flags = CMD_WANT_SKB, + }; + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_RFIM_SUPPORT)) + return ERR_PTR(-EOPNOTSUPP); + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_send_cmd(mvm, &cmd); + mutex_unlock(&mvm->mutex); + if (ret) + return ERR_PTR(ret); + + if (WARN_ON_ONCE(iwl_rx_packet_payload_len(cmd.resp_pkt) != resp_size)) + return ERR_PTR(-EIO); + + resp = kzalloc(resp_size, GFP_KERNEL); + if (!resp) + return ERR_PTR(-ENOMEM); + + memcpy(resp, cmd.resp_pkt->data, resp_size); + + iwl_free_resp(&cmd); + return resp; +} diff --git a/sys/contrib/dev/iwlwifi/mvm/rs-fw.c b/sys/contrib/dev/iwlwifi/mvm/rs-fw.c new file mode 100644 index 00000000000..958702403a4 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/rs-fw.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#include "rs.h" +#include "fw-api.h" +#include "sta.h" +#include "iwl-op-mode.h" +#include "mvm.h" + +static u8 rs_fw_bw_from_sta_bw(struct ieee80211_sta *sta) +{ + switch (sta->bandwidth) { + case IEEE80211_STA_RX_BW_160: + return IWL_TLC_MNG_CH_WIDTH_160MHZ; + case IEEE80211_STA_RX_BW_80: + return IWL_TLC_MNG_CH_WIDTH_80MHZ; + case IEEE80211_STA_RX_BW_40: + return IWL_TLC_MNG_CH_WIDTH_40MHZ; + case IEEE80211_STA_RX_BW_20: + default: + return IWL_TLC_MNG_CH_WIDTH_20MHZ; + } +} + +static u8 rs_fw_set_active_chains(u8 chains) +{ + u8 fw_chains = 0; + + if (chains & ANT_A) + fw_chains |= IWL_TLC_MNG_CHAIN_A_MSK; + if (chains & ANT_B) + fw_chains |= IWL_TLC_MNG_CHAIN_B_MSK; + + return fw_chains; +} + +static u8 rs_fw_sgi_cw_support(struct ieee80211_sta *sta) +{ + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + struct ieee80211_sta_vht_cap *vht_cap = &sta->vht_cap; + struct ieee80211_sta_he_cap *he_cap = &sta->he_cap; + u8 supp = 0; + + if (he_cap->has_he) + return 0; + + if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20) + supp |= BIT(IWL_TLC_MNG_CH_WIDTH_20MHZ); + if (ht_cap->cap & IEEE80211_HT_CAP_SGI_40) + supp |= BIT(IWL_TLC_MNG_CH_WIDTH_40MHZ); + if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80) + supp |= BIT(IWL_TLC_MNG_CH_WIDTH_80MHZ); + if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160) + supp |= BIT(IWL_TLC_MNG_CH_WIDTH_160MHZ); + + return supp; +} + +static u16 rs_fw_get_config_flags(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct ieee80211_supported_band *sband) +{ + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + struct ieee80211_sta_vht_cap *vht_cap = &sta->vht_cap; + struct ieee80211_sta_he_cap *he_cap = &sta->he_cap; + bool vht_ena = vht_cap->vht_supported; + u16 flags = 0; + + /* get STBC flags */ + if (mvm->cfg->ht_params->stbc && + (num_of_ant(iwl_mvm_get_valid_tx_ant(mvm)) > 1)) { + if (he_cap->has_he && he_cap->he_cap_elem.phy_cap_info[2] & + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ) + flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK; + else if (vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK) + flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK; + else if (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC) + flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK; + } + + if (mvm->cfg->ht_params->ldpc && + ((ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) || + (vht_ena && (vht_cap->cap & IEEE80211_VHT_CAP_RXLDPC)))) + flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK; + + /* consider LDPC support in case of HE */ + if (he_cap->has_he && (he_cap->he_cap_elem.phy_cap_info[1] & + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD)) + flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK; + + if (sband->iftype_data && sband->iftype_data->he_cap.has_he && + !(sband->iftype_data->he_cap.he_cap_elem.phy_cap_info[1] & + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD)) + flags &= ~IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK; + + if (he_cap->has_he && + (he_cap->he_cap_elem.phy_cap_info[3] & + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_MASK)) + flags |= IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_1_MSK; + + return flags; +} + +static +int rs_fw_vht_highest_rx_mcs_index(const struct ieee80211_sta_vht_cap *vht_cap, + int nss) +{ + u16 rx_mcs = le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map) & + (0x3 << (2 * (nss - 1))); + rx_mcs >>= (2 * (nss - 1)); + + switch (rx_mcs) { + case IEEE80211_VHT_MCS_SUPPORT_0_7: + return IWL_TLC_MNG_HT_RATE_MCS7; + case IEEE80211_VHT_MCS_SUPPORT_0_8: + return IWL_TLC_MNG_HT_RATE_MCS8; + case IEEE80211_VHT_MCS_SUPPORT_0_9: + return IWL_TLC_MNG_HT_RATE_MCS9; + default: + WARN_ON_ONCE(1); + break; + } + + return 0; +} + +static void +rs_fw_vht_set_enabled_rates(const struct ieee80211_sta *sta, + const struct ieee80211_sta_vht_cap *vht_cap, + struct iwl_tlc_config_cmd *cmd) +{ + u16 supp; + int i, highest_mcs; + u8 max_nss = sta->rx_nss; + struct ieee80211_vht_cap ieee_vht_cap = { + .vht_cap_info = cpu_to_le32(vht_cap->cap), + .supp_mcs = vht_cap->vht_mcs, + }; + + /* the station support only a single receive chain */ + if (sta->smps_mode == IEEE80211_SMPS_STATIC) + max_nss = 1; + + for (i = 0; i < max_nss && i < IWL_TLC_NSS_MAX; i++) { + int nss = i + 1; + + highest_mcs = rs_fw_vht_highest_rx_mcs_index(vht_cap, nss); + if (!highest_mcs) + continue; + + supp = BIT(highest_mcs + 1) - 1; + if (sta->bandwidth == IEEE80211_STA_RX_BW_20) + supp &= ~BIT(IWL_TLC_MNG_HT_RATE_MCS9); + + cmd->ht_rates[i][IWL_TLC_HT_BW_NONE_160] = cpu_to_le16(supp); + /* + * Check if VHT extended NSS indicates that the bandwidth/NSS + * configuration is supported - only for MCS 0 since we already + * decoded the MCS bits anyway ourselves. + */ + if (sta->bandwidth == IEEE80211_STA_RX_BW_160 && + ieee80211_get_vht_max_nss(&ieee_vht_cap, + IEEE80211_VHT_CHANWIDTH_160MHZ, + 0, true, nss) >= nss) + cmd->ht_rates[i][IWL_TLC_HT_BW_160] = + cmd->ht_rates[i][IWL_TLC_HT_BW_NONE_160]; + } +} + +static u16 rs_fw_he_ieee80211_mcs_to_rs_mcs(u16 mcs) +{ + switch (mcs) { + case IEEE80211_HE_MCS_SUPPORT_0_7: + return BIT(IWL_TLC_MNG_HT_RATE_MCS7 + 1) - 1; + case IEEE80211_HE_MCS_SUPPORT_0_9: + return BIT(IWL_TLC_MNG_HT_RATE_MCS9 + 1) - 1; + case IEEE80211_HE_MCS_SUPPORT_0_11: + return BIT(IWL_TLC_MNG_HT_RATE_MCS11 + 1) - 1; + case IEEE80211_HE_MCS_NOT_SUPPORTED: + return 0; + } + + WARN(1, "invalid HE MCS %d\n", mcs); + return 0; +} + +static void +rs_fw_he_set_enabled_rates(const struct ieee80211_sta *sta, + struct ieee80211_supported_band *sband, + struct iwl_tlc_config_cmd *cmd) +{ + const struct ieee80211_sta_he_cap *he_cap = &sta->he_cap; + u16 mcs_160 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160); + u16 mcs_80 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80); + u16 tx_mcs_80 = + le16_to_cpu(sband->iftype_data->he_cap.he_mcs_nss_supp.tx_mcs_80); + u16 tx_mcs_160 = + le16_to_cpu(sband->iftype_data->he_cap.he_mcs_nss_supp.tx_mcs_160); + int i; + u8 nss = sta->rx_nss; + + /* the station support only a single receive chain */ + if (sta->smps_mode == IEEE80211_SMPS_STATIC) + nss = 1; + + for (i = 0; i < nss && i < IWL_TLC_NSS_MAX; i++) { + u16 _mcs_160 = (mcs_160 >> (2 * i)) & 0x3; + u16 _mcs_80 = (mcs_80 >> (2 * i)) & 0x3; + u16 _tx_mcs_160 = (tx_mcs_160 >> (2 * i)) & 0x3; + u16 _tx_mcs_80 = (tx_mcs_80 >> (2 * i)) & 0x3; + + /* If one side doesn't support - mark both as not supporting */ + if (_mcs_80 == IEEE80211_HE_MCS_NOT_SUPPORTED || + _tx_mcs_80 == IEEE80211_HE_MCS_NOT_SUPPORTED) { + _mcs_80 = IEEE80211_HE_MCS_NOT_SUPPORTED; + _tx_mcs_80 = IEEE80211_HE_MCS_NOT_SUPPORTED; + } + if (_mcs_80 > _tx_mcs_80) + _mcs_80 = _tx_mcs_80; + cmd->ht_rates[i][IWL_TLC_HT_BW_NONE_160] = + cpu_to_le16(rs_fw_he_ieee80211_mcs_to_rs_mcs(_mcs_80)); + + /* If one side doesn't support - mark both as not supporting */ + if (_mcs_160 == IEEE80211_HE_MCS_NOT_SUPPORTED || + _tx_mcs_160 == IEEE80211_HE_MCS_NOT_SUPPORTED) { + _mcs_160 = IEEE80211_HE_MCS_NOT_SUPPORTED; + _tx_mcs_160 = IEEE80211_HE_MCS_NOT_SUPPORTED; + } + if (_mcs_160 > _tx_mcs_160) + _mcs_160 = _tx_mcs_160; + cmd->ht_rates[i][IWL_TLC_HT_BW_160] = + cpu_to_le16(rs_fw_he_ieee80211_mcs_to_rs_mcs(_mcs_160)); + } +} + +static void rs_fw_set_supp_rates(struct ieee80211_sta *sta, + struct ieee80211_supported_band *sband, + struct iwl_tlc_config_cmd *cmd) +{ + int i; + u16 supp = 0; + unsigned long tmp; /* must be unsigned long for for_each_set_bit */ + const struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + const struct ieee80211_sta_vht_cap *vht_cap = &sta->vht_cap; + const struct ieee80211_sta_he_cap *he_cap = &sta->he_cap; + + /* non HT rates */ + tmp = sta->supp_rates[sband->band]; + for_each_set_bit(i, &tmp, BITS_PER_LONG) + supp |= BIT(sband->bitrates[i].hw_value); + + cmd->non_ht_rates = cpu_to_le16(supp); + cmd->mode = IWL_TLC_MNG_MODE_NON_HT; + + /* HT/VHT rates */ + if (he_cap->has_he) { + cmd->mode = IWL_TLC_MNG_MODE_HE; + rs_fw_he_set_enabled_rates(sta, sband, cmd); + } else if (vht_cap->vht_supported) { + cmd->mode = IWL_TLC_MNG_MODE_VHT; + rs_fw_vht_set_enabled_rates(sta, vht_cap, cmd); + } else if (ht_cap->ht_supported) { + cmd->mode = IWL_TLC_MNG_MODE_HT; + cmd->ht_rates[IWL_TLC_NSS_1][IWL_TLC_HT_BW_NONE_160] = + cpu_to_le16(ht_cap->mcs.rx_mask[0]); + + /* the station support only a single receive chain */ + if (sta->smps_mode == IEEE80211_SMPS_STATIC) + cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_HT_BW_NONE_160] = + 0; + else + cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_HT_BW_NONE_160] = + cpu_to_le16(ht_cap->mcs.rx_mask[1]); + } +} + +void iwl_mvm_tlc_update_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_tlc_update_notif *notif; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + struct iwl_lq_sta_rs_fw *lq_sta; + u32 flags; + + rcu_read_lock(); + + notif = (void *)pkt->data; + sta = rcu_dereference(mvm->fw_id_to_mac_id[notif->sta_id]); + if (IS_ERR_OR_NULL(sta)) { + IWL_ERR(mvm, "Invalid sta id (%d) in FW TLC notification\n", + notif->sta_id); + goto out; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (!mvmsta) { + IWL_ERR(mvm, "Invalid sta id (%d) in FW TLC notification\n", + notif->sta_id); + goto out; + } + + flags = le32_to_cpu(notif->flags); + + lq_sta = &mvmsta->lq_sta.rs_fw; + + if (flags & IWL_TLC_NOTIF_FLAG_RATE) { + char pretty_rate[100]; + + if (iwl_fw_lookup_notif_ver(mvm->fw, DATA_PATH_GROUP, + TLC_MNG_UPDATE_NOTIF, 0) < 3) { + rs_pretty_print_rate_v1(pretty_rate, sizeof(pretty_rate), + le32_to_cpu(notif->rate)); + IWL_DEBUG_RATE(mvm, + "Got rate in old format. Rate: %s. Converting.\n", + pretty_rate); + lq_sta->last_rate_n_flags = + iwl_new_rate_from_v1(le32_to_cpu(notif->rate)); + } else { + lq_sta->last_rate_n_flags = le32_to_cpu(notif->rate); + } + rs_pretty_print_rate(pretty_rate, sizeof(pretty_rate), + lq_sta->last_rate_n_flags); + IWL_DEBUG_RATE(mvm, "new rate: %s\n", pretty_rate); + } + + if (flags & IWL_TLC_NOTIF_FLAG_AMSDU && !mvmsta->orig_amsdu_len) { + u16 size = le32_to_cpu(notif->amsdu_size); + int i; + + if (sta->max_amsdu_len < size) { + /* + * In debug sta->max_amsdu_len < size + * so also check with orig_amsdu_len which holds the + * original data before debugfs changed the value + */ + WARN_ON(mvmsta->orig_amsdu_len < size); + goto out; + } + + mvmsta->amsdu_enabled = le32_to_cpu(notif->amsdu_enabled); + mvmsta->max_amsdu_len = size; + sta->max_rc_amsdu_len = mvmsta->max_amsdu_len; + + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + if (mvmsta->amsdu_enabled & BIT(i)) + sta->max_tid_amsdu_len[i] = + iwl_mvm_max_amsdu_size(mvm, sta, i); + else + /* + * Not so elegant, but this will effectively + * prevent AMSDU on this TID + */ + sta->max_tid_amsdu_len[i] = 1; + } + + IWL_DEBUG_RATE(mvm, + "AMSDU update. AMSDU size: %d, AMSDU selected size: %d, AMSDU TID bitmap 0x%X\n", + le32_to_cpu(notif->amsdu_size), size, + mvmsta->amsdu_enabled); + } +out: + rcu_read_unlock(); +} + +u16 rs_fw_get_max_amsdu_len(struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + const struct ieee80211_sta_vht_cap *vht_cap = &sta->vht_cap; + const struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + + if (mvmsta->vif->bss_conf.chandef.chan->band == NL80211_BAND_6GHZ) { + switch (le16_get_bits(sta->he_6ghz_capa.capa, + IEEE80211_HE_6GHZ_CAP_MAX_MPDU_LEN)) { + case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454: + return IEEE80211_MAX_MPDU_LEN_VHT_11454; + case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991: + return IEEE80211_MAX_MPDU_LEN_VHT_7991; + default: + return IEEE80211_MAX_MPDU_LEN_VHT_3895; + } + } else + if (vht_cap->vht_supported) { + switch (vht_cap->cap & IEEE80211_VHT_CAP_MAX_MPDU_MASK) { + case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454: + return IEEE80211_MAX_MPDU_LEN_VHT_11454; + case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991: + return IEEE80211_MAX_MPDU_LEN_VHT_7991; + default: + return IEEE80211_MAX_MPDU_LEN_VHT_3895; + } + } else if (ht_cap->ht_supported) { + if (ht_cap->cap & IEEE80211_HT_CAP_MAX_AMSDU) + /* + * agg is offloaded so we need to assume that agg + * are enabled and max mpdu in ampdu is 4095 + * (spec 802.11-2016 9.3.2.1) + */ + return IEEE80211_MAX_MPDU_LEN_HT_BA; + else + return IEEE80211_MAX_MPDU_LEN_HT_3839; + } + + /* in legacy mode no amsdu is enabled so return zero */ + return 0; +} + +void rs_fw_rate_init(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + enum nl80211_band band, bool update) +{ + struct ieee80211_hw *hw = mvm->hw; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_lq_sta_rs_fw *lq_sta = &mvmsta->lq_sta.rs_fw; + u32 cmd_id = iwl_cmd_id(TLC_MNG_CONFIG_CMD, DATA_PATH_GROUP, 0); + struct ieee80211_supported_band *sband = hw->wiphy->bands[band]; + u16 max_amsdu_len = rs_fw_get_max_amsdu_len(sta); + struct iwl_tlc_config_cmd cfg_cmd = { + .sta_id = mvmsta->sta_id, + .max_ch_width = update ? + rs_fw_bw_from_sta_bw(sta) : RATE_MCS_CHAN_WIDTH_20, + .flags = cpu_to_le16(rs_fw_get_config_flags(mvm, sta, sband)), + .chains = rs_fw_set_active_chains(iwl_mvm_get_valid_tx_ant(mvm)), + .sgi_ch_width_supp = rs_fw_sgi_cw_support(sta), + .max_mpdu_len = cpu_to_le16(max_amsdu_len), + .amsdu = iwl_mvm_is_csum_supported(mvm), + }; + int ret; + u16 cmd_size = sizeof(cfg_cmd); + + /* In old versions of the API the struct is 4 bytes smaller */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, DATA_PATH_GROUP, + TLC_MNG_CONFIG_CMD, 0) < 3) + cmd_size -= 4; + + memset(lq_sta, 0, offsetof(typeof(*lq_sta), pers)); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + iwl_mvm_reset_frame_stats(mvm); +#endif + rs_fw_set_supp_rates(sta, sband, &cfg_cmd); + + /* + * since TLC offload works with one mode we can assume + * that only vht/ht is used and also set it as station max amsdu + */ + sta->max_amsdu_len = max_amsdu_len; + + ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, CMD_ASYNC, cmd_size, + &cfg_cmd); + if (ret) + IWL_ERR(mvm, "Failed to send rate scale config (%d)\n", ret); +} + +int rs_fw_tx_protection(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool enable) +{ + /* TODO: need to introduce a new FW cmd since LQ cmd is not relevant */ + IWL_DEBUG_RATE(mvm, "tx protection - not implemented yet.\n"); + return 0; +} + +void iwl_mvm_rs_add_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta) +{ + struct iwl_lq_sta_rs_fw *lq_sta = &mvmsta->lq_sta.rs_fw; + + IWL_DEBUG_RATE(mvm, "create station rate scale window\n"); + + lq_sta->pers.drv = mvm; + lq_sta->pers.sta_id = mvmsta->sta_id; + lq_sta->pers.chains = 0; + memset(lq_sta->pers.chain_signal, 0, sizeof(lq_sta->pers.chain_signal)); + lq_sta->pers.last_rssi = S8_MIN; + lq_sta->last_rate_n_flags = 0; + +#ifdef CONFIG_MAC80211_DEBUGFS + lq_sta->pers.dbg_fixed_rate = 0; +#endif +} diff --git a/sys/contrib/dev/iwlwifi/mvm/rs.c b/sys/contrib/dev/iwlwifi/mvm/rs.c new file mode 100644 index 00000000000..cf2f7e75552 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/rs.c @@ -0,0 +1,92 @@ +/*- + * Copyright (c) 2020-2021 The FreeBSD Foundation + * + * This software was developed by Björn Zeeb under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * XXX-BZ: + * This file is left as a wrapper to make mvm compile and we will only + * deal with it on a need basis. Most newer chipsets do this in firmware. + */ + +#include + +#include "mvm.h" + + +int +iwl_mvm_rate_control_register(void) +{ + + return (0); +} + +int +iwl_mvm_tx_protection(struct iwl_mvm *mvm, struct iwl_mvm_sta *sta, bool enable) +{ + + return (0); +} + +void +iwl_mvm_rate_control_unregister(void) +{ +} + +void +iwl_mvm_rs_rate_init(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + enum nl80211_band band, bool t) +{ +} + +void +iwl_mvm_rs_tx_status(struct iwl_mvm *mvm, struct ieee80211_sta *sta, int tid, + struct ieee80211_tx_info *ba_info, bool t) +{ +} + +void +rs_update_last_rssi(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + struct ieee80211_rx_status *rx_status) +{ + + /* + * Assumption based on mvm/sta.h is that this should update + * mvmsta->lq_sta.rs_drv but so far we only saw a iwl_lq_cmd (lq) + * access in that struct so nowhere to put rssi information. + * So the only thing would be if this is required internally + * to functions in this file. + */ +} + +int +rs_pretty_print_rate_v1(char *buf, int bufsz, const u32 rate) +{ + + return (0); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/rs.h b/sys/contrib/dev/iwlwifi/mvm/rs.h new file mode 100644 index 00000000000..9c3d16b0120 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/rs.h @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 2020-2021 The FreeBSD Foundation + * + * This software was developed by Björn Zeeb under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * XXX-BZ: + * This file is left as a wrapper to make mvm compile and we will only + * deal with it on a need basis. Most newer chipsets do this in firmware. + */ + +#ifndef _IWLWIFI_MVM_RS_H +#define _IWLWIFI_MVM_RS_H + +#include + +#include "iwl-trans.h" +#include "fw-api.h" + +#define RS_NAME "XXX_unknown" + +#define LINK_QUAL_AGG_FRAME_LIMIT_DEF (64-1) +#define LINK_QUAL_AGG_FRAME_LIMIT_GEN2_DEF (256-1) + +struct iwl_mvm; + +struct iwl_lq_sta_rs_fw { + int last_rate_n_flags; + struct { + struct iwl_mvm *drv; + u8 sta_id; + u8 chains; + u8 chain_signal[IEEE80211_MAX_CHAINS]; + u8 last_rssi; + } pers; +}; + +struct iwl_lq_sta { + struct iwl_lq_cmd lq; + struct { + spinlock_t lock; + } pers; +}; + +#define RS_DRV_DATA_PACK(_lq_c, f) (0) /* XXX TODO | ? */ + +struct iwl_mvm_sta; + +void iwl_mvm_rs_add_sta(struct iwl_mvm *, struct iwl_mvm_sta *); +void iwl_mvm_tlc_update_notif(struct iwl_mvm *, struct iwl_rx_cmd_buffer *); +u16 rs_fw_get_max_amsdu_len(struct ieee80211_sta *); +void rs_fw_rate_init(struct iwl_mvm *, struct ieee80211_sta *, + enum nl80211_band, bool); +int rs_fw_tx_protection(struct iwl_mvm *, struct iwl_mvm_sta *, bool); +int iwl_mvm_tx_protection(struct iwl_mvm *, struct iwl_mvm_sta *, bool); + +int iwl_mvm_rate_control_register(void); +void iwl_mvm_rate_control_unregister(void); +void iwl_mvm_rs_rate_init(struct iwl_mvm *, struct ieee80211_sta *, + enum nl80211_band, bool); +void iwl_mvm_rs_tx_status(struct iwl_mvm *, struct ieee80211_sta *, + int, struct ieee80211_tx_info *, bool); + +#endif /* _IWLWIFI_MVM_RS_H */ diff --git a/sys/contrib/dev/iwlwifi/mvm/rx.c b/sys/contrib/dev/iwlwifi/mvm/rx.c new file mode 100644 index 00000000000..578b39c9b44 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/rx.c @@ -0,0 +1,916 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include +#include +#include "iwl-trans.h" +#include "mvm.h" +#include "fw-api.h" + +/* + * iwl_mvm_rx_rx_phy_cmd - REPLY_RX_PHY_CMD handler + * + * Copies the phy information in mvm->last_phy_info, it will be used when the + * actual data will come from the fw in the next packet. + */ +void iwl_mvm_rx_rx_phy_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + + if (unlikely(pkt_len < sizeof(mvm->last_phy_info))) + return; + + memcpy(&mvm->last_phy_info, pkt->data, sizeof(mvm->last_phy_info)); + mvm->ampdu_ref++; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvm->last_phy_info.phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_AGG)) { + spin_lock(&mvm->drv_stats_lock); + mvm->drv_rx_stats.ampdu_count++; + spin_unlock(&mvm->drv_stats_lock); + } +#endif +} + +/* + * iwl_mvm_pass_packet_to_mac80211 - builds the packet for mac80211 + * + * Adds the rxb to a new skb and give it to mac80211 + */ +static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct napi_struct *napi, + struct sk_buff *skb, + struct ieee80211_hdr *hdr, u16 len, + u8 crypt_len, + struct iwl_rx_cmd_buffer *rxb) +{ + unsigned int hdrlen = ieee80211_hdrlen(hdr->frame_control); + unsigned int fraglen; + + /* + * The 'hdrlen' (plus the 8 bytes for the SNAP and the crypt_len, + * but those are all multiples of 4 long) all goes away, but we + * want the *end* of it, which is going to be the start of the IP + * header, to be aligned when it gets pulled in. + * The beginning of the skb->data is aligned on at least a 4-byte + * boundary after allocation. Everything here is aligned at least + * on a 2-byte boundary so we can just take hdrlen & 3 and pad by + * the result. + */ + skb_reserve(skb, hdrlen & 3); + + /* If frame is small enough to fit in skb->head, pull it completely. + * If not, only pull ieee80211_hdr (including crypto if present, and + * an additional 8 bytes for SNAP/ethertype, see below) so that + * splice() or TCP coalesce are more efficient. + * + * Since, in addition, ieee80211_data_to_8023() always pull in at + * least 8 bytes (possibly more for mesh) we can do the same here + * to save the cost of doing it later. That still doesn't pull in + * the actual IP header since the typical case has a SNAP header. + * If the latter changes (there are efforts in the standards group + * to do so) we should revisit this and ieee80211_data_to_8023(). + */ + hdrlen = (len <= skb_tailroom(skb)) ? len : hdrlen + crypt_len + 8; + + skb_put_data(skb, hdr, hdrlen); + fraglen = len - hdrlen; + + if (fraglen) { + int offset = (u8 *)hdr + hdrlen - + (u8 *)rxb_addr(rxb) + rxb_offset(rxb); + + skb_add_rx_frag(skb, 0, rxb_steal_page(rxb), offset, + fraglen, rxb->truesize); + } + + ieee80211_rx_napi(mvm->hw, sta, skb, napi); +} + +/* + * iwl_mvm_get_signal_strength - use new rx PHY INFO API + * values are reported by the fw as positive values - need to negate + * to obtain their dBM. Account for missing antennas by replacing 0 + * values by -256dBm: practically 0 power and a non-feasible 8 bit value. + */ +static void iwl_mvm_get_signal_strength(struct iwl_mvm *mvm, + struct iwl_rx_phy_info *phy_info, + struct ieee80211_rx_status *rx_status) +{ + int energy_a, energy_b, max_energy; + u32 val; + + val = + le32_to_cpu(phy_info->non_cfg_phy[IWL_RX_INFO_ENERGY_ANT_ABC_IDX]); + energy_a = (val & IWL_RX_INFO_ENERGY_ANT_A_MSK) >> + IWL_RX_INFO_ENERGY_ANT_A_POS; + energy_a = energy_a ? -energy_a : S8_MIN; + energy_b = (val & IWL_RX_INFO_ENERGY_ANT_B_MSK) >> + IWL_RX_INFO_ENERGY_ANT_B_POS; + energy_b = energy_b ? -energy_b : S8_MIN; + max_energy = max(energy_a, energy_b); + + IWL_DEBUG_STATS(mvm, "energy In A %d B %d , and max %d\n", + energy_a, energy_b, max_energy); + + rx_status->signal = max_energy; + rx_status->chains = (le16_to_cpu(phy_info->phy_flags) & + RX_RES_PHY_FLAGS_ANTENNA) + >> RX_RES_PHY_FLAGS_ANTENNA_POS; + rx_status->chain_signal[0] = energy_a; + rx_status->chain_signal[1] = energy_b; +} + +/* + * iwl_mvm_set_mac80211_rx_flag - translate fw status to mac80211 format + * @mvm: the mvm object + * @hdr: 80211 header + * @stats: status in mac80211's format + * @rx_pkt_status: status coming from fw + * + * returns non 0 value if the packet should be dropped + */ +static u32 iwl_mvm_set_mac80211_rx_flag(struct iwl_mvm *mvm, + struct ieee80211_hdr *hdr, + struct ieee80211_rx_status *stats, + u32 rx_pkt_status, + u8 *crypt_len) +{ + if (!ieee80211_has_protected(hdr->frame_control) || + (rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == + RX_MPDU_RES_STATUS_SEC_NO_ENC) + return 0; + + /* packet was encrypted with unknown alg */ + if ((rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == + RX_MPDU_RES_STATUS_SEC_ENC_ERR) + return 0; + + switch (rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) { + case RX_MPDU_RES_STATUS_SEC_CCM_ENC: + /* alg is CCM: check MIC only */ + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_MIC_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + *crypt_len = IEEE80211_CCMP_HDR_LEN; + return 0; + + case RX_MPDU_RES_STATUS_SEC_TKIP_ENC: + /* Don't drop the frame and decrypt it in SW */ + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_DEPRECATE_TTAK) && + !(rx_pkt_status & RX_MPDU_RES_STATUS_TTAK_OK)) + return 0; + *crypt_len = IEEE80211_TKIP_IV_LEN; + fallthrough; + + case RX_MPDU_RES_STATUS_SEC_WEP_ENC: + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_ICV_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + if ((rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == + RX_MPDU_RES_STATUS_SEC_WEP_ENC) + *crypt_len = IEEE80211_WEP_IV_LEN; + return 0; + + case RX_MPDU_RES_STATUS_SEC_EXT_ENC: + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_MIC_OK)) + return -1; + stats->flag |= RX_FLAG_DECRYPTED; + return 0; + + default: + /* Expected in monitor (not having the keys) */ + if (!mvm->monitor_on && net_ratelimit()) + IWL_ERR(mvm, "%s: Unhandled alg: 0x%x\n", + __func__, rx_pkt_status); + } + + return 0; +} + +static void iwl_mvm_rx_handle_tcm(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct ieee80211_hdr *hdr, u32 len, + struct iwl_rx_phy_info *phy_info, + u32 rate_n_flags) +{ + struct iwl_mvm_sta *mvmsta; + struct iwl_mvm_tcm_mac *mdata; + int mac; + int ac = IEEE80211_AC_BE; /* treat non-QoS as BE */ + struct iwl_mvm_vif *mvmvif; + /* expected throughput in 100Kbps, single stream, 20 MHz */ + static const u8 thresh_tpt[] = { + 9, 18, 30, 42, 60, 78, 90, 96, 120, 135, + }; + u16 thr; + + if (ieee80211_is_data_qos(hdr->frame_control)) + ac = tid_to_mac80211_ac[ieee80211_get_tid(hdr)]; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + mac = mvmsta->mac_id_n_color & FW_CTXT_ID_MSK; + + if (time_after(jiffies, mvm->tcm.ts + MVM_TCM_PERIOD)) + schedule_delayed_work(&mvm->tcm.work, 0); + mdata = &mvm->tcm.data[mac]; + mdata->rx.pkts[ac]++; + + /* count the airtime only once for each ampdu */ + if (mdata->rx.last_ampdu_ref != mvm->ampdu_ref) { + mdata->rx.last_ampdu_ref = mvm->ampdu_ref; + mdata->rx.airtime += le16_to_cpu(phy_info->frame_time); + } + + if (!(rate_n_flags & (RATE_MCS_HT_MSK_V1 | RATE_MCS_VHT_MSK_V1))) + return; + + mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + + if (mdata->opened_rx_ba_sessions || + mdata->uapsd_nonagg_detect.detected || + (!mvmvif->queue_params[IEEE80211_AC_VO].uapsd && + !mvmvif->queue_params[IEEE80211_AC_VI].uapsd && + !mvmvif->queue_params[IEEE80211_AC_BE].uapsd && + !mvmvif->queue_params[IEEE80211_AC_BK].uapsd) || + mvmsta->sta_id != mvmvif->ap_sta_id) + return; + + if (rate_n_flags & RATE_MCS_HT_MSK_V1) { + thr = thresh_tpt[rate_n_flags & RATE_HT_MCS_RATE_CODE_MSK_V1]; + thr *= 1 + ((rate_n_flags & RATE_HT_MCS_NSS_MSK_V1) >> + RATE_HT_MCS_NSS_POS_V1); + } else { + if (WARN_ON((rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK) >= + ARRAY_SIZE(thresh_tpt))) + return; + thr = thresh_tpt[rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK]; + thr *= 1 + ((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS); + } + + thr <<= ((rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK_V1) >> + RATE_MCS_CHAN_WIDTH_POS); + + mdata->uapsd_nonagg_detect.rx_bytes += len; + ewma_rate_add(&mdata->uapsd_nonagg_detect.rate, thr); +} + +static void iwl_mvm_rx_csum(struct ieee80211_sta *sta, + struct sk_buff *skb, + u32 status) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + + if (mvmvif->features & NETIF_F_RXCSUM && + status & RX_MPDU_RES_STATUS_CSUM_DONE && + status & RX_MPDU_RES_STATUS_CSUM_OK) + skb->ip_summed = CHECKSUM_UNNECESSARY; +} + +/* + * iwl_mvm_rx_rx_mpdu - REPLY_RX_MPDU_CMD handler + * + * Handles the actual data of the Rx packet from the fw + */ +void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb) +{ + struct ieee80211_hdr *hdr; + struct ieee80211_rx_status *rx_status; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_phy_info *phy_info; + struct iwl_rx_mpdu_res_start *rx_res; + struct ieee80211_sta *sta = NULL; + struct sk_buff *skb; + u32 len, pkt_len = iwl_rx_packet_payload_len(pkt); + u32 rate_n_flags; + u32 rx_pkt_status; + u8 crypt_len = 0; + + if (unlikely(pkt_len < sizeof(*rx_res))) { + IWL_DEBUG_DROP(mvm, "Bad REPLY_RX_MPDU_CMD size\n"); + return; + } + + phy_info = &mvm->last_phy_info; + rx_res = (struct iwl_rx_mpdu_res_start *)pkt->data; + hdr = (struct ieee80211_hdr *)(pkt->data + sizeof(*rx_res)); + len = le16_to_cpu(rx_res->byte_count); + + if (unlikely(len + sizeof(*rx_res) + sizeof(__le32) > pkt_len)) { + IWL_DEBUG_DROP(mvm, "FW lied about packet len\n"); + return; + } + + rx_pkt_status = get_unaligned_le32((__le32 *) + (pkt->data + sizeof(*rx_res) + len)); + + /* Dont use dev_alloc_skb(), we'll have enough headroom once + * ieee80211_hdr pulled. + */ + skb = alloc_skb(128, GFP_ATOMIC); + if (!skb) { + IWL_ERR(mvm, "alloc_skb failed\n"); + return; + } + + rx_status = IEEE80211_SKB_RXCB(skb); + + /* + * drop the packet if it has failed being decrypted by HW + */ + if (iwl_mvm_set_mac80211_rx_flag(mvm, hdr, rx_status, rx_pkt_status, + &crypt_len)) { + IWL_DEBUG_DROP(mvm, "Bad decryption results 0x%08x\n", + rx_pkt_status); + kfree_skb(skb); + return; + } + + /* + * Keep packets with CRC errors (and with overrun) for monitor mode + * (otherwise the firmware discards them) but mark them as bad. + */ + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_CRC_OK) || + !(rx_pkt_status & RX_MPDU_RES_STATUS_OVERRUN_OK)) { + IWL_DEBUG_RX(mvm, "Bad CRC or FIFO: 0x%08X.\n", rx_pkt_status); + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + } + + /* This will be used in several places later */ + rate_n_flags = le32_to_cpu(phy_info->rate_n_flags); + + /* rx_status carries information about the packet to mac80211 */ + rx_status->mactime = le64_to_cpu(phy_info->timestamp); + rx_status->device_timestamp = le32_to_cpu(phy_info->system_timestamp); + rx_status->band = + (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_BAND_24)) ? + NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + rx_status->freq = + ieee80211_channel_to_frequency(le16_to_cpu(phy_info->channel), + rx_status->band); + + /* TSF as indicated by the firmware is at INA time */ + rx_status->flag |= RX_FLAG_MACTIME_PLCP_START; + + iwl_mvm_get_signal_strength(mvm, phy_info, rx_status); + + IWL_DEBUG_STATS_LIMIT(mvm, "Rssi %d, TSF %llu\n", rx_status->signal, + (unsigned long long)rx_status->mactime); + + rcu_read_lock(); + if (rx_pkt_status & RX_MPDU_RES_STATUS_SRC_STA_FOUND) { + u32 id = rx_pkt_status & RX_MPDU_RES_STATUS_STA_ID_MSK; + + id >>= RX_MDPU_RES_STATUS_STA_ID_SHIFT; + + if (!WARN_ON_ONCE(id >= mvm->fw->ucode_capa.num_stations)) { + sta = rcu_dereference(mvm->fw_id_to_mac_id[id]); + if (IS_ERR(sta)) + sta = NULL; + } + } else if (!is_multicast_ether_addr(hdr->addr2)) { + /* This is fine since we prevent two stations with the same + * address from being added. + */ + sta = ieee80211_find_sta_by_ifaddr(mvm->hw, hdr->addr2, NULL); + } + + if (sta) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_vif *tx_blocked_vif = + rcu_dereference(mvm->csa_tx_blocked_vif); + struct iwl_fw_dbg_trigger_tlv *trig; + struct ieee80211_vif *vif = mvmsta->vif; + + /* We have tx blocked stations (with CS bit). If we heard + * frames from a blocked station on a new channel we can + * TX to it again. + */ + if (unlikely(tx_blocked_vif) && vif == tx_blocked_vif) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(tx_blocked_vif); + + if (mvmvif->csa_target_freq == rx_status->freq) + iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, + false); + } + + rs_update_last_rssi(mvm, mvmsta, rx_status); + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, + ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_RSSI); + + if (trig && ieee80211_is_beacon(hdr->frame_control)) { + struct iwl_fw_dbg_trigger_low_rssi *rssi_trig; + s32 rssi; + + rssi_trig = (void *)trig->data; + rssi = le32_to_cpu(rssi_trig->rssi); + + if (rx_status->signal < rssi) + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + NULL); + } + + if (!mvm->tcm.paused && len >= sizeof(*hdr) && + !is_multicast_ether_addr(hdr->addr1) && + ieee80211_is_data(hdr->frame_control)) + iwl_mvm_rx_handle_tcm(mvm, sta, hdr, len, phy_info, + rate_n_flags); + + if (ieee80211_is_data(hdr->frame_control)) + iwl_mvm_rx_csum(sta, skb, rx_pkt_status); + } + rcu_read_unlock(); + + /* set the preamble flag if appropriate */ + if (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_SHORT_PREAMBLE)) + rx_status->enc_flags |= RX_ENC_FLAG_SHORTPRE; + + if (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_AGG)) { + /* + * We know which subframes of an A-MPDU belong + * together since we get a single PHY response + * from the firmware for all of them + */ + rx_status->flag |= RX_FLAG_AMPDU_DETAILS; + rx_status->ampdu_reference = mvm->ampdu_ref; + } + + /* Set up the HT phy flags */ + switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK_V1) { + case RATE_MCS_CHAN_WIDTH_20: + break; + case RATE_MCS_CHAN_WIDTH_40: + rx_status->bw = RATE_INFO_BW_40; + break; + case RATE_MCS_CHAN_WIDTH_80: + rx_status->bw = RATE_INFO_BW_80; + break; + case RATE_MCS_CHAN_WIDTH_160: + rx_status->bw = RATE_INFO_BW_160; + break; + } + if (!(rate_n_flags & RATE_MCS_CCK_MSK_V1) && + rate_n_flags & RATE_MCS_SGI_MSK_V1) + rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; + if (rate_n_flags & RATE_HT_MCS_GF_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_HT_GF; + if (rate_n_flags & RATE_MCS_LDPC_MSK_V1) + rx_status->enc_flags |= RX_ENC_FLAG_LDPC; + if (rate_n_flags & RATE_MCS_HT_MSK_V1) { + u8 stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >> + RATE_MCS_STBC_POS; + rx_status->encoding = RX_ENC_HT; + rx_status->rate_idx = rate_n_flags & RATE_HT_MCS_INDEX_MSK_V1; + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + } else if (rate_n_flags & RATE_MCS_VHT_MSK_V1) { + u8 stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >> + RATE_MCS_STBC_POS; + rx_status->nss = + ((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS) + 1; + rx_status->rate_idx = rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK; + rx_status->encoding = RX_ENC_VHT; + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + if (rate_n_flags & RATE_MCS_BF_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_BF; + } else { + int rate = iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags, + rx_status->band); + + if (WARN(rate < 0 || rate > 0xFF, + "Invalid rate flags 0x%x, band %d,\n", + rate_n_flags, rx_status->band)) { + kfree_skb(skb); + return; + } + rx_status->rate_idx = rate; + } + +#ifdef CONFIG_IWLWIFI_DEBUGFS + iwl_mvm_update_frame_stats(mvm, rate_n_flags, + rx_status->flag & RX_FLAG_AMPDU_DETAILS); +#endif + + if (unlikely((ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control)) && + mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_ENABLED)) + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_FOUND; + + if (unlikely(ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control))) + rx_status->boottime_ns = ktime_get_boottime_ns(); + + iwl_mvm_pass_packet_to_mac80211(mvm, sta, napi, skb, hdr, len, + crypt_len, rxb); +} + +struct iwl_mvm_stat_data { + struct iwl_mvm *mvm; + __le32 flags; + __le32 mac_id; + u8 beacon_filter_average_energy; + __le32 *beacon_counter; + u8 *beacon_average_energy; +}; + +static void iwl_mvm_stat_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_stat_data *data = _data; + struct iwl_mvm *mvm = data->mvm; + int sig = -data->beacon_filter_average_energy; + int last_event; + int thold = vif->bss_conf.cqm_rssi_thold; + int hyst = vif->bss_conf.cqm_rssi_hyst; + u16 id = le32_to_cpu(data->mac_id); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 vif_id = mvmvif->id; + + /* This doesn't need the MAC ID check since it's not taking the + * data copied into the "data" struct, but rather the data from + * the notification directly. + */ + mvmvif->beacon_stats.num_beacons = + le32_to_cpu(data->beacon_counter[vif_id]); + mvmvif->beacon_stats.avg_signal = + -data->beacon_average_energy[vif_id]; + + /* make sure that beacon statistics don't go backwards with TCM + * request to clear statistics + */ + if (le32_to_cpu(data->flags) & IWL_STATISTICS_REPLY_FLG_CLEAR) + mvmvif->beacon_stats.accu_num_beacons += + mvmvif->beacon_stats.num_beacons; + + if (mvmvif->id != id) + return; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + if (sig == 0) { + IWL_DEBUG_RX(mvm, "RSSI is 0 - skip signal based decision\n"); + return; + } + + mvmvif->bf_data.ave_beacon_signal = sig; + + /* BT Coex */ + if (mvmvif->bf_data.bt_coex_min_thold != + mvmvif->bf_data.bt_coex_max_thold) { + last_event = mvmvif->bf_data.last_bt_coex_event; + if (sig > mvmvif->bf_data.bt_coex_max_thold && + (last_event <= mvmvif->bf_data.bt_coex_min_thold || + last_event == 0)) { + mvmvif->bf_data.last_bt_coex_event = sig; + IWL_DEBUG_RX(mvm, "cqm_iterator bt coex high %d\n", + sig); + iwl_mvm_bt_rssi_event(mvm, vif, RSSI_EVENT_HIGH); + } else if (sig < mvmvif->bf_data.bt_coex_min_thold && + (last_event >= mvmvif->bf_data.bt_coex_max_thold || + last_event == 0)) { + mvmvif->bf_data.last_bt_coex_event = sig; + IWL_DEBUG_RX(mvm, "cqm_iterator bt coex low %d\n", + sig); + iwl_mvm_bt_rssi_event(mvm, vif, RSSI_EVENT_LOW); + } + } + + if (!(vif->driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI)) + return; + + /* CQM Notification */ + last_event = mvmvif->bf_data.last_cqm_event; + if (thold && sig < thold && (last_event == 0 || + sig < last_event - hyst)) { + mvmvif->bf_data.last_cqm_event = sig; + IWL_DEBUG_RX(mvm, "cqm_iterator cqm low %d\n", + sig); + ieee80211_cqm_rssi_notify( + vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, + sig, + GFP_KERNEL); + } else if (sig > thold && + (last_event == 0 || sig > last_event + hyst)) { + mvmvif->bf_data.last_cqm_event = sig; + IWL_DEBUG_RX(mvm, "cqm_iterator cqm high %d\n", + sig); + ieee80211_cqm_rssi_notify( + vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + sig, + GFP_KERNEL); + } +} + +static inline void +iwl_mvm_rx_stats_check_trigger(struct iwl_mvm *mvm, struct iwl_rx_packet *pkt) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_stats *trig_stats; + u32 trig_offset, trig_thold; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, NULL, FW_DBG_TRIGGER_STATS); + if (!trig) + return; + + trig_stats = (void *)trig->data; + + trig_offset = le32_to_cpu(trig_stats->stop_offset); + trig_thold = le32_to_cpu(trig_stats->stop_threshold); + + if (WARN_ON_ONCE(trig_offset >= iwl_rx_packet_payload_len(pkt))) + return; + + if (le32_to_cpup((__le32 *) (pkt->data + trig_offset)) < trig_thold) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, NULL); +} + +static void iwl_mvm_stats_energy_iter(void *_data, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + u8 *energy = _data; + u32 sta_id = mvmsta->sta_id; + + if (WARN_ONCE(sta_id >= IWL_MVM_STATION_COUNT_MAX, "sta_id %d >= %d", + sta_id, IWL_MVM_STATION_COUNT_MAX)) + return; + + if (energy[sta_id]) + mvmsta->avg_energy = energy[sta_id]; + +} + +static void +iwl_mvm_update_tcm_from_stats(struct iwl_mvm *mvm, __le32 *air_time_le, + __le32 *rx_bytes_le) +{ + int i; + + spin_lock(&mvm->tcm.lock); + for (i = 0; i < NUM_MAC_INDEX_DRIVER; i++) { + struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[i]; + u32 rx_bytes = le32_to_cpu(rx_bytes_le[i]); + u32 airtime = le32_to_cpu(air_time_le[i]); + + mdata->rx.airtime += airtime; + mdata->uapsd_nonagg_detect.rx_bytes += rx_bytes; + if (airtime) { + /* re-init every time to store rate from FW */ + ewma_rate_init(&mdata->uapsd_nonagg_detect.rate); + ewma_rate_add(&mdata->uapsd_nonagg_detect.rate, + rx_bytes * 8 / airtime); + } + } + spin_unlock(&mvm->tcm.lock); +} + +static void +iwl_mvm_handle_rx_statistics_tlv(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_mvm_stat_data data = { + .mvm = mvm, + }; + u8 beacon_average_energy[MAC_INDEX_AUX]; + u8 average_energy[IWL_MVM_STATION_COUNT_MAX]; + struct iwl_statistics_operational_ntfy *stats; + int expected_size; + __le32 flags; + int i; + + expected_size = sizeof(*stats); + if (WARN_ONCE(iwl_rx_packet_payload_len(pkt) < expected_size, + "received invalid statistics size (%d)!, expected_size: %d\n", + iwl_rx_packet_payload_len(pkt), expected_size)) + return; + + stats = (void *)&pkt->data; + + if (WARN_ONCE(stats->hdr.type != FW_STATISTICS_OPERATIONAL || + stats->hdr.version != + iwl_fw_lookup_notif_ver(mvm->fw, LONG_GROUP, STATISTICS_CMD, 0), + "received unsupported hdr type %d, version %d\n", + stats->hdr.type, stats->hdr.version)) + return; + + flags = stats->flags; + mvm->radio_stats.rx_time = le64_to_cpu(stats->rx_time); + mvm->radio_stats.tx_time = le64_to_cpu(stats->tx_time); + mvm->radio_stats.on_time_rf = le64_to_cpu(stats->on_time_rf); + mvm->radio_stats.on_time_scan = le64_to_cpu(stats->on_time_scan); + + iwl_mvm_rx_stats_check_trigger(mvm, pkt); + + data.mac_id = stats->mac_id; + data.beacon_filter_average_energy = + le32_to_cpu(stats->beacon_filter_average_energy); + data.flags = flags; + data.beacon_counter = stats->beacon_counter; + for (i = 0; i < ARRAY_SIZE(beacon_average_energy); i++) + beacon_average_energy[i] = + le32_to_cpu(stats->beacon_average_energy[i]); + + data.beacon_average_energy = beacon_average_energy; + + ieee80211_iterate_active_interfaces(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_stat_iterator, + &data); + + for (i = 0; i < ARRAY_SIZE(average_energy); i++) + average_energy[i] = le32_to_cpu(stats->average_energy[i]); + ieee80211_iterate_stations_atomic(mvm->hw, iwl_mvm_stats_energy_iter, + average_energy); + /* + * Don't update in case the statistics are not cleared, since + * we will end up counting twice the same airtime, once in TCM + * request and once in statistics notification. + */ + if (le32_to_cpu(flags) & IWL_STATISTICS_REPLY_FLG_CLEAR) + iwl_mvm_update_tcm_from_stats(mvm, stats->air_time, + stats->rx_bytes); +} + +void iwl_mvm_handle_rx_statistics(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_mvm_stat_data data = { + .mvm = mvm, + }; + __le32 *bytes, *air_time, flags; + int expected_size; + u8 *energy; + + /* From ver 14 and up we use TLV statistics format */ + if (iwl_fw_lookup_notif_ver(mvm->fw, LONG_GROUP, + STATISTICS_CMD, 0) >= 14) + return iwl_mvm_handle_rx_statistics_tlv(mvm, pkt); + + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + if (iwl_mvm_has_new_rx_api(mvm)) + expected_size = sizeof(struct iwl_notif_statistics_v11); + else + expected_size = sizeof(struct iwl_notif_statistics_v10); + } else { + expected_size = sizeof(struct iwl_notif_statistics); + } + + if (WARN_ONCE(iwl_rx_packet_payload_len(pkt) != expected_size, + "received invalid statistics size (%d)!\n", + iwl_rx_packet_payload_len(pkt))) + return; + + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + struct iwl_notif_statistics_v11 *stats = (void *)&pkt->data; + + data.mac_id = stats->rx.general.mac_id; + data.beacon_filter_average_energy = + stats->general.common.beacon_filter_average_energy; + + mvm->rx_stats_v3 = stats->rx; + + mvm->radio_stats.rx_time = + le64_to_cpu(stats->general.common.rx_time); + mvm->radio_stats.tx_time = + le64_to_cpu(stats->general.common.tx_time); + mvm->radio_stats.on_time_rf = + le64_to_cpu(stats->general.common.on_time_rf); + mvm->radio_stats.on_time_scan = + le64_to_cpu(stats->general.common.on_time_scan); + + data.beacon_counter = stats->general.beacon_counter; + data.beacon_average_energy = + stats->general.beacon_average_energy; + flags = stats->flag; + } else { + struct iwl_notif_statistics *stats = (void *)&pkt->data; + + data.mac_id = stats->rx.general.mac_id; + data.beacon_filter_average_energy = + stats->general.common.beacon_filter_average_energy; + + mvm->rx_stats = stats->rx; + + mvm->radio_stats.rx_time = + le64_to_cpu(stats->general.common.rx_time); + mvm->radio_stats.tx_time = + le64_to_cpu(stats->general.common.tx_time); + mvm->radio_stats.on_time_rf = + le64_to_cpu(stats->general.common.on_time_rf); + mvm->radio_stats.on_time_scan = + le64_to_cpu(stats->general.common.on_time_scan); + + data.beacon_counter = stats->general.beacon_counter; + data.beacon_average_energy = + stats->general.beacon_average_energy; + flags = stats->flag; + } + data.flags = flags; + + iwl_mvm_rx_stats_check_trigger(mvm, pkt); + + ieee80211_iterate_active_interfaces(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_stat_iterator, + &data); + + if (!iwl_mvm_has_new_rx_api(mvm)) + return; + + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + struct iwl_notif_statistics_v11 *v11 = (void *)&pkt->data; + + energy = (void *)&v11->load_stats.avg_energy; + bytes = (void *)&v11->load_stats.byte_count; + air_time = (void *)&v11->load_stats.air_time; + } else { + struct iwl_notif_statistics *stats = (void *)&pkt->data; + + energy = (void *)&stats->load_stats.avg_energy; + bytes = (void *)&stats->load_stats.byte_count; + air_time = (void *)&stats->load_stats.air_time; + } + ieee80211_iterate_stations_atomic(mvm->hw, iwl_mvm_stats_energy_iter, + energy); + + /* + * Don't update in case the statistics are not cleared, since + * we will end up counting twice the same airtime, once in TCM + * request and once in statistics notification. + */ + if (le32_to_cpu(flags) & IWL_STATISTICS_REPLY_FLG_CLEAR) + iwl_mvm_update_tcm_from_stats(mvm, air_time, bytes); + +} + +void iwl_mvm_rx_statistics(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + iwl_mvm_handle_rx_statistics(mvm, rxb_addr(rxb)); +} + +void iwl_mvm_window_status_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_ba_window_status_notif *notif = (void *)pkt->data; + int i; + + BUILD_BUG_ON(ARRAY_SIZE(notif->ra_tid) != BA_WINDOW_STREAMS_MAX); + BUILD_BUG_ON(ARRAY_SIZE(notif->mpdu_rx_count) != BA_WINDOW_STREAMS_MAX); + BUILD_BUG_ON(ARRAY_SIZE(notif->bitmap) != BA_WINDOW_STREAMS_MAX); + BUILD_BUG_ON(ARRAY_SIZE(notif->start_seq_num) != BA_WINDOW_STREAMS_MAX); + + rcu_read_lock(); + for (i = 0; i < BA_WINDOW_STREAMS_MAX; i++) { + struct ieee80211_sta *sta; + u8 sta_id, tid; + u64 bitmap; + u32 ssn; + u16 ratid; + u16 received_mpdu; + + ratid = le16_to_cpu(notif->ra_tid[i]); + /* check that this TID is valid */ + if (!(ratid & BA_WINDOW_STATUS_VALID_MSK)) + continue; + + received_mpdu = le16_to_cpu(notif->mpdu_rx_count[i]); + if (received_mpdu == 0) + continue; + + tid = ratid & BA_WINDOW_STATUS_TID_MSK; + /* get the station */ + sta_id = (ratid & BA_WINDOW_STATUS_STA_ID_MSK) + >> BA_WINDOW_STATUS_STA_ID_POS; + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (IS_ERR_OR_NULL(sta)) + continue; + bitmap = le64_to_cpu(notif->bitmap[i]); + ssn = le32_to_cpu(notif->start_seq_num[i]); + + /* update mac80211 with the bitmap for the reordering buffer */ + ieee80211_mark_rx_ba_filtered_frames(sta, tid, ssn, bitmap, + received_mpdu); + } + rcu_read_unlock(); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/rxmq.c b/sys/contrib/dev/iwlwifi/mvm/rxmq.c new file mode 100644 index 00000000000..feb1afd65f8 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/rxmq.c @@ -0,0 +1,2230 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include +#include +#if defined(__FreeBSD__) +#include +#endif +#include "iwl-trans.h" +#include "mvm.h" +#include "fw-api.h" + +static void *iwl_mvm_skb_get_hdr(struct sk_buff *skb) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + u8 *data = skb->data; + + /* Alignment concerns */ + BUILD_BUG_ON(sizeof(struct ieee80211_radiotap_he) % 4); + BUILD_BUG_ON(sizeof(struct ieee80211_radiotap_he_mu) % 4); + BUILD_BUG_ON(sizeof(struct ieee80211_radiotap_lsig) % 4); + BUILD_BUG_ON(sizeof(struct ieee80211_vendor_radiotap) % 4); + + if (rx_status->flag & RX_FLAG_RADIOTAP_HE) + data += sizeof(struct ieee80211_radiotap_he); + if (rx_status->flag & RX_FLAG_RADIOTAP_HE_MU) + data += sizeof(struct ieee80211_radiotap_he_mu); + if (rx_status->flag & RX_FLAG_RADIOTAP_LSIG) + data += sizeof(struct ieee80211_radiotap_lsig); + if (rx_status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) { + struct ieee80211_vendor_radiotap *radiotap = (void *)data; + + data += sizeof(*radiotap) + radiotap->len + radiotap->pad; + } + + return data; +} + +static inline int iwl_mvm_check_pn(struct iwl_mvm *mvm, struct sk_buff *skb, + int queue, struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta; + struct ieee80211_hdr *hdr = iwl_mvm_skb_get_hdr(skb); + struct ieee80211_rx_status *stats = IEEE80211_SKB_RXCB(skb); + struct iwl_mvm_key_pn *ptk_pn; + int res; + u8 tid, keyidx; + u8 pn[IEEE80211_CCMP_PN_LEN]; + u8 *extiv; + + /* do PN checking */ + + /* multicast and non-data only arrives on default queue */ + if (!ieee80211_is_data(hdr->frame_control) || + is_multicast_ether_addr(hdr->addr1)) + return 0; + + /* do not check PN for open AP */ + if (!(stats->flag & RX_FLAG_DECRYPTED)) + return 0; + + /* + * avoid checking for default queue - we don't want to replicate + * all the logic that's necessary for checking the PN on fragmented + * frames, leave that to mac80211 + */ + if (queue == 0) + return 0; + + /* if we are here - this for sure is either CCMP or GCMP */ + if (IS_ERR_OR_NULL(sta)) { + IWL_DEBUG_DROP(mvm, + "expected hw-decrypted unicast frame for station\n"); + return -1; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + extiv = (u8 *)hdr + ieee80211_hdrlen(hdr->frame_control); + keyidx = extiv[3] >> 6; + + ptk_pn = rcu_dereference(mvmsta->ptk_pn[keyidx]); + if (!ptk_pn) + return -1; + + if (ieee80211_is_data_qos(hdr->frame_control)) + tid = ieee80211_get_tid(hdr); + else + tid = 0; + + /* we don't use HCCA/802.11 QoS TSPECs, so drop such frames */ + if (tid >= IWL_MAX_TID_COUNT) + return -1; + + /* load pn */ + pn[0] = extiv[7]; + pn[1] = extiv[6]; + pn[2] = extiv[5]; + pn[3] = extiv[4]; + pn[4] = extiv[1]; + pn[5] = extiv[0]; + + res = memcmp(pn, ptk_pn->q[queue].pn[tid], IEEE80211_CCMP_PN_LEN); + if (res < 0) + return -1; + if (!res && !(stats->flag & RX_FLAG_ALLOW_SAME_PN)) + return -1; + + memcpy(ptk_pn->q[queue].pn[tid], pn, IEEE80211_CCMP_PN_LEN); + stats->flag |= RX_FLAG_PN_VALIDATED; + + return 0; +} + +/* iwl_mvm_create_skb Adds the rxb to a new skb */ +static int iwl_mvm_create_skb(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_hdr *hdr, u16 len, u8 crypt_len, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_mpdu_desc *desc = (void *)pkt->data; + unsigned int headlen, fraglen, pad_len = 0; + unsigned int hdrlen = ieee80211_hdrlen(hdr->frame_control); + + if (desc->mac_flags2 & IWL_RX_MPDU_MFLG2_PAD) { + len -= 2; + pad_len = 2; + } + + /* If frame is small enough to fit in skb->head, pull it completely. + * If not, only pull ieee80211_hdr (including crypto if present, and + * an additional 8 bytes for SNAP/ethertype, see below) so that + * splice() or TCP coalesce are more efficient. + * + * Since, in addition, ieee80211_data_to_8023() always pull in at + * least 8 bytes (possibly more for mesh) we can do the same here + * to save the cost of doing it later. That still doesn't pull in + * the actual IP header since the typical case has a SNAP header. + * If the latter changes (there are efforts in the standards group + * to do so) we should revisit this and ieee80211_data_to_8023(). + */ + headlen = (len <= skb_tailroom(skb)) ? len : + hdrlen + crypt_len + 8; + + /* The firmware may align the packet to DWORD. + * The padding is inserted after the IV. + * After copying the header + IV skip the padding if + * present before copying packet data. + */ + hdrlen += crypt_len; + + if (WARN_ONCE(headlen < hdrlen, + "invalid packet lengths (hdrlen=%d, len=%d, crypt_len=%d)\n", + hdrlen, len, crypt_len)) { + /* + * We warn and trace because we want to be able to see + * it in trace-cmd as well. + */ + IWL_DEBUG_RX(mvm, + "invalid packet lengths (hdrlen=%d, len=%d, crypt_len=%d)\n", + hdrlen, len, crypt_len); + return -EINVAL; + } + + skb_put_data(skb, hdr, hdrlen); + skb_put_data(skb, (u8 *)hdr + hdrlen + pad_len, headlen - hdrlen); + + /* + * If we did CHECKSUM_COMPLETE, the hardware only does it right for + * certain cases and starts the checksum after the SNAP. Check if + * this is the case - it's easier to just bail out to CHECKSUM_NONE + * in the cases the hardware didn't handle, since it's rare to see + * such packets, even though the hardware did calculate the checksum + * in this case, just starting after the MAC header instead. + */ + if (skb->ip_summed == CHECKSUM_COMPLETE) { + struct { + u8 hdr[6]; + __be16 type; + } __packed *shdr = (void *)((u8 *)hdr + hdrlen + pad_len); + + if (unlikely(headlen - hdrlen < sizeof(*shdr) || + !ether_addr_equal(shdr->hdr, rfc1042_header) || + (shdr->type != htons(ETH_P_IP) && + shdr->type != htons(ETH_P_ARP) && + shdr->type != htons(ETH_P_IPV6) && + shdr->type != htons(ETH_P_8021Q) && + shdr->type != htons(ETH_P_PAE) && + shdr->type != htons(ETH_P_TDLS)))) + skb->ip_summed = CHECKSUM_NONE; + } + + fraglen = len - headlen; + + if (fraglen) { + int offset = (u8 *)hdr + headlen + pad_len - + (u8 *)rxb_addr(rxb) + rxb_offset(rxb); + + skb_add_rx_frag(skb, 0, rxb_steal_page(rxb), offset, + fraglen, rxb->truesize); + } + + return 0; +} + +static void iwl_mvm_add_rtap_sniffer_config(struct iwl_mvm *mvm, + struct sk_buff *skb) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + struct ieee80211_vendor_radiotap *radiotap; + const int size = sizeof(*radiotap) + sizeof(__le16); + + if (!mvm->cur_aid) + return; + + /* ensure alignment */ + BUILD_BUG_ON((size + 2) % 4); + + radiotap = skb_put(skb, size + 2); + radiotap->align = 1; + /* Intel OUI */ + radiotap->oui[0] = 0xf6; + radiotap->oui[1] = 0x54; + radiotap->oui[2] = 0x25; + /* radiotap sniffer config sub-namespace */ + radiotap->subns = 1; + radiotap->present = 0x1; + radiotap->len = size - sizeof(*radiotap); + radiotap->pad = 2; + + /* fill the data now */ + memcpy(radiotap->data, &mvm->cur_aid, sizeof(mvm->cur_aid)); + /* and clear the padding */ + memset(radiotap->data + sizeof(__le16), 0, radiotap->pad); + + rx_status->flag |= RX_FLAG_RADIOTAP_VENDOR_DATA; +} + +/* iwl_mvm_pass_packet_to_mac80211 - passes the packet for mac80211 */ +static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm, + struct napi_struct *napi, + struct sk_buff *skb, int queue, + struct ieee80211_sta *sta) +{ + if (iwl_mvm_check_pn(mvm, skb, queue, sta)) + kfree_skb(skb); + else + ieee80211_rx_napi(mvm->hw, sta, skb, napi); +} + +static void iwl_mvm_get_signal_strength(struct iwl_mvm *mvm, + struct ieee80211_rx_status *rx_status, + u32 rate_n_flags, int energy_a, + int energy_b) +{ + int max_energy; + u32 rate_flags = rate_n_flags; + + energy_a = energy_a ? -energy_a : S8_MIN; + energy_b = energy_b ? -energy_b : S8_MIN; + max_energy = max(energy_a, energy_b); + + IWL_DEBUG_STATS(mvm, "energy In A %d B %d, and max %d\n", + energy_a, energy_b, max_energy); + + rx_status->signal = max_energy; + rx_status->chains = + (rate_flags & RATE_MCS_ANT_AB_MSK) >> RATE_MCS_ANT_POS; + rx_status->chain_signal[0] = energy_a; + rx_status->chain_signal[1] = energy_b; +} + +static int iwl_mvm_rx_mgmt_prot(struct ieee80211_sta *sta, + struct ieee80211_hdr *hdr, + struct iwl_rx_mpdu_desc *desc, + u32 status) +{ + struct iwl_mvm_sta *mvmsta; + struct iwl_mvm_vif *mvmvif; + u8 keyid; + struct ieee80211_key_conf *key; + u32 len = le16_to_cpu(desc->mpdu_len); + const u8 *frame = (void *)hdr; + + if ((status & IWL_RX_MPDU_STATUS_SEC_MASK) == IWL_RX_MPDU_STATUS_SEC_NONE) + return 0; + + /* + * For non-beacon, we don't really care. But beacons may + * be filtered out, and we thus need the firmware's replay + * detection, otherwise beacons the firmware previously + * filtered could be replayed, or something like that, and + * it can filter a lot - though usually only if nothing has + * changed. + */ + if (!ieee80211_is_beacon(hdr->frame_control)) + return 0; + + /* key mismatch - will also report !MIC_OK but we shouldn't count it */ + if (!(status & IWL_RX_MPDU_STATUS_KEY_VALID)) + return -1; + + /* good cases */ + if (likely(status & IWL_RX_MPDU_STATUS_MIC_OK && + !(status & IWL_RX_MPDU_STATUS_REPLAY_ERROR))) + return 0; + + if (!sta) + return -1; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + + /* + * both keys will have the same cipher and MIC length, use + * whichever one is available + */ + key = rcu_dereference(mvmvif->bcn_prot.keys[0]); + if (!key) { + key = rcu_dereference(mvmvif->bcn_prot.keys[1]); + if (!key) + return -1; + } + + if (len < key->icv_len + IEEE80211_GMAC_PN_LEN + 2) + return -1; + + /* get the real key ID */ + keyid = frame[len - key->icv_len - IEEE80211_GMAC_PN_LEN - 2]; + /* and if that's the other key, look it up */ + if (keyid != key->keyidx) { + /* + * shouldn't happen since firmware checked, but be safe + * in case the MIC length is wrong too, for example + */ + if (keyid != 6 && keyid != 7) + return -1; + key = rcu_dereference(mvmvif->bcn_prot.keys[keyid - 6]); + if (!key) + return -1; + } + + /* Report status to mac80211 */ + if (!(status & IWL_RX_MPDU_STATUS_MIC_OK)) + ieee80211_key_mic_failure(key); + else if (status & IWL_RX_MPDU_STATUS_REPLAY_ERROR) + ieee80211_key_replay(key); + + return -1; +} + +static int iwl_mvm_rx_crypto(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + struct ieee80211_hdr *hdr, + struct ieee80211_rx_status *stats, u16 phy_info, + struct iwl_rx_mpdu_desc *desc, + u32 pkt_flags, int queue, u8 *crypt_len) +{ + u32 status = le32_to_cpu(desc->status); + + /* + * Drop UNKNOWN frames in aggregation, unless in monitor mode + * (where we don't have the keys). + * We limit this to aggregation because in TKIP this is a valid + * scenario, since we may not have the (correct) TTAK (phase 1 + * key) in the firmware. + */ + if (phy_info & IWL_RX_MPDU_PHY_AMPDU && + (status & IWL_RX_MPDU_STATUS_SEC_MASK) == + IWL_RX_MPDU_STATUS_SEC_UNKNOWN && !mvm->monitor_on) + return -1; + + if (unlikely(ieee80211_is_mgmt(hdr->frame_control) && + !ieee80211_has_protected(hdr->frame_control))) + return iwl_mvm_rx_mgmt_prot(sta, hdr, desc, status); + + if (!ieee80211_has_protected(hdr->frame_control) || + (status & IWL_RX_MPDU_STATUS_SEC_MASK) == + IWL_RX_MPDU_STATUS_SEC_NONE) + return 0; + + /* TODO: handle packets encrypted with unknown alg */ +#if defined(__FreeBSD__) + /* XXX-BZ do similar to rx.c for now as these are plenty. */ + if ((status & IWL_RX_MPDU_STATUS_SEC_MASK) == + IWL_RX_MPDU_STATUS_SEC_ENC_ERR) + return (0); +#endif + + switch (status & IWL_RX_MPDU_STATUS_SEC_MASK) { + case IWL_RX_MPDU_STATUS_SEC_CCM: + case IWL_RX_MPDU_STATUS_SEC_GCM: + BUILD_BUG_ON(IEEE80211_CCMP_PN_LEN != IEEE80211_GCMP_PN_LEN); + /* alg is CCM: check MIC only */ + if (!(status & IWL_RX_MPDU_STATUS_MIC_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + if (pkt_flags & FH_RSCSR_RADA_EN) + stats->flag |= RX_FLAG_MIC_STRIPPED; + *crypt_len = IEEE80211_CCMP_HDR_LEN; + return 0; + case IWL_RX_MPDU_STATUS_SEC_TKIP: + /* Don't drop the frame and decrypt it in SW */ + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_DEPRECATE_TTAK) && + !(status & IWL_RX_MPDU_RES_STATUS_TTAK_OK)) + return 0; + + if (mvm->trans->trans_cfg->gen2 && + !(status & RX_MPDU_RES_STATUS_MIC_OK)) + stats->flag |= RX_FLAG_MMIC_ERROR; + + *crypt_len = IEEE80211_TKIP_IV_LEN; + fallthrough; + case IWL_RX_MPDU_STATUS_SEC_WEP: + if (!(status & IWL_RX_MPDU_STATUS_ICV_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + if ((status & IWL_RX_MPDU_STATUS_SEC_MASK) == + IWL_RX_MPDU_STATUS_SEC_WEP) + *crypt_len = IEEE80211_WEP_IV_LEN; + + if (pkt_flags & FH_RSCSR_RADA_EN) { + stats->flag |= RX_FLAG_ICV_STRIPPED; + if (mvm->trans->trans_cfg->gen2) + stats->flag |= RX_FLAG_MMIC_STRIPPED; + } + + return 0; + case IWL_RX_MPDU_STATUS_SEC_EXT_ENC: + if (!(status & IWL_RX_MPDU_STATUS_MIC_OK)) + return -1; + stats->flag |= RX_FLAG_DECRYPTED; + return 0; + case RX_MPDU_RES_STATUS_SEC_CMAC_GMAC_ENC: + break; + default: + /* + * Sometimes we can get frames that were not decrypted + * because the firmware didn't have the keys yet. This can + * happen after connection where we can get multicast frames + * before the GTK is installed. + * Silently drop those frames. + * Also drop un-decrypted frames in monitor mode. + */ + if (!is_multicast_ether_addr(hdr->addr1) && + !mvm->monitor_on && net_ratelimit()) + IWL_ERR(mvm, "%s: Unhandled alg: 0x%x\n", + __func__, status); + } + + return 0; +} + +static void iwl_mvm_rx_csum(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct sk_buff *skb, + struct iwl_rx_packet *pkt) +{ + struct iwl_rx_mpdu_desc *desc = (void *)pkt->data; + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + if (pkt->len_n_flags & cpu_to_le32(FH_RSCSR_RPA_EN)) { + u16 hwsum = be16_to_cpu(desc->v3.raw_xsum); + + skb->ip_summed = CHECKSUM_COMPLETE; + skb->csum = csum_unfold(~(__force __sum16)hwsum); + } + } else { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_vif *mvmvif; + u16 flags = le16_to_cpu(desc->l3l4_flags); + u8 l3_prot = (u8)((flags & IWL_RX_L3L4_L3_PROTO_MASK) >> + IWL_RX_L3_PROTO_POS); + + mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + + if (mvmvif->features & NETIF_F_RXCSUM && + flags & IWL_RX_L3L4_TCP_UDP_CSUM_OK && + (flags & IWL_RX_L3L4_IP_HDR_CSUM_OK || + l3_prot == IWL_RX_L3_TYPE_IPV6 || + l3_prot == IWL_RX_L3_TYPE_IPV6_FRAG)) + skb->ip_summed = CHECKSUM_UNNECESSARY; + } +} + +/* + * returns true if a packet is a duplicate and should be dropped. + * Updates AMSDU PN tracking info + */ +static bool iwl_mvm_is_dup(struct ieee80211_sta *sta, int queue, + struct ieee80211_rx_status *rx_status, + struct ieee80211_hdr *hdr, + struct iwl_rx_mpdu_desc *desc) +{ + struct iwl_mvm_sta *mvm_sta; + struct iwl_mvm_rxq_dup_data *dup_data; + u8 tid, sub_frame_idx; + + if (WARN_ON(IS_ERR_OR_NULL(sta))) + return false; + + mvm_sta = iwl_mvm_sta_from_mac80211(sta); + dup_data = &mvm_sta->dup_data[queue]; + + /* + * Drop duplicate 802.11 retransmissions + * (IEEE 802.11-2012: 9.3.2.10 "Duplicate detection and recovery") + */ + if (ieee80211_is_ctl(hdr->frame_control) || + ieee80211_is_qos_nullfunc(hdr->frame_control) || + is_multicast_ether_addr(hdr->addr1)) { + rx_status->flag |= RX_FLAG_DUP_VALIDATED; + return false; + } + + if (ieee80211_is_data_qos(hdr->frame_control)) + /* frame has qos control */ + tid = ieee80211_get_tid(hdr); + else + tid = IWL_MAX_TID_COUNT; + + /* If this wasn't a part of an A-MSDU the sub-frame index will be 0 */ + sub_frame_idx = desc->amsdu_info & + IWL_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK; + + if (unlikely(ieee80211_has_retry(hdr->frame_control) && + dup_data->last_seq[tid] == hdr->seq_ctrl && + dup_data->last_sub_frame[tid] >= sub_frame_idx)) + return true; + + /* Allow same PN as the first subframe for following sub frames */ + if (dup_data->last_seq[tid] == hdr->seq_ctrl && + sub_frame_idx > dup_data->last_sub_frame[tid] && + desc->mac_flags2 & IWL_RX_MPDU_MFLG2_AMSDU) + rx_status->flag |= RX_FLAG_ALLOW_SAME_PN; + + dup_data->last_seq[tid] = hdr->seq_ctrl; + dup_data->last_sub_frame[tid] = sub_frame_idx; + + rx_status->flag |= RX_FLAG_DUP_VALIDATED; + + return false; +} + +/* + * Returns true if sn2 - buffer_size < sn1 < sn2. + * To be used only in order to compare reorder buffer head with NSSN. + * We fully trust NSSN unless it is behind us due to reorder timeout. + * Reorder timeout can only bring us up to buffer_size SNs ahead of NSSN. + */ +static bool iwl_mvm_is_sn_less(u16 sn1, u16 sn2, u16 buffer_size) +{ + return ieee80211_sn_less(sn1, sn2) && + !ieee80211_sn_less(sn1, sn2 - buffer_size); +} + +static void iwl_mvm_sync_nssn(struct iwl_mvm *mvm, u8 baid, u16 nssn) +{ + if (IWL_MVM_USE_NSSN_SYNC) { + struct iwl_mvm_nssn_sync_data notif = { + .baid = baid, + .nssn = nssn, + }; + + iwl_mvm_sync_rx_queues_internal(mvm, IWL_MVM_RXQ_NSSN_SYNC, false, + ¬if, sizeof(notif)); + } +} + +#define RX_REORDER_BUF_TIMEOUT_MQ (HZ / 10) + +enum iwl_mvm_release_flags { + IWL_MVM_RELEASE_SEND_RSS_SYNC = BIT(0), + IWL_MVM_RELEASE_FROM_RSS_SYNC = BIT(1), +}; + +static void iwl_mvm_release_frames(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct napi_struct *napi, + struct iwl_mvm_baid_data *baid_data, + struct iwl_mvm_reorder_buffer *reorder_buf, + u16 nssn, u32 flags) +{ + struct iwl_mvm_reorder_buf_entry *entries = + &baid_data->entries[reorder_buf->queue * + baid_data->entries_per_queue]; + u16 ssn = reorder_buf->head_sn; + + lockdep_assert_held(&reorder_buf->lock); + + /* + * We keep the NSSN not too far behind, if we are sync'ing it and it + * is more than 2048 ahead of us, it must be behind us. Discard it. + * This can happen if the queue that hit the 0 / 2048 seqno was lagging + * behind and this queue already processed packets. The next if + * would have caught cases where this queue would have processed less + * than 64 packets, but it may have processed more than 64 packets. + */ + if ((flags & IWL_MVM_RELEASE_FROM_RSS_SYNC) && + ieee80211_sn_less(nssn, ssn)) + goto set_timer; + + /* ignore nssn smaller than head sn - this can happen due to timeout */ + if (iwl_mvm_is_sn_less(nssn, ssn, reorder_buf->buf_size)) + goto set_timer; + + while (iwl_mvm_is_sn_less(ssn, nssn, reorder_buf->buf_size)) { + int index = ssn % reorder_buf->buf_size; + struct sk_buff_head *skb_list = &entries[index].e.frames; + struct sk_buff *skb; + + ssn = ieee80211_sn_inc(ssn); + if ((flags & IWL_MVM_RELEASE_SEND_RSS_SYNC) && + (ssn == 2048 || ssn == 0)) + iwl_mvm_sync_nssn(mvm, baid_data->baid, ssn); + + /* + * Empty the list. Will have more than one frame for A-MSDU. + * Empty list is valid as well since nssn indicates frames were + * received. + */ + while ((skb = __skb_dequeue(skb_list))) { + iwl_mvm_pass_packet_to_mac80211(mvm, napi, skb, + reorder_buf->queue, + sta); + reorder_buf->num_stored--; + } + } + reorder_buf->head_sn = nssn; + +set_timer: + if (reorder_buf->num_stored && !reorder_buf->removed) { + u16 index = reorder_buf->head_sn % reorder_buf->buf_size; + + while (skb_queue_empty(&entries[index].e.frames)) + index = (index + 1) % reorder_buf->buf_size; + /* modify timer to match next frame's expiration time */ + mod_timer(&reorder_buf->reorder_timer, + entries[index].e.reorder_time + 1 + + RX_REORDER_BUF_TIMEOUT_MQ); + } else { + del_timer(&reorder_buf->reorder_timer); + } +} + +void iwl_mvm_reorder_timer_expired(struct timer_list *t) +{ + struct iwl_mvm_reorder_buffer *buf = from_timer(buf, t, reorder_timer); + struct iwl_mvm_baid_data *baid_data = + iwl_mvm_baid_data_from_reorder_buf(buf); + struct iwl_mvm_reorder_buf_entry *entries = + &baid_data->entries[buf->queue * baid_data->entries_per_queue]; + int i; + u16 sn = 0, index = 0; + bool expired = false; + bool cont = false; + + spin_lock(&buf->lock); + + if (!buf->num_stored || buf->removed) { + spin_unlock(&buf->lock); + return; + } + + for (i = 0; i < buf->buf_size ; i++) { + index = (buf->head_sn + i) % buf->buf_size; + + if (skb_queue_empty(&entries[index].e.frames)) { + /* + * If there is a hole and the next frame didn't expire + * we want to break and not advance SN + */ + cont = false; + continue; + } + if (!cont && + !time_after(jiffies, entries[index].e.reorder_time + + RX_REORDER_BUF_TIMEOUT_MQ)) + break; + + expired = true; + /* continue until next hole after this expired frames */ + cont = true; + sn = ieee80211_sn_add(buf->head_sn, i + 1); + } + + if (expired) { + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + u8 sta_id = baid_data->sta_id; + + rcu_read_lock(); + sta = rcu_dereference(buf->mvm->fw_id_to_mac_id[sta_id]); + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + /* SN is set to the last expired frame + 1 */ + IWL_DEBUG_HT(buf->mvm, + "Releasing expired frames for sta %u, sn %d\n", + sta_id, sn); + iwl_mvm_event_frame_timeout_callback(buf->mvm, mvmsta->vif, + sta, baid_data->tid); + iwl_mvm_release_frames(buf->mvm, sta, NULL, baid_data, + buf, sn, IWL_MVM_RELEASE_SEND_RSS_SYNC); + rcu_read_unlock(); + } else { + /* + * If no frame expired and there are stored frames, index is now + * pointing to the first unexpired frame - modify timer + * accordingly to this frame. + */ + mod_timer(&buf->reorder_timer, + entries[index].e.reorder_time + + 1 + RX_REORDER_BUF_TIMEOUT_MQ); + } + spin_unlock(&buf->lock); +} + +static void iwl_mvm_del_ba(struct iwl_mvm *mvm, int queue, + struct iwl_mvm_delba_data *data) +{ + struct iwl_mvm_baid_data *ba_data; + struct ieee80211_sta *sta; + struct iwl_mvm_reorder_buffer *reorder_buf; + u8 baid = data->baid; + + if (WARN_ONCE(baid >= IWL_MAX_BAID, "invalid BAID: %x\n", baid)) + return; + + rcu_read_lock(); + + ba_data = rcu_dereference(mvm->baid_map[baid]); + if (WARN_ON_ONCE(!ba_data)) + goto out; + + sta = rcu_dereference(mvm->fw_id_to_mac_id[ba_data->sta_id]); + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) + goto out; + + reorder_buf = &ba_data->reorder_buf[queue]; + + /* release all frames that are in the reorder buffer to the stack */ + spin_lock_bh(&reorder_buf->lock); + iwl_mvm_release_frames(mvm, sta, NULL, ba_data, reorder_buf, + ieee80211_sn_add(reorder_buf->head_sn, + reorder_buf->buf_size), + 0); + spin_unlock_bh(&reorder_buf->lock); + del_timer_sync(&reorder_buf->reorder_timer); + +out: + rcu_read_unlock(); +} + +static void iwl_mvm_release_frames_from_notif(struct iwl_mvm *mvm, + struct napi_struct *napi, + u8 baid, u16 nssn, int queue, + u32 flags) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_reorder_buffer *reorder_buf; + struct iwl_mvm_baid_data *ba_data; + + IWL_DEBUG_HT(mvm, "Frame release notification for BAID %u, NSSN %d\n", + baid, nssn); + + if (WARN_ON_ONCE(baid == IWL_RX_REORDER_DATA_INVALID_BAID || + baid >= ARRAY_SIZE(mvm->baid_map))) + return; + + rcu_read_lock(); + + ba_data = rcu_dereference(mvm->baid_map[baid]); + if (WARN_ON_ONCE(!ba_data)) + goto out; + + sta = rcu_dereference(mvm->fw_id_to_mac_id[ba_data->sta_id]); + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) + goto out; + + reorder_buf = &ba_data->reorder_buf[queue]; + + spin_lock_bh(&reorder_buf->lock); + iwl_mvm_release_frames(mvm, sta, napi, ba_data, + reorder_buf, nssn, flags); + spin_unlock_bh(&reorder_buf->lock); + +out: + rcu_read_unlock(); +} + +static void iwl_mvm_nssn_sync(struct iwl_mvm *mvm, + struct napi_struct *napi, int queue, + const struct iwl_mvm_nssn_sync_data *data) +{ + iwl_mvm_release_frames_from_notif(mvm, napi, data->baid, + data->nssn, queue, + IWL_MVM_RELEASE_FROM_RSS_SYNC); +} + +void iwl_mvm_rx_queue_notif(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rxq_sync_notification *notif; + struct iwl_mvm_internal_rxq_notif *internal_notif; + u32 len = iwl_rx_packet_payload_len(pkt); + + notif = (void *)pkt->data; + internal_notif = (void *)notif->payload; + + if (WARN_ONCE(len < sizeof(*notif) + sizeof(*internal_notif), + "invalid notification size %d (%d)", + len, (int)(sizeof(*notif) + sizeof(*internal_notif)))) + return; + len -= sizeof(*notif) + sizeof(*internal_notif); + + if (internal_notif->sync && + mvm->queue_sync_cookie != internal_notif->cookie) { + WARN_ONCE(1, "Received expired RX queue sync message\n"); + return; + } + + switch (internal_notif->type) { + case IWL_MVM_RXQ_EMPTY: + WARN_ONCE(len, "invalid empty notification size %d", len); + break; + case IWL_MVM_RXQ_NOTIF_DEL_BA: + if (WARN_ONCE(len != sizeof(struct iwl_mvm_delba_data), + "invalid delba notification size %d (%d)", + len, (int)sizeof(struct iwl_mvm_delba_data))) + break; + iwl_mvm_del_ba(mvm, queue, (void *)internal_notif->data); + break; + case IWL_MVM_RXQ_NSSN_SYNC: + if (WARN_ONCE(len != sizeof(struct iwl_mvm_nssn_sync_data), + "invalid nssn sync notification size %d (%d)", + len, (int)sizeof(struct iwl_mvm_nssn_sync_data))) + break; + iwl_mvm_nssn_sync(mvm, napi, queue, + (void *)internal_notif->data); + break; + default: + WARN_ONCE(1, "Invalid identifier %d", internal_notif->type); + } + + if (internal_notif->sync) { + WARN_ONCE(!test_and_clear_bit(queue, &mvm->queue_sync_state), + "queue sync: queue %d responded a second time!\n", + queue); + if (READ_ONCE(mvm->queue_sync_state) == 0) + wake_up(&mvm->rx_sync_waitq); + } +} + +static void iwl_mvm_oldsn_workaround(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, int tid, + struct iwl_mvm_reorder_buffer *buffer, + u32 reorder, u32 gp2, int queue) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (gp2 != buffer->consec_oldsn_ampdu_gp2) { + /* we have a new (A-)MPDU ... */ + + /* + * reset counter to 0 if we didn't have any oldsn in + * the last A-MPDU (as detected by GP2 being identical) + */ + if (!buffer->consec_oldsn_prev_drop) + buffer->consec_oldsn_drops = 0; + + /* either way, update our tracking state */ + buffer->consec_oldsn_ampdu_gp2 = gp2; + } else if (buffer->consec_oldsn_prev_drop) { + /* + * tracking state didn't change, and we had an old SN + * indication before - do nothing in this case, we + * already noted this one down and are waiting for the + * next A-MPDU (by GP2) + */ + return; + } + + /* return unless this MPDU has old SN */ + if (!(reorder & IWL_RX_MPDU_REORDER_BA_OLD_SN)) + return; + + /* update state */ + buffer->consec_oldsn_prev_drop = 1; + buffer->consec_oldsn_drops++; + + /* if limit is reached, send del BA and reset state */ + if (buffer->consec_oldsn_drops == IWL_MVM_AMPDU_CONSEC_DROPS_DELBA) { + IWL_WARN(mvm, + "reached %d old SN frames from %pM on queue %d, stopping BA session on TID %d\n", + IWL_MVM_AMPDU_CONSEC_DROPS_DELBA, + sta->addr, queue, tid); + ieee80211_stop_rx_ba_session(mvmsta->vif, BIT(tid), sta->addr); + buffer->consec_oldsn_prev_drop = 0; + buffer->consec_oldsn_drops = 0; + } +} + +/* + * Returns true if the MPDU was buffered\dropped, false if it should be passed + * to upper layer. + */ +static bool iwl_mvm_reorder(struct iwl_mvm *mvm, + struct napi_struct *napi, + int queue, + struct ieee80211_sta *sta, + struct sk_buff *skb, + struct iwl_rx_mpdu_desc *desc) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + struct ieee80211_hdr *hdr = iwl_mvm_skb_get_hdr(skb); + struct iwl_mvm_sta *mvm_sta; + struct iwl_mvm_baid_data *baid_data; + struct iwl_mvm_reorder_buffer *buffer; + struct sk_buff *tail; + u32 reorder = le32_to_cpu(desc->reorder_data); + bool amsdu = desc->mac_flags2 & IWL_RX_MPDU_MFLG2_AMSDU; + bool last_subframe = + desc->amsdu_info & IWL_RX_MPDU_AMSDU_LAST_SUBFRAME; + u8 tid = ieee80211_get_tid(hdr); + u8 sub_frame_idx = desc->amsdu_info & + IWL_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK; + struct iwl_mvm_reorder_buf_entry *entries; + int index; + u16 nssn, sn; + u8 baid; + + baid = (reorder & IWL_RX_MPDU_REORDER_BAID_MASK) >> + IWL_RX_MPDU_REORDER_BAID_SHIFT; + + /* + * This also covers the case of receiving a Block Ack Request + * outside a BA session; we'll pass it to mac80211 and that + * then sends a delBA action frame. + * This also covers pure monitor mode, in which case we won't + * have any BA sessions. + */ + if (baid == IWL_RX_REORDER_DATA_INVALID_BAID) + return false; + + /* no sta yet */ + if (WARN_ONCE(IS_ERR_OR_NULL(sta), + "Got valid BAID without a valid station assigned\n")) + return false; + + mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + /* not a data packet or a bar */ + if (!ieee80211_is_back_req(hdr->frame_control) && + (!ieee80211_is_data_qos(hdr->frame_control) || + is_multicast_ether_addr(hdr->addr1))) + return false; + + if (unlikely(!ieee80211_is_data_present(hdr->frame_control))) + return false; + + baid_data = rcu_dereference(mvm->baid_map[baid]); + if (!baid_data) { + IWL_DEBUG_RX(mvm, + "Got valid BAID but no baid allocated, bypass the re-ordering buffer. Baid %d reorder 0x%x\n", + baid, reorder); + return false; + } + + if (WARN(tid != baid_data->tid || mvm_sta->sta_id != baid_data->sta_id, + "baid 0x%x is mapped to sta:%d tid:%d, but was received for sta:%d tid:%d\n", + baid, baid_data->sta_id, baid_data->tid, mvm_sta->sta_id, + tid)) + return false; + + nssn = reorder & IWL_RX_MPDU_REORDER_NSSN_MASK; + sn = (reorder & IWL_RX_MPDU_REORDER_SN_MASK) >> + IWL_RX_MPDU_REORDER_SN_SHIFT; + + buffer = &baid_data->reorder_buf[queue]; + entries = &baid_data->entries[queue * baid_data->entries_per_queue]; + + spin_lock_bh(&buffer->lock); + + if (!buffer->valid) { + if (reorder & IWL_RX_MPDU_REORDER_BA_OLD_SN) { + spin_unlock_bh(&buffer->lock); + return false; + } + buffer->valid = true; + } + + if (ieee80211_is_back_req(hdr->frame_control)) { + iwl_mvm_release_frames(mvm, sta, napi, baid_data, + buffer, nssn, 0); + goto drop; + } + + /* + * If there was a significant jump in the nssn - adjust. + * If the SN is smaller than the NSSN it might need to first go into + * the reorder buffer, in which case we just release up to it and the + * rest of the function will take care of storing it and releasing up to + * the nssn. + * This should not happen. This queue has been lagging and it should + * have been updated by a IWL_MVM_RXQ_NSSN_SYNC notification. Be nice + * and update the other queues. + */ + if (!iwl_mvm_is_sn_less(nssn, buffer->head_sn + buffer->buf_size, + buffer->buf_size) || + !ieee80211_sn_less(sn, buffer->head_sn + buffer->buf_size)) { + u16 min_sn = ieee80211_sn_less(sn, nssn) ? sn : nssn; + + iwl_mvm_release_frames(mvm, sta, napi, baid_data, buffer, + min_sn, IWL_MVM_RELEASE_SEND_RSS_SYNC); + } + + iwl_mvm_oldsn_workaround(mvm, sta, tid, buffer, reorder, + rx_status->device_timestamp, queue); + + /* drop any oudated packets */ + if (ieee80211_sn_less(sn, buffer->head_sn)) + goto drop; + + /* release immediately if allowed by nssn and no stored frames */ + if (!buffer->num_stored && ieee80211_sn_less(sn, nssn)) { + if (iwl_mvm_is_sn_less(buffer->head_sn, nssn, + buffer->buf_size) && + (!amsdu || last_subframe)) { + /* + * If we crossed the 2048 or 0 SN, notify all the + * queues. This is done in order to avoid having a + * head_sn that lags behind for too long. When that + * happens, we can get to a situation where the head_sn + * is within the interval [nssn - buf_size : nssn] + * which will make us think that the nssn is a packet + * that we already freed because of the reordering + * buffer and we will ignore it. So maintain the + * head_sn somewhat updated across all the queues: + * when it crosses 0 and 2048. + */ + if (sn == 2048 || sn == 0) + iwl_mvm_sync_nssn(mvm, baid, sn); + buffer->head_sn = nssn; + } + /* No need to update AMSDU last SN - we are moving the head */ + spin_unlock_bh(&buffer->lock); + return false; + } + + /* + * release immediately if there are no stored frames, and the sn is + * equal to the head. + * This can happen due to reorder timer, where NSSN is behind head_sn. + * When we released everything, and we got the next frame in the + * sequence, according to the NSSN we can't release immediately, + * while technically there is no hole and we can move forward. + */ + if (!buffer->num_stored && sn == buffer->head_sn) { + if (!amsdu || last_subframe) { + if (sn == 2048 || sn == 0) + iwl_mvm_sync_nssn(mvm, baid, sn); + buffer->head_sn = ieee80211_sn_inc(buffer->head_sn); + } + /* No need to update AMSDU last SN - we are moving the head */ + spin_unlock_bh(&buffer->lock); + return false; + } + + index = sn % buffer->buf_size; + + /* + * Check if we already stored this frame + * As AMSDU is either received or not as whole, logic is simple: + * If we have frames in that position in the buffer and the last frame + * originated from AMSDU had a different SN then it is a retransmission. + * If it is the same SN then if the subframe index is incrementing it + * is the same AMSDU - otherwise it is a retransmission. + */ + tail = skb_peek_tail(&entries[index].e.frames); + if (tail && !amsdu) + goto drop; + else if (tail && (sn != buffer->last_amsdu || + buffer->last_sub_index >= sub_frame_idx)) + goto drop; + + /* put in reorder buffer */ + __skb_queue_tail(&entries[index].e.frames, skb); + buffer->num_stored++; + entries[index].e.reorder_time = jiffies; + + if (amsdu) { + buffer->last_amsdu = sn; + buffer->last_sub_index = sub_frame_idx; + } + + /* + * We cannot trust NSSN for AMSDU sub-frames that are not the last. + * The reason is that NSSN advances on the first sub-frame, and may + * cause the reorder buffer to advance before all the sub-frames arrive. + * Example: reorder buffer contains SN 0 & 2, and we receive AMSDU with + * SN 1. NSSN for first sub frame will be 3 with the result of driver + * releasing SN 0,1, 2. When sub-frame 1 arrives - reorder buffer is + * already ahead and it will be dropped. + * If the last sub-frame is not on this queue - we will get frame + * release notification with up to date NSSN. + */ + if (!amsdu || last_subframe) + iwl_mvm_release_frames(mvm, sta, napi, baid_data, + buffer, nssn, + IWL_MVM_RELEASE_SEND_RSS_SYNC); + + spin_unlock_bh(&buffer->lock); + return true; + +drop: + kfree_skb(skb); + spin_unlock_bh(&buffer->lock); + return true; +} + +static void iwl_mvm_agg_rx_received(struct iwl_mvm *mvm, + u32 reorder_data, u8 baid) +{ + unsigned long now = jiffies; + unsigned long timeout; + struct iwl_mvm_baid_data *data; + + rcu_read_lock(); + + data = rcu_dereference(mvm->baid_map[baid]); + if (!data) { + IWL_DEBUG_RX(mvm, + "Got valid BAID but no baid allocated, bypass the re-ordering buffer. Baid %d reorder 0x%x\n", + baid, reorder_data); + goto out; + } + + if (!data->timeout) + goto out; + + timeout = data->timeout; + /* + * Do not update last rx all the time to avoid cache bouncing + * between the rx queues. + * Update it every timeout. Worst case is the session will + * expire after ~ 2 * timeout, which doesn't matter that much. + */ + if (time_before(data->last_rx + TU_TO_JIFFIES(timeout), now)) + /* Update is atomic */ + data->last_rx = now; + +out: + rcu_read_unlock(); +} + +static void iwl_mvm_flip_address(u8 *addr) +{ + int i; + u8 mac_addr[ETH_ALEN]; + + for (i = 0; i < ETH_ALEN; i++) + mac_addr[i] = addr[ETH_ALEN - i - 1]; + ether_addr_copy(addr, mac_addr); +} + +struct iwl_mvm_rx_phy_data { + enum iwl_rx_phy_info_type info_type; + __le32 d0, d1, d2, d3; + __le16 d4; +}; + +static void iwl_mvm_decode_he_mu_ext(struct iwl_mvm *mvm, + struct iwl_mvm_rx_phy_data *phy_data, + u32 rate_n_flags, + struct ieee80211_radiotap_he_mu *he_mu) +{ + u32 phy_data2 = le32_to_cpu(phy_data->d2); + u32 phy_data3 = le32_to_cpu(phy_data->d3); + u16 phy_data4 = le16_to_cpu(phy_data->d4); + + if (FIELD_GET(IWL_RX_PHY_DATA4_HE_MU_EXT_CH1_CRC_OK, phy_data4)) { + he_mu->flags1 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_RU_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_CTR_26T_RU_KNOWN); + + he_mu->flags1 |= + le16_encode_bits(FIELD_GET(IWL_RX_PHY_DATA4_HE_MU_EXT_CH1_CTR_RU, + phy_data4), + IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_CTR_26T_RU); + + he_mu->ru_ch1[0] = FIELD_GET(IWL_RX_PHY_DATA2_HE_MU_EXT_CH1_RU0, + phy_data2); + he_mu->ru_ch1[1] = FIELD_GET(IWL_RX_PHY_DATA3_HE_MU_EXT_CH1_RU1, + phy_data3); + he_mu->ru_ch1[2] = FIELD_GET(IWL_RX_PHY_DATA2_HE_MU_EXT_CH1_RU2, + phy_data2); + he_mu->ru_ch1[3] = FIELD_GET(IWL_RX_PHY_DATA3_HE_MU_EXT_CH1_RU3, + phy_data3); + } + + if (FIELD_GET(IWL_RX_PHY_DATA4_HE_MU_EXT_CH2_CRC_OK, phy_data4) && + (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK_V1) != RATE_MCS_CHAN_WIDTH_20) { + he_mu->flags1 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH2_RU_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH2_CTR_26T_RU_KNOWN); + + he_mu->flags2 |= + le16_encode_bits(FIELD_GET(IWL_RX_PHY_DATA4_HE_MU_EXT_CH2_CTR_RU, + phy_data4), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_CH2_CTR_26T_RU); + + he_mu->ru_ch2[0] = FIELD_GET(IWL_RX_PHY_DATA2_HE_MU_EXT_CH2_RU0, + phy_data2); + he_mu->ru_ch2[1] = FIELD_GET(IWL_RX_PHY_DATA3_HE_MU_EXT_CH2_RU1, + phy_data3); + he_mu->ru_ch2[2] = FIELD_GET(IWL_RX_PHY_DATA2_HE_MU_EXT_CH2_RU2, + phy_data2); + he_mu->ru_ch2[3] = FIELD_GET(IWL_RX_PHY_DATA3_HE_MU_EXT_CH2_RU3, + phy_data3); + } +} + +static void +iwl_mvm_decode_he_phy_ru_alloc(struct iwl_mvm_rx_phy_data *phy_data, + u32 rate_n_flags, + struct ieee80211_radiotap_he *he, + struct ieee80211_radiotap_he_mu *he_mu, + struct ieee80211_rx_status *rx_status) +{ + /* + * Unfortunately, we have to leave the mac80211 data + * incorrect for the case that we receive an HE-MU + * transmission and *don't* have the HE phy data (due + * to the bits being used for TSF). This shouldn't + * happen though as management frames where we need + * the TSF/timers are not be transmitted in HE-MU. + */ + u8 ru = le32_get_bits(phy_data->d1, IWL_RX_PHY_DATA1_HE_RU_ALLOC_MASK); + u32 he_type = rate_n_flags & RATE_MCS_HE_TYPE_MSK_V1; + u8 offs = 0; + + rx_status->bw = RATE_INFO_BW_HE_RU; + + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN); + + switch (ru) { + case 0 ... 36: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_26; + offs = ru; + break; + case 37 ... 52: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_52; + offs = ru - 37; + break; + case 53 ... 60: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_106; + offs = ru - 53; + break; + case 61 ... 64: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_242; + offs = ru - 61; + break; + case 65 ... 66: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_484; + offs = ru - 65; + break; + case 67: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_996; + break; + case 68: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_2x996; + break; + } + he->data2 |= le16_encode_bits(offs, + IEEE80211_RADIOTAP_HE_DATA2_RU_OFFSET); + he->data2 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_PRISEC_80_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_RU_OFFSET_KNOWN); + if (phy_data->d1 & cpu_to_le32(IWL_RX_PHY_DATA1_HE_RU_ALLOC_SEC80)) + he->data2 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_PRISEC_80_SEC); + +#define CHECK_BW(bw) \ + BUILD_BUG_ON(IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_ ## bw ## MHZ != \ + RATE_MCS_CHAN_WIDTH_##bw >> RATE_MCS_CHAN_WIDTH_POS); \ + BUILD_BUG_ON(IEEE80211_RADIOTAP_HE_DATA6_TB_PPDU_BW_ ## bw ## MHZ != \ + RATE_MCS_CHAN_WIDTH_##bw >> RATE_MCS_CHAN_WIDTH_POS) + CHECK_BW(20); + CHECK_BW(40); + CHECK_BW(80); + CHECK_BW(160); + + if (he_mu) + he_mu->flags2 |= + le16_encode_bits(FIELD_GET(RATE_MCS_CHAN_WIDTH_MSK_V1, + rate_n_flags), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW); + else if (he_type == RATE_MCS_HE_TYPE_TRIG_V1) + he->data6 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA6_TB_PPDU_BW_KNOWN) | + le16_encode_bits(FIELD_GET(RATE_MCS_CHAN_WIDTH_MSK_V1, + rate_n_flags), + IEEE80211_RADIOTAP_HE_DATA6_TB_PPDU_BW); +} + +static void iwl_mvm_decode_he_phy_data(struct iwl_mvm *mvm, + struct iwl_mvm_rx_phy_data *phy_data, + struct ieee80211_radiotap_he *he, + struct ieee80211_radiotap_he_mu *he_mu, + struct ieee80211_rx_status *rx_status, + u32 rate_n_flags, int queue) +{ + switch (phy_data->info_type) { + case IWL_RX_PHY_INFO_TYPE_NONE: + case IWL_RX_PHY_INFO_TYPE_CCK: + case IWL_RX_PHY_INFO_TYPE_OFDM_LGCY: + case IWL_RX_PHY_INFO_TYPE_HT: + case IWL_RX_PHY_INFO_TYPE_VHT_SU: + case IWL_RX_PHY_INFO_TYPE_VHT_MU: + return; + case IWL_RX_PHY_INFO_TYPE_HE_TB_EXT: + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE2_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE3_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE4_KNOWN); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d2, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE1), + IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE1); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d2, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE2), + IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE2); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d2, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE3), + IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE3); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d2, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE4), + IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE4); + fallthrough; + case IWL_RX_PHY_INFO_TYPE_HE_SU: + case IWL_RX_PHY_INFO_TYPE_HE_MU: + case IWL_RX_PHY_INFO_TYPE_HE_MU_EXT: + case IWL_RX_PHY_INFO_TYPE_HE_TB: + /* HE common */ + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_LDPC_XSYMSEG_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_DOPPLER_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_BSS_COLOR_KNOWN); + he->data2 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_PRE_FEC_PAD_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_PE_DISAMBIG_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_TXOP_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_NUM_LTF_SYMS_KNOWN); + he->data3 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_BSS_COLOR_MASK), + IEEE80211_RADIOTAP_HE_DATA3_BSS_COLOR); + if (phy_data->info_type != IWL_RX_PHY_INFO_TYPE_HE_TB && + phy_data->info_type != IWL_RX_PHY_INFO_TYPE_HE_TB_EXT) { + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_UL_DL_KNOWN); + he->data3 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_UPLINK), + IEEE80211_RADIOTAP_HE_DATA3_UL_DL); + } + he->data3 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_LDPC_EXT_SYM), + IEEE80211_RADIOTAP_HE_DATA3_LDPC_XSYMSEG); + he->data5 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_PRE_FEC_PAD_MASK), + IEEE80211_RADIOTAP_HE_DATA5_PRE_FEC_PAD); + he->data5 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_PE_DISAMBIG), + IEEE80211_RADIOTAP_HE_DATA5_PE_DISAMBIG); + he->data5 |= le16_encode_bits(le32_get_bits(phy_data->d1, + IWL_RX_PHY_DATA1_HE_LTF_NUM_MASK), + IEEE80211_RADIOTAP_HE_DATA5_NUM_LTF_SYMS); + he->data6 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_TXOP_DUR_MASK), + IEEE80211_RADIOTAP_HE_DATA6_TXOP); + he->data6 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_DOPPLER), + IEEE80211_RADIOTAP_HE_DATA6_DOPPLER); + break; + } + + switch (phy_data->info_type) { + case IWL_RX_PHY_INFO_TYPE_HE_MU_EXT: + case IWL_RX_PHY_INFO_TYPE_HE_MU: + case IWL_RX_PHY_INFO_TYPE_HE_SU: + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE_KNOWN); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_SPATIAL_REUSE_MASK), + IEEE80211_RADIOTAP_HE_DATA4_SU_MU_SPTL_REUSE); + break; + default: + /* nothing here */ + break; + } + + switch (phy_data->info_type) { + case IWL_RX_PHY_INFO_TYPE_HE_MU_EXT: + he_mu->flags1 |= + le16_encode_bits(le16_get_bits(phy_data->d4, + IWL_RX_PHY_DATA4_HE_MU_EXT_SIGB_DCM), + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_DCM); + he_mu->flags1 |= + le16_encode_bits(le16_get_bits(phy_data->d4, + IWL_RX_PHY_DATA4_HE_MU_EXT_SIGB_MCS_MASK), + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_MCS); + he_mu->flags2 |= + le16_encode_bits(le16_get_bits(phy_data->d4, + IWL_RX_PHY_DATA4_HE_MU_EXT_PREAMBLE_PUNC_TYPE_MASK), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_PUNC_FROM_SIG_A_BW); + iwl_mvm_decode_he_mu_ext(mvm, phy_data, rate_n_flags, he_mu); + fallthrough; + case IWL_RX_PHY_INFO_TYPE_HE_MU: + he_mu->flags2 |= + le16_encode_bits(le32_get_bits(phy_data->d1, + IWL_RX_PHY_DATA1_HE_MU_SIBG_SYM_OR_USER_NUM_MASK), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_SIG_B_SYMS_USERS); + he_mu->flags2 |= + le16_encode_bits(le32_get_bits(phy_data->d1, + IWL_RX_PHY_DATA1_HE_MU_SIGB_COMPRESSION), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_SIG_B_COMP); + fallthrough; + case IWL_RX_PHY_INFO_TYPE_HE_TB: + case IWL_RX_PHY_INFO_TYPE_HE_TB_EXT: + iwl_mvm_decode_he_phy_ru_alloc(phy_data, rate_n_flags, + he, he_mu, rx_status); + break; + case IWL_RX_PHY_INFO_TYPE_HE_SU: + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_BEAM_CHANGE_KNOWN); + he->data3 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_BEAM_CHNG), + IEEE80211_RADIOTAP_HE_DATA3_BEAM_CHANGE); + break; + default: + /* nothing */ + break; + } +} + +static void iwl_mvm_rx_he(struct iwl_mvm *mvm, struct sk_buff *skb, + struct iwl_mvm_rx_phy_data *phy_data, + u32 rate_n_flags, u16 phy_info, int queue) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + struct ieee80211_radiotap_he *he = NULL; + struct ieee80211_radiotap_he_mu *he_mu = NULL; + u32 he_type = rate_n_flags & RATE_MCS_HE_TYPE_MSK; + u8 stbc, ltf; + static const struct ieee80211_radiotap_he known = { + .data1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_DATA_DCM_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_STBC_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_CODING_KNOWN), + .data2 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_GI_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_TXBF_KNOWN), + }; + static const struct ieee80211_radiotap_he_mu mu_known = { + .flags1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_MCS_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_DCM_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_SYMS_USERS_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_COMP_KNOWN), + .flags2 = cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS2_PUNC_FROM_SIG_A_BW_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_KNOWN), + }; + + he = skb_put_data(skb, &known, sizeof(known)); + rx_status->flag |= RX_FLAG_RADIOTAP_HE; + + if (phy_data->info_type == IWL_RX_PHY_INFO_TYPE_HE_MU || + phy_data->info_type == IWL_RX_PHY_INFO_TYPE_HE_MU_EXT) { + he_mu = skb_put_data(skb, &mu_known, sizeof(mu_known)); + rx_status->flag |= RX_FLAG_RADIOTAP_HE_MU; + } + + /* report the AMPDU-EOF bit on single frames */ + if (!queue && !(phy_info & IWL_RX_MPDU_PHY_AMPDU)) { + rx_status->flag |= RX_FLAG_AMPDU_DETAILS; + rx_status->flag |= RX_FLAG_AMPDU_EOF_BIT_KNOWN; + if (phy_data->d0 & cpu_to_le32(IWL_RX_PHY_DATA0_HE_DELIM_EOF)) + rx_status->flag |= RX_FLAG_AMPDU_EOF_BIT; + } + + if (phy_info & IWL_RX_MPDU_PHY_TSF_OVERLOAD) + iwl_mvm_decode_he_phy_data(mvm, phy_data, he, he_mu, rx_status, + rate_n_flags, queue); + + /* update aggregation data for monitor sake on default queue */ + if (!queue && (phy_info & IWL_RX_MPDU_PHY_TSF_OVERLOAD) && + (phy_info & IWL_RX_MPDU_PHY_AMPDU)) { + bool toggle_bit = phy_info & IWL_RX_MPDU_PHY_AMPDU_TOGGLE; + + /* toggle is switched whenever new aggregation starts */ + if (toggle_bit != mvm->ampdu_toggle) { + rx_status->flag |= RX_FLAG_AMPDU_EOF_BIT_KNOWN; + if (phy_data->d0 & cpu_to_le32(IWL_RX_PHY_DATA0_HE_DELIM_EOF)) + rx_status->flag |= RX_FLAG_AMPDU_EOF_BIT; + } + } + + if (he_type == RATE_MCS_HE_TYPE_EXT_SU && + rate_n_flags & RATE_MCS_HE_106T_MSK) { + rx_status->bw = RATE_INFO_BW_HE_RU; + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_106; + } + + /* actually data is filled in mac80211 */ + if (he_type == RATE_MCS_HE_TYPE_SU || + he_type == RATE_MCS_HE_TYPE_EXT_SU) + he->data1 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN); + + stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >> RATE_MCS_STBC_POS; + rx_status->nss = + ((rate_n_flags & RATE_MCS_NSS_MSK) >> + RATE_MCS_NSS_POS) + 1; + rx_status->rate_idx = rate_n_flags & RATE_MCS_CODE_MSK; + rx_status->encoding = RX_ENC_HE; + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + if (rate_n_flags & RATE_MCS_BF_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_BF; + + rx_status->he_dcm = + !!(rate_n_flags & RATE_HE_DUAL_CARRIER_MODE_MSK); + +#define CHECK_TYPE(F) \ + BUILD_BUG_ON(IEEE80211_RADIOTAP_HE_DATA1_FORMAT_ ## F != \ + (RATE_MCS_HE_TYPE_ ## F >> RATE_MCS_HE_TYPE_POS)) + + CHECK_TYPE(SU); + CHECK_TYPE(EXT_SU); + CHECK_TYPE(MU); + CHECK_TYPE(TRIG); + + he->data1 |= cpu_to_le16(he_type >> RATE_MCS_HE_TYPE_POS); + + if (rate_n_flags & RATE_MCS_BF_MSK) + he->data5 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA5_TXBF); + + switch ((rate_n_flags & RATE_MCS_HE_GI_LTF_MSK) >> + RATE_MCS_HE_GI_LTF_POS) { + case 0: + if (he_type == RATE_MCS_HE_TYPE_TRIG) + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + if (he_type == RATE_MCS_HE_TYPE_MU) + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_4X; + else + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_1X; + break; + case 1: + if (he_type == RATE_MCS_HE_TYPE_TRIG) + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_2X; + break; + case 2: + if (he_type == RATE_MCS_HE_TYPE_TRIG) { + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_4X; + } else { + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_2X; + } + break; + case 3: + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_4X; + break; + case 4: + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_4X; + break; + default: + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_UNKNOWN; + } + + he->data5 |= le16_encode_bits(ltf, + IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE); +} + +static void iwl_mvm_decode_lsig(struct sk_buff *skb, + struct iwl_mvm_rx_phy_data *phy_data) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + struct ieee80211_radiotap_lsig *lsig; + + switch (phy_data->info_type) { + case IWL_RX_PHY_INFO_TYPE_HT: + case IWL_RX_PHY_INFO_TYPE_VHT_SU: + case IWL_RX_PHY_INFO_TYPE_VHT_MU: + case IWL_RX_PHY_INFO_TYPE_HE_TB_EXT: + case IWL_RX_PHY_INFO_TYPE_HE_SU: + case IWL_RX_PHY_INFO_TYPE_HE_MU: + case IWL_RX_PHY_INFO_TYPE_HE_MU_EXT: + case IWL_RX_PHY_INFO_TYPE_HE_TB: + lsig = skb_put(skb, sizeof(*lsig)); + lsig->data1 = cpu_to_le16(IEEE80211_RADIOTAP_LSIG_DATA1_LENGTH_KNOWN); + lsig->data2 = le16_encode_bits(le32_get_bits(phy_data->d1, + IWL_RX_PHY_DATA1_LSIG_LEN_MASK), + IEEE80211_RADIOTAP_LSIG_DATA2_LENGTH); + rx_status->flag |= RX_FLAG_RADIOTAP_LSIG; + break; + default: + break; + } +} + +static inline u8 iwl_mvm_nl80211_band_from_rx_msdu(u8 phy_band) +{ + switch (phy_band) { + case PHY_BAND_24: + return NL80211_BAND_2GHZ; + case PHY_BAND_5: + return NL80211_BAND_5GHZ; + case PHY_BAND_6: + return NL80211_BAND_6GHZ; + default: + WARN_ONCE(1, "Unsupported phy band (%u)\n", phy_band); + return NL80211_BAND_5GHZ; + } +} + +struct iwl_rx_sta_csa { + bool all_sta_unblocked; + struct ieee80211_vif *vif; +}; + +static void iwl_mvm_rx_get_sta_block_tx(void *data, struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_rx_sta_csa *rx_sta_csa = data; + + if (mvmsta->vif != rx_sta_csa->vif) + return; + + if (mvmsta->disable_tx) + rx_sta_csa->all_sta_unblocked = false; +} + +void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct ieee80211_rx_status *rx_status; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_mpdu_desc *desc = (void *)pkt->data; + struct ieee80211_hdr *hdr; + u32 len; + u32 pkt_len = iwl_rx_packet_payload_len(pkt); + u32 rate_n_flags, gp2_on_air_rise; + u16 phy_info; + struct ieee80211_sta *sta = NULL; + struct sk_buff *skb; + u8 crypt_len = 0, channel, energy_a, energy_b; + size_t desc_size; + struct iwl_mvm_rx_phy_data phy_data = { + .info_type = IWL_RX_PHY_INFO_TYPE_NONE, + }; + u32 format; + bool is_sgi; + + if (unlikely(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))) + return; + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + desc_size = sizeof(*desc); + else + desc_size = IWL_RX_DESC_SIZE_V1; + + if (unlikely(pkt_len < desc_size)) { + IWL_DEBUG_DROP(mvm, "Bad REPLY_RX_MPDU_CMD size\n"); + return; + } + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + rate_n_flags = le32_to_cpu(desc->v3.rate_n_flags); + channel = desc->v3.channel; + gp2_on_air_rise = le32_to_cpu(desc->v3.gp2_on_air_rise); + energy_a = desc->v3.energy_a; + energy_b = desc->v3.energy_b; + + phy_data.d0 = desc->v3.phy_data0; + phy_data.d1 = desc->v3.phy_data1; + phy_data.d2 = desc->v3.phy_data2; + phy_data.d3 = desc->v3.phy_data3; + } else { + rate_n_flags = le32_to_cpu(desc->v1.rate_n_flags); + channel = desc->v1.channel; + gp2_on_air_rise = le32_to_cpu(desc->v1.gp2_on_air_rise); + energy_a = desc->v1.energy_a; + energy_b = desc->v1.energy_b; + + phy_data.d0 = desc->v1.phy_data0; + phy_data.d1 = desc->v1.phy_data1; + phy_data.d2 = desc->v1.phy_data2; + phy_data.d3 = desc->v1.phy_data3; + } + if (iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, + REPLY_RX_MPDU_CMD, 0) < 4) { + rate_n_flags = iwl_new_rate_from_v1(rate_n_flags); + IWL_DEBUG_DROP(mvm, "Got old format rate, converting. New rate: 0x%x\n", + rate_n_flags); + } + format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + + len = le16_to_cpu(desc->mpdu_len); + + if (unlikely(len + desc_size > pkt_len)) { + IWL_DEBUG_DROP(mvm, "FW lied about packet len\n"); + return; + } + + phy_info = le16_to_cpu(desc->phy_info); + phy_data.d4 = desc->phy_data4; + + if (phy_info & IWL_RX_MPDU_PHY_TSF_OVERLOAD) + phy_data.info_type = + le32_get_bits(phy_data.d1, + IWL_RX_PHY_DATA1_INFO_TYPE_MASK); + + hdr = (void *)(pkt->data + desc_size); + /* Dont use dev_alloc_skb(), we'll have enough headroom once + * ieee80211_hdr pulled. + */ + skb = alloc_skb(128, GFP_ATOMIC); + if (!skb) { + IWL_ERR(mvm, "alloc_skb failed\n"); + return; + } + + if (desc->mac_flags2 & IWL_RX_MPDU_MFLG2_PAD) { + /* + * If the device inserted padding it means that (it thought) + * the 802.11 header wasn't a multiple of 4 bytes long. In + * this case, reserve two bytes at the start of the SKB to + * align the payload properly in case we end up copying it. + */ + skb_reserve(skb, 2); + } + + rx_status = IEEE80211_SKB_RXCB(skb); + + /* This may be overridden by iwl_mvm_rx_he() to HE_RU */ + switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { + case RATE_MCS_CHAN_WIDTH_20: + break; + case RATE_MCS_CHAN_WIDTH_40: + rx_status->bw = RATE_INFO_BW_40; + break; + case RATE_MCS_CHAN_WIDTH_80: + rx_status->bw = RATE_INFO_BW_80; + break; + case RATE_MCS_CHAN_WIDTH_160: + rx_status->bw = RATE_INFO_BW_160; + break; + } + + if (format == RATE_MCS_HE_MSK) + iwl_mvm_rx_he(mvm, skb, &phy_data, rate_n_flags, + phy_info, queue); + + iwl_mvm_decode_lsig(skb, &phy_data); + + /* + * Keep packets with CRC errors (and with overrun) for monitor mode + * (otherwise the firmware discards them) but mark them as bad. + */ + if (!(desc->status & cpu_to_le32(IWL_RX_MPDU_STATUS_CRC_OK)) || + !(desc->status & cpu_to_le32(IWL_RX_MPDU_STATUS_OVERRUN_OK))) { + IWL_DEBUG_RX(mvm, "Bad CRC or FIFO: 0x%08X.\n", + le32_to_cpu(desc->status)); + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + } + /* set the preamble flag if appropriate */ + if (format == RATE_MCS_CCK_MSK && + phy_info & IWL_RX_MPDU_PHY_SHORT_PREAMBLE) + rx_status->enc_flags |= RX_ENC_FLAG_SHORTPRE; + + if (likely(!(phy_info & IWL_RX_MPDU_PHY_TSF_OVERLOAD))) { + u64 tsf_on_air_rise; + + if (mvm->trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210) + tsf_on_air_rise = le64_to_cpu(desc->v3.tsf_on_air_rise); + else + tsf_on_air_rise = le64_to_cpu(desc->v1.tsf_on_air_rise); + + rx_status->mactime = tsf_on_air_rise; + /* TSF as indicated by the firmware is at INA time */ + rx_status->flag |= RX_FLAG_MACTIME_PLCP_START; + } + + rx_status->device_timestamp = gp2_on_air_rise; + if (iwl_mvm_is_band_in_rx_supported(mvm)) { + u8 band = BAND_IN_RX_STATUS(desc->mac_phy_idx); + + rx_status->band = iwl_mvm_nl80211_band_from_rx_msdu(band); + } else { + rx_status->band = channel > 14 ? NL80211_BAND_5GHZ : + NL80211_BAND_2GHZ; + } + rx_status->freq = ieee80211_channel_to_frequency(channel, + rx_status->band); + iwl_mvm_get_signal_strength(mvm, rx_status, rate_n_flags, energy_a, + energy_b); + + /* update aggregation data for monitor sake on default queue */ + if (!queue && (phy_info & IWL_RX_MPDU_PHY_AMPDU)) { + bool toggle_bit = phy_info & IWL_RX_MPDU_PHY_AMPDU_TOGGLE; + + rx_status->flag |= RX_FLAG_AMPDU_DETAILS; + /* + * Toggle is switched whenever new aggregation starts. Make + * sure ampdu_reference is never 0 so we can later use it to + * see if the frame was really part of an A-MPDU or not. + */ + if (toggle_bit != mvm->ampdu_toggle) { + mvm->ampdu_ref++; + if (mvm->ampdu_ref == 0) + mvm->ampdu_ref++; + mvm->ampdu_toggle = toggle_bit; + } + rx_status->ampdu_reference = mvm->ampdu_ref; + } + + if (unlikely(mvm->monitor_on)) + iwl_mvm_add_rtap_sniffer_config(mvm, skb); + + rcu_read_lock(); + + if (desc->status & cpu_to_le32(IWL_RX_MPDU_STATUS_SRC_STA_FOUND)) { + u8 id = le32_get_bits(desc->status, IWL_RX_MPDU_STATUS_STA_ID); + + if (!WARN_ON_ONCE(id >= mvm->fw->ucode_capa.num_stations)) { + sta = rcu_dereference(mvm->fw_id_to_mac_id[id]); + if (IS_ERR(sta)) + sta = NULL; + } + } else if (!is_multicast_ether_addr(hdr->addr2)) { + /* + * This is fine since we prevent two stations with the same + * address from being added. + */ + sta = ieee80211_find_sta_by_ifaddr(mvm->hw, hdr->addr2, NULL); + } + + if (iwl_mvm_rx_crypto(mvm, sta, hdr, rx_status, phy_info, desc, + le32_to_cpu(pkt->len_n_flags), queue, + &crypt_len)) { + kfree_skb(skb); + goto out; + } + + if (sta) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_vif *tx_blocked_vif = + rcu_dereference(mvm->csa_tx_blocked_vif); + u8 baid = (u8)((le32_to_cpu(desc->reorder_data) & + IWL_RX_MPDU_REORDER_BAID_MASK) >> + IWL_RX_MPDU_REORDER_BAID_SHIFT); + struct iwl_fw_dbg_trigger_tlv *trig; + struct ieee80211_vif *vif = mvmsta->vif; + + if (!mvm->tcm.paused && len >= sizeof(*hdr) && + !is_multicast_ether_addr(hdr->addr1) && + ieee80211_is_data(hdr->frame_control) && + time_after(jiffies, mvm->tcm.ts + MVM_TCM_PERIOD)) + schedule_delayed_work(&mvm->tcm.work, 0); + + /* + * We have tx blocked stations (with CS bit). If we heard + * frames from a blocked station on a new channel we can + * TX to it again. + */ + if (unlikely(tx_blocked_vif) && tx_blocked_vif == vif) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(tx_blocked_vif); + struct iwl_rx_sta_csa rx_sta_csa = { + .all_sta_unblocked = true, + .vif = tx_blocked_vif, + }; + + if (mvmvif->csa_target_freq == rx_status->freq) + iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, + false); + ieee80211_iterate_stations_atomic(mvm->hw, + iwl_mvm_rx_get_sta_block_tx, + &rx_sta_csa); + + if (rx_sta_csa.all_sta_unblocked) { + RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); + /* Unblock BCAST / MCAST station */ + iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, false); + cancel_delayed_work_sync(&mvm->cs_tx_unblock_dwork); + } + } + + rs_update_last_rssi(mvm, mvmsta, rx_status); + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, + ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_RSSI); + + if (trig && ieee80211_is_beacon(hdr->frame_control)) { + struct iwl_fw_dbg_trigger_low_rssi *rssi_trig; + s32 rssi; + + rssi_trig = (void *)trig->data; + rssi = le32_to_cpu(rssi_trig->rssi); + + if (rx_status->signal < rssi) + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + NULL); + } + + if (ieee80211_is_data(hdr->frame_control)) + iwl_mvm_rx_csum(mvm, sta, skb, pkt); + + if (iwl_mvm_is_dup(sta, queue, rx_status, hdr, desc)) { + kfree_skb(skb); + goto out; + } + + /* + * Our hardware de-aggregates AMSDUs but copies the mac header + * as it to the de-aggregated MPDUs. We need to turn off the + * AMSDU bit in the QoS control ourselves. + * In addition, HW reverses addr3 and addr4 - reverse it back. + */ + if ((desc->mac_flags2 & IWL_RX_MPDU_MFLG2_AMSDU) && + !WARN_ON(!ieee80211_is_data_qos(hdr->frame_control))) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + + *qc &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT; + + if (mvm->trans->trans_cfg->device_family == + IWL_DEVICE_FAMILY_9000) { + iwl_mvm_flip_address(hdr->addr3); + + if (ieee80211_has_a4(hdr->frame_control)) + iwl_mvm_flip_address(hdr->addr4); + } + } + if (baid != IWL_RX_REORDER_DATA_INVALID_BAID) { + u32 reorder_data = le32_to_cpu(desc->reorder_data); + + iwl_mvm_agg_rx_received(mvm, reorder_data, baid); + } + } + + is_sgi = format == RATE_MCS_HE_MSK ? + iwl_he_is_sgi(rate_n_flags) : + rate_n_flags & RATE_MCS_SGI_MSK; + + if (!(format == RATE_MCS_CCK_MSK) && is_sgi) + rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; + if (rate_n_flags & RATE_MCS_LDPC_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_LDPC; + if (format == RATE_MCS_HT_MSK) { + u8 stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >> + RATE_MCS_STBC_POS; + rx_status->encoding = RX_ENC_HT; + rx_status->rate_idx = RATE_HT_MCS_INDEX(rate_n_flags); + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + } else if (format == RATE_MCS_VHT_MSK) { + u8 stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >> + RATE_MCS_STBC_POS; + rx_status->nss = + ((rate_n_flags & RATE_MCS_NSS_MSK) >> + RATE_MCS_NSS_POS) + 1; + rx_status->rate_idx = rate_n_flags & RATE_MCS_CODE_MSK; + rx_status->encoding = RX_ENC_VHT; + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + if (rate_n_flags & RATE_MCS_BF_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_BF; + } else if (!(format == RATE_MCS_HE_MSK)) { + int rate = iwl_mvm_legacy_hw_idx_to_mac80211_idx(rate_n_flags, + rx_status->band); + + if (WARN(rate < 0 || rate > 0xFF, + "Invalid rate flags 0x%x, band %d,\n", + rate_n_flags, rx_status->band)) { + kfree_skb(skb); + goto out; + } + rx_status->rate_idx = rate; + } + + /* management stuff on default queue */ + if (!queue) { + if (unlikely((ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control)) && + mvm->sched_scan_pass_all == + SCHED_SCAN_PASS_ALL_ENABLED)) + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_FOUND; + + if (unlikely(ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control))) + rx_status->boottime_ns = ktime_get_boottime_ns(); + } + + if (iwl_mvm_create_skb(mvm, skb, hdr, len, crypt_len, rxb)) { + kfree_skb(skb); + goto out; + } + + if (!iwl_mvm_reorder(mvm, napi, queue, sta, skb, desc)) + iwl_mvm_pass_packet_to_mac80211(mvm, napi, skb, queue, + sta); +out: + rcu_read_unlock(); +} + +void iwl_mvm_rx_monitor_no_data(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct ieee80211_rx_status *rx_status; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_no_data *desc = (void *)pkt->data; + u32 rate_n_flags = le32_to_cpu(desc->rate); + u32 gp2_on_air_rise = le32_to_cpu(desc->on_air_rise_time); + u32 rssi = le32_to_cpu(desc->rssi); + u32 info_type = le32_to_cpu(desc->info) & RX_NO_DATA_INFO_TYPE_MSK; + u16 phy_info = IWL_RX_MPDU_PHY_TSF_OVERLOAD; + struct ieee80211_sta *sta = NULL; + struct sk_buff *skb; + u8 channel, energy_a, energy_b; + u32 format; + struct iwl_mvm_rx_phy_data phy_data = { + .info_type = le32_get_bits(desc->phy_info[1], + IWL_RX_PHY_DATA1_INFO_TYPE_MASK), + .d0 = desc->phy_info[0], + .d1 = desc->phy_info[1], + }; + bool is_sgi; + + if (iwl_fw_lookup_notif_ver(mvm->fw, DATA_PATH_GROUP, + RX_NO_DATA_NOTIF, 0) < 2) { + IWL_DEBUG_DROP(mvm, "Got an old rate format. Old rate: 0x%x\n", + rate_n_flags); + rate_n_flags = iwl_new_rate_from_v1(rate_n_flags); + IWL_DEBUG_DROP(mvm, " Rate after conversion to the new format: 0x%x\n", + rate_n_flags); + } + format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + + if (unlikely(iwl_rx_packet_payload_len(pkt) < sizeof(*desc))) + return; + + if (unlikely(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))) + return; + + energy_a = (rssi & RX_NO_DATA_CHAIN_A_MSK) >> RX_NO_DATA_CHAIN_A_POS; + energy_b = (rssi & RX_NO_DATA_CHAIN_B_MSK) >> RX_NO_DATA_CHAIN_B_POS; + channel = (rssi & RX_NO_DATA_CHANNEL_MSK) >> RX_NO_DATA_CHANNEL_POS; + + /* Dont use dev_alloc_skb(), we'll have enough headroom once + * ieee80211_hdr pulled. + */ + skb = alloc_skb(128, GFP_ATOMIC); + if (!skb) { + IWL_ERR(mvm, "alloc_skb failed\n"); + return; + } + + rx_status = IEEE80211_SKB_RXCB(skb); + + /* 0-length PSDU */ + rx_status->flag |= RX_FLAG_NO_PSDU; + + switch (info_type) { + case RX_NO_DATA_INFO_TYPE_NDP: + rx_status->zero_length_psdu_type = + IEEE80211_RADIOTAP_ZERO_LEN_PSDU_SOUNDING; + break; + case RX_NO_DATA_INFO_TYPE_MU_UNMATCHED: + case RX_NO_DATA_INFO_TYPE_HE_TB_UNMATCHED: + rx_status->zero_length_psdu_type = + IEEE80211_RADIOTAP_ZERO_LEN_PSDU_NOT_CAPTURED; + break; + default: + rx_status->zero_length_psdu_type = + IEEE80211_RADIOTAP_ZERO_LEN_PSDU_VENDOR; + break; + } + + /* This may be overridden by iwl_mvm_rx_he() to HE_RU */ + switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { + case RATE_MCS_CHAN_WIDTH_20: + break; + case RATE_MCS_CHAN_WIDTH_40: + rx_status->bw = RATE_INFO_BW_40; + break; + case RATE_MCS_CHAN_WIDTH_80: + rx_status->bw = RATE_INFO_BW_80; + break; + case RATE_MCS_CHAN_WIDTH_160: + rx_status->bw = RATE_INFO_BW_160; + break; + } + + if (format == RATE_MCS_HE_MSK) + iwl_mvm_rx_he(mvm, skb, &phy_data, rate_n_flags, + phy_info, queue); + + iwl_mvm_decode_lsig(skb, &phy_data); + + rx_status->device_timestamp = gp2_on_air_rise; + rx_status->band = channel > 14 ? NL80211_BAND_5GHZ : + NL80211_BAND_2GHZ; + rx_status->freq = ieee80211_channel_to_frequency(channel, + rx_status->band); + iwl_mvm_get_signal_strength(mvm, rx_status, rate_n_flags, energy_a, + energy_b); + + rcu_read_lock(); + + is_sgi = format == RATE_MCS_HE_MSK ? + iwl_he_is_sgi(rate_n_flags) : + rate_n_flags & RATE_MCS_SGI_MSK; + + if (!(format == RATE_MCS_CCK_MSK) && is_sgi) + rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; + if (rate_n_flags & RATE_MCS_LDPC_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_LDPC; + if (format == RATE_MCS_HT_MSK) { + u8 stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >> + RATE_MCS_STBC_POS; + rx_status->encoding = RX_ENC_HT; + rx_status->rate_idx = RATE_HT_MCS_INDEX(rate_n_flags); + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + } else if (format == RATE_MCS_VHT_MSK) { + u8 stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >> + RATE_MCS_STBC_POS; + rx_status->rate_idx = rate_n_flags & RATE_MCS_CODE_MSK; + rx_status->encoding = RX_ENC_VHT; + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + if (rate_n_flags & RATE_MCS_BF_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_BF; + /* + * take the nss from the rx_vec since the rate_n_flags has + * only 2 bits for the nss which gives a max of 4 ss but + * there may be up to 8 spatial streams + */ + rx_status->nss = + le32_get_bits(desc->rx_vec[0], + RX_NO_DATA_RX_VEC0_VHT_NSTS_MSK) + 1; + } else if (format == RATE_MCS_HE_MSK) { + rx_status->nss = + le32_get_bits(desc->rx_vec[0], + RX_NO_DATA_RX_VEC0_HE_NSTS_MSK) + 1; + } else { + int rate = iwl_mvm_legacy_hw_idx_to_mac80211_idx(rate_n_flags, + rx_status->band); + + if (WARN(rate < 0 || rate > 0xFF, + "Invalid rate flags 0x%x, band %d,\n", + rate_n_flags, rx_status->band)) { + kfree_skb(skb); + goto out; + } + rx_status->rate_idx = rate; + } + + ieee80211_rx_napi(mvm->hw, sta, skb, napi); +out: + rcu_read_unlock(); +} + +void iwl_mvm_rx_frame_release(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_frame_release *release = (void *)pkt->data; + + if (unlikely(iwl_rx_packet_payload_len(pkt) < sizeof(*release))) + return; + + iwl_mvm_release_frames_from_notif(mvm, napi, release->baid, + le16_to_cpu(release->nssn), + queue, 0); +} + +void iwl_mvm_rx_bar_frame_release(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_bar_frame_release *release = (void *)pkt->data; + unsigned int baid = le32_get_bits(release->ba_info, + IWL_BAR_FRAME_RELEASE_BAID_MASK); + unsigned int nssn = le32_get_bits(release->ba_info, + IWL_BAR_FRAME_RELEASE_NSSN_MASK); + unsigned int sta_id = le32_get_bits(release->sta_tid, + IWL_BAR_FRAME_RELEASE_STA_MASK); + unsigned int tid = le32_get_bits(release->sta_tid, + IWL_BAR_FRAME_RELEASE_TID_MASK); + struct iwl_mvm_baid_data *baid_data; + + if (unlikely(iwl_rx_packet_payload_len(pkt) < sizeof(*release))) + return; + + if (WARN_ON_ONCE(baid == IWL_RX_REORDER_DATA_INVALID_BAID || + baid >= ARRAY_SIZE(mvm->baid_map))) + return; + + rcu_read_lock(); + baid_data = rcu_dereference(mvm->baid_map[baid]); + if (!baid_data) { + IWL_DEBUG_RX(mvm, + "Got valid BAID %d but not allocated, invalid BAR release!\n", + baid); + goto out; + } + + if (WARN(tid != baid_data->tid || sta_id != baid_data->sta_id, + "baid 0x%x is mapped to sta:%d tid:%d, but BAR release received for sta:%d tid:%d\n", + baid, baid_data->sta_id, baid_data->tid, sta_id, + tid)) + goto out; + + iwl_mvm_release_frames_from_notif(mvm, napi, baid, nssn, queue, 0); +out: + rcu_read_unlock(); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/scan.c b/sys/contrib/dev/iwlwifi/mvm/scan.c new file mode 100644 index 00000000000..3007db40d99 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/scan.c @@ -0,0 +1,3107 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include +#include + +#include "mvm.h" +#include "fw/api/scan.h" +#include "iwl-io.h" + +#define IWL_DENSE_EBS_SCAN_RATIO 5 +#define IWL_SPARSE_EBS_SCAN_RATIO 1 + +#define IWL_SCAN_DWELL_ACTIVE 10 +#define IWL_SCAN_DWELL_PASSIVE 110 +#define IWL_SCAN_DWELL_FRAGMENTED 44 +#define IWL_SCAN_DWELL_EXTENDED 90 +#define IWL_SCAN_NUM_OF_FRAGS 3 +#define IWL_SCAN_LAST_2_4_CHN 14 + +/* adaptive dwell max budget time [TU] for full scan */ +#define IWL_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN 300 +/* adaptive dwell max budget time [TU] for directed scan */ +#define IWL_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN 100 +/* adaptive dwell default high band APs number */ +#define IWL_SCAN_ADWELL_DEFAULT_HB_N_APS 8 +/* adaptive dwell default low band APs number */ +#define IWL_SCAN_ADWELL_DEFAULT_LB_N_APS 2 +/* adaptive dwell default APs number in social channels (1, 6, 11) */ +#define IWL_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL 10 +/* number of scan channels */ +#define IWL_SCAN_NUM_CHANNELS 112 +/* adaptive dwell number of APs override mask for p2p friendly GO */ +#define IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY_BIT BIT(20) +/* adaptive dwell number of APs override mask for social channels */ +#define IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS_BIT BIT(21) +/* adaptive dwell number of APs override for p2p friendly GO channels */ +#define IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY 10 +/* adaptive dwell number of APs override for social channels */ +#define IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS 2 + +/* minimal number of 2GHz and 5GHz channels in the regular scan request */ +#define IWL_MVM_6GHZ_PASSIVE_SCAN_MIN_CHANS 4 + +struct iwl_mvm_scan_timing_params { + u32 suspend_time; + u32 max_out_time; +}; + +static struct iwl_mvm_scan_timing_params scan_timing[] = { + [IWL_SCAN_TYPE_UNASSOC] = { + .suspend_time = 0, + .max_out_time = 0, + }, + [IWL_SCAN_TYPE_WILD] = { + .suspend_time = 30, + .max_out_time = 120, + }, + [IWL_SCAN_TYPE_MILD] = { + .suspend_time = 120, + .max_out_time = 120, + }, + [IWL_SCAN_TYPE_FRAGMENTED] = { + .suspend_time = 95, + .max_out_time = 44, + }, + [IWL_SCAN_TYPE_FAST_BALANCE] = { + .suspend_time = 30, + .max_out_time = 37, + }, +}; + +struct iwl_mvm_scan_params { + /* For CDB this is low band scan type, for non-CDB - type. */ + enum iwl_mvm_scan_type type; + enum iwl_mvm_scan_type hb_type; + u32 n_channels; + u16 delay; + int n_ssids; + struct cfg80211_ssid *ssids; + struct ieee80211_channel **channels; + u32 flags; + u8 *mac_addr; + u8 *mac_addr_mask; + bool no_cck; + bool pass_all; + int n_match_sets; + struct iwl_scan_probe_req preq; + struct cfg80211_match_set *match_sets; + int n_scan_plans; + struct cfg80211_sched_scan_plan *scan_plans; + bool iter_notif; + struct cfg80211_scan_6ghz_params *scan_6ghz_params; + u32 n_6ghz_params; + bool scan_6ghz; + bool enable_6ghz_passive; +}; + +static inline void *iwl_mvm_get_scan_req_umac_data(struct iwl_mvm *mvm) +{ + struct iwl_scan_req_umac *cmd = mvm->scan_cmd; + + if (iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) + return (void *)&cmd->v8.data; + + if (iwl_mvm_is_adaptive_dwell_supported(mvm)) + return (void *)&cmd->v7.data; + + if (iwl_mvm_cdb_scan_api(mvm)) + return (void *)&cmd->v6.data; + + return (void *)&cmd->v1.data; +} + +static inline struct iwl_scan_umac_chan_param * +iwl_mvm_get_scan_req_umac_channel(struct iwl_mvm *mvm) +{ + struct iwl_scan_req_umac *cmd = mvm->scan_cmd; + + if (iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) + return &cmd->v8.channel; + + if (iwl_mvm_is_adaptive_dwell_supported(mvm)) + return &cmd->v7.channel; + + if (iwl_mvm_cdb_scan_api(mvm)) + return &cmd->v6.channel; + + return &cmd->v1.channel; +} + +static u8 iwl_mvm_scan_rx_ant(struct iwl_mvm *mvm) +{ + if (mvm->scan_rx_ant != ANT_NONE) + return mvm->scan_rx_ant; + return iwl_mvm_get_valid_rx_ant(mvm); +} + +static inline __le16 iwl_mvm_scan_rx_chain(struct iwl_mvm *mvm) +{ + u16 rx_chain; + u8 rx_ant; + + rx_ant = iwl_mvm_scan_rx_ant(mvm); + rx_chain = rx_ant << PHY_RX_CHAIN_VALID_POS; + rx_chain |= rx_ant << PHY_RX_CHAIN_FORCE_MIMO_SEL_POS; + rx_chain |= rx_ant << PHY_RX_CHAIN_FORCE_SEL_POS; + rx_chain |= 0x1 << PHY_RX_CHAIN_DRIVER_FORCE_POS; + return cpu_to_le16(rx_chain); +} + +static inline __le32 +iwl_mvm_scan_rate_n_flags(struct iwl_mvm *mvm, enum nl80211_band band, + bool no_cck) +{ + u32 tx_ant; + + iwl_mvm_toggle_tx_ant(mvm, &mvm->scan_last_antenna_idx); + tx_ant = BIT(mvm->scan_last_antenna_idx) << RATE_MCS_ANT_POS; + + if (band == NL80211_BAND_2GHZ && !no_cck) + return cpu_to_le32(IWL_RATE_1M_PLCP | RATE_MCS_CCK_MSK_V1 | + tx_ant); + else + return cpu_to_le32(IWL_RATE_6M_PLCP | tx_ant); +} + +static void iwl_mvm_scan_condition_iterator(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int *global_cnt = data; + + if (vif->type != NL80211_IFTYPE_P2P_DEVICE && mvmvif->phy_ctxt && + mvmvif->phy_ctxt->id < NUM_PHY_CTX) + *global_cnt += 1; +} + +static enum iwl_mvm_traffic_load iwl_mvm_get_traffic_load(struct iwl_mvm *mvm) +{ + return mvm->tcm.result.global_load; +} + +static enum iwl_mvm_traffic_load +iwl_mvm_get_traffic_load_band(struct iwl_mvm *mvm, enum nl80211_band band) +{ + return mvm->tcm.result.band_load[band]; +} + +struct iwl_is_dcm_with_go_iterator_data { + struct ieee80211_vif *current_vif; + bool is_dcm_with_p2p_go; +}; + +static void iwl_mvm_is_dcm_with_go_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_is_dcm_with_go_iterator_data *data = _data; + struct iwl_mvm_vif *other_mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_vif *curr_mvmvif = + iwl_mvm_vif_from_mac80211(data->current_vif); + + /* exclude the given vif */ + if (vif == data->current_vif) + return; + + if (vif->type == NL80211_IFTYPE_AP && vif->p2p && + other_mvmvif->phy_ctxt && curr_mvmvif->phy_ctxt && + other_mvmvif->phy_ctxt->id != curr_mvmvif->phy_ctxt->id) + data->is_dcm_with_p2p_go = true; +} + +static enum +iwl_mvm_scan_type _iwl_mvm_get_scan_type(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum iwl_mvm_traffic_load load, + bool low_latency) +{ + int global_cnt = 0; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_scan_condition_iterator, + &global_cnt); + if (!global_cnt) + return IWL_SCAN_TYPE_UNASSOC; + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FRAGMENTED_SCAN)) { + if ((load == IWL_MVM_TRAFFIC_HIGH || low_latency) && + (!vif || vif->type != NL80211_IFTYPE_P2P_DEVICE)) + return IWL_SCAN_TYPE_FRAGMENTED; + + /* in case of DCM with GO where BSS DTIM interval < 220msec + * set all scan requests as fast-balance scan + * */ + if (vif && vif->type == NL80211_IFTYPE_STATION && + vif->bss_conf.dtim_period < 220) { + struct iwl_is_dcm_with_go_iterator_data data = { + .current_vif = vif, + .is_dcm_with_p2p_go = false, + }; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_is_dcm_with_go_iterator, + &data); + if (data.is_dcm_with_p2p_go) + return IWL_SCAN_TYPE_FAST_BALANCE; + } + } + + if (load >= IWL_MVM_TRAFFIC_MEDIUM || low_latency) + return IWL_SCAN_TYPE_MILD; + + return IWL_SCAN_TYPE_WILD; +} + +static enum +iwl_mvm_scan_type iwl_mvm_get_scan_type(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + enum iwl_mvm_traffic_load load; + bool low_latency; + + load = iwl_mvm_get_traffic_load(mvm); + low_latency = iwl_mvm_low_latency(mvm); + + return _iwl_mvm_get_scan_type(mvm, vif, load, low_latency); +} + +static enum +iwl_mvm_scan_type iwl_mvm_get_scan_type_band(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum nl80211_band band) +{ + enum iwl_mvm_traffic_load load; + bool low_latency; + + load = iwl_mvm_get_traffic_load_band(mvm, band); + low_latency = iwl_mvm_low_latency_band(mvm, band); + + return _iwl_mvm_get_scan_type(mvm, vif, load, low_latency); +} + +static inline bool iwl_mvm_rrm_scan_needed(struct iwl_mvm *mvm) +{ + /* require rrm scan whenever the fw supports it */ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT); +} + +static int iwl_mvm_max_scan_ie_fw_cmd_room(struct iwl_mvm *mvm) +{ + int max_probe_len; + + max_probe_len = SCAN_OFFLOAD_PROBE_REQ_SIZE; + + /* we create the 802.11 header and SSID element */ + max_probe_len -= 24 + 2; + + /* DS parameter set element is added on 2.4GHZ band if required */ + if (iwl_mvm_rrm_scan_needed(mvm)) + max_probe_len -= 3; + + return max_probe_len; +} + +int iwl_mvm_max_scan_ie_len(struct iwl_mvm *mvm) +{ + int max_ie_len = iwl_mvm_max_scan_ie_fw_cmd_room(mvm); + + /* TODO: [BUG] This function should return the maximum allowed size of + * scan IEs, however the LMAC scan api contains both 2GHZ and 5GHZ IEs + * in the same command. So the correct implementation of this function + * is just iwl_mvm_max_scan_ie_fw_cmd_room() / 2. Currently the scan + * command has only 512 bytes and it would leave us with about 240 + * bytes for scan IEs, which is clearly not enough. So meanwhile + * we will report an incorrect value. This may result in a failure to + * issue a scan in unified_scan_lmac and unified_sched_scan_lmac + * functions with -ENOBUFS, if a large enough probe will be provided. + */ + return max_ie_len; +} + +void iwl_mvm_rx_lmac_scan_iter_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_lmac_scan_complete_notif *notif = (void *)pkt->data; + + IWL_DEBUG_SCAN(mvm, + "Scan offload iteration complete: status=0x%x scanned channels=%d\n", + notif->status, notif->scanned_channels); + + if (mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_FOUND) { + IWL_DEBUG_SCAN(mvm, "Pass all scheduled scan results found\n"); + ieee80211_sched_scan_results(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_ENABLED; + } +} + +void iwl_mvm_rx_scan_match_found(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + IWL_DEBUG_SCAN(mvm, "Scheduled scan results\n"); + ieee80211_sched_scan_results(mvm->hw); +} + +static const char *iwl_mvm_ebs_status_str(enum iwl_scan_ebs_status status) +{ + switch (status) { + case IWL_SCAN_EBS_SUCCESS: + return "successful"; + case IWL_SCAN_EBS_INACTIVE: + return "inactive"; + case IWL_SCAN_EBS_FAILED: + case IWL_SCAN_EBS_CHAN_NOT_FOUND: + default: + return "failed"; + } +} + +void iwl_mvm_rx_lmac_scan_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_periodic_scan_complete *scan_notif = (void *)pkt->data; + bool aborted = (scan_notif->status == IWL_SCAN_OFFLOAD_ABORTED); + + /* If this happens, the firmware has mistakenly sent an LMAC + * notification during UMAC scans -- warn and ignore it. + */ + if (WARN_ON_ONCE(fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_UMAC_SCAN))) + return; + + /* scan status must be locked for proper checking */ + lockdep_assert_held(&mvm->mutex); + + /* We first check if we were stopping a scan, in which case we + * just clear the stopping flag. Then we check if it was a + * firmware initiated stop, in which case we need to inform + * mac80211. + * Note that we can have a stopping and a running scan + * simultaneously, but we can't have two different types of + * scans stopping or running at the same time (since LMAC + * doesn't support it). + */ + + if (mvm->scan_status & IWL_MVM_SCAN_STOPPING_SCHED) { + WARN_ON_ONCE(mvm->scan_status & IWL_MVM_SCAN_STOPPING_REGULAR); + + IWL_DEBUG_SCAN(mvm, "Scheduled scan %s, EBS status %s\n", + aborted ? "aborted" : "completed", + iwl_mvm_ebs_status_str(scan_notif->ebs_status)); + IWL_DEBUG_SCAN(mvm, + "Last line %d, Last iteration %d, Time after last iteration %d\n", + scan_notif->last_schedule_line, + scan_notif->last_schedule_iteration, + __le32_to_cpu(scan_notif->time_after_last_iter)); + + mvm->scan_status &= ~IWL_MVM_SCAN_STOPPING_SCHED; + } else if (mvm->scan_status & IWL_MVM_SCAN_STOPPING_REGULAR) { + IWL_DEBUG_SCAN(mvm, "Regular scan %s, EBS status %s\n", + aborted ? "aborted" : "completed", + iwl_mvm_ebs_status_str(scan_notif->ebs_status)); + + mvm->scan_status &= ~IWL_MVM_SCAN_STOPPING_REGULAR; + } else if (mvm->scan_status & IWL_MVM_SCAN_SCHED) { + WARN_ON_ONCE(mvm->scan_status & IWL_MVM_SCAN_REGULAR); + + IWL_DEBUG_SCAN(mvm, "Scheduled scan %s, EBS status %s\n", + aborted ? "aborted" : "completed", + iwl_mvm_ebs_status_str(scan_notif->ebs_status)); + IWL_DEBUG_SCAN(mvm, + "Last line %d, Last iteration %d, Time after last iteration %d (FW)\n", + scan_notif->last_schedule_line, + scan_notif->last_schedule_iteration, + __le32_to_cpu(scan_notif->time_after_last_iter)); + + mvm->scan_status &= ~IWL_MVM_SCAN_SCHED; + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } else if (mvm->scan_status & IWL_MVM_SCAN_REGULAR) { + struct cfg80211_scan_info info = { + .aborted = aborted, + }; + + IWL_DEBUG_SCAN(mvm, "Regular scan %s, EBS status %s (FW)\n", + aborted ? "aborted" : "completed", + iwl_mvm_ebs_status_str(scan_notif->ebs_status)); + + mvm->scan_status &= ~IWL_MVM_SCAN_REGULAR; + ieee80211_scan_completed(mvm->hw, &info); + cancel_delayed_work(&mvm->scan_timeout_dwork); + iwl_mvm_resume_tcm(mvm); + } else { + IWL_ERR(mvm, + "got scan complete notification but no scan is running\n"); + } + + mvm->last_ebs_successful = + scan_notif->ebs_status == IWL_SCAN_EBS_SUCCESS || + scan_notif->ebs_status == IWL_SCAN_EBS_INACTIVE; +} + +static int iwl_ssid_exist(u8 *ssid, u8 ssid_len, struct iwl_ssid_ie *ssid_list) +{ + int i; + + for (i = 0; i < PROBE_OPTION_MAX; i++) { + if (!ssid_list[i].len) + break; + if (ssid_list[i].len == ssid_len && + !memcmp(ssid_list->ssid, ssid, ssid_len)) + return i; + } + return -1; +} + +/* We insert the SSIDs in an inverted order, because the FW will + * invert it back. + */ +static void iwl_scan_build_ssids(struct iwl_mvm_scan_params *params, + struct iwl_ssid_ie *ssids, + u32 *ssid_bitmap) +{ + int i, j; + int index; + u32 tmp_bitmap = 0; + + /* + * copy SSIDs from match list. + * iwl_config_sched_scan_profiles() uses the order of these ssids to + * config match list. + */ + for (i = 0, j = params->n_match_sets - 1; + j >= 0 && i < PROBE_OPTION_MAX; + i++, j--) { + /* skip empty SSID matchsets */ + if (!params->match_sets[j].ssid.ssid_len) + continue; + ssids[i].id = WLAN_EID_SSID; + ssids[i].len = params->match_sets[j].ssid.ssid_len; + memcpy(ssids[i].ssid, params->match_sets[j].ssid.ssid, + ssids[i].len); + } + + /* add SSIDs from scan SSID list */ + for (j = params->n_ssids - 1; + j >= 0 && i < PROBE_OPTION_MAX; + i++, j--) { + index = iwl_ssid_exist(params->ssids[j].ssid, + params->ssids[j].ssid_len, + ssids); + if (index < 0) { + ssids[i].id = WLAN_EID_SSID; + ssids[i].len = params->ssids[j].ssid_len; + memcpy(ssids[i].ssid, params->ssids[j].ssid, + ssids[i].len); + tmp_bitmap |= BIT(i); + } else { + tmp_bitmap |= BIT(index); + } + } + if (ssid_bitmap) + *ssid_bitmap = tmp_bitmap; +} + +static int +iwl_mvm_config_sched_scan_profiles(struct iwl_mvm *mvm, + struct cfg80211_sched_scan_request *req) +{ + struct iwl_scan_offload_profile *profile; + struct iwl_scan_offload_profile_cfg_v1 *profile_cfg_v1; + struct iwl_scan_offload_blocklist *blocklist; + struct iwl_scan_offload_profile_cfg_data *data; + int max_profiles = iwl_umac_scan_get_max_profiles(mvm->fw); + int profile_cfg_size = sizeof(*data) + + sizeof(*profile) * max_profiles; + struct iwl_host_cmd cmd = { + .id = SCAN_OFFLOAD_UPDATE_PROFILES_CMD, + .len[1] = profile_cfg_size, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + .dataflags[1] = IWL_HCMD_DFL_NOCOPY, + }; + int blocklist_len; + int i; + int ret; + + if (WARN_ON(req->n_match_sets > max_profiles)) + return -EIO; + + if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_SHORT_BL) + blocklist_len = IWL_SCAN_SHORT_BLACKLIST_LEN; + else + blocklist_len = IWL_SCAN_MAX_BLACKLIST_LEN; + + blocklist = kcalloc(blocklist_len, sizeof(*blocklist), GFP_KERNEL); + if (!blocklist) + return -ENOMEM; + + profile_cfg_v1 = kzalloc(profile_cfg_size, GFP_KERNEL); + if (!profile_cfg_v1) { + ret = -ENOMEM; + goto free_blocklist; + } + + cmd.data[0] = blocklist; + cmd.len[0] = sizeof(*blocklist) * blocklist_len; + cmd.data[1] = profile_cfg_v1; + + /* if max_profile is MAX_PROFILES_V2, we have the new API */ + if (max_profiles == IWL_SCAN_MAX_PROFILES_V2) { + struct iwl_scan_offload_profile_cfg *profile_cfg = + (struct iwl_scan_offload_profile_cfg *)profile_cfg_v1; + + data = &profile_cfg->data; + } else { + data = &profile_cfg_v1->data; + } + + /* No blocklist configuration */ + data->num_profiles = req->n_match_sets; + data->active_clients = SCAN_CLIENT_SCHED_SCAN; + data->pass_match = SCAN_CLIENT_SCHED_SCAN; + data->match_notify = SCAN_CLIENT_SCHED_SCAN; + + if (!req->n_match_sets || !req->match_sets[0].ssid.ssid_len) + data->any_beacon_notify = SCAN_CLIENT_SCHED_SCAN; + + for (i = 0; i < req->n_match_sets; i++) { + profile = &profile_cfg_v1->profiles[i]; + profile->ssid_index = i; + /* Support any cipher and auth algorithm */ + profile->unicast_cipher = 0xff; + profile->auth_alg = 0xff; + profile->network_type = IWL_NETWORK_TYPE_ANY; + profile->band_selection = IWL_SCAN_OFFLOAD_SELECT_ANY; + profile->client_bitmap = SCAN_CLIENT_SCHED_SCAN; + } + + IWL_DEBUG_SCAN(mvm, "Sending scheduled scan profile config\n"); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + kfree(profile_cfg_v1); +free_blocklist: + kfree(blocklist); + + return ret; +} + +static bool iwl_mvm_scan_pass_all(struct iwl_mvm *mvm, + struct cfg80211_sched_scan_request *req) +{ + if (req->n_match_sets && req->match_sets[0].ssid.ssid_len) { + IWL_DEBUG_SCAN(mvm, + "Sending scheduled scan with filtering, n_match_sets %d\n", + req->n_match_sets); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + return false; + } + + IWL_DEBUG_SCAN(mvm, "Sending Scheduled scan without filtering\n"); + + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_ENABLED; + return true; +} + +static int iwl_mvm_lmac_scan_abort(struct iwl_mvm *mvm) +{ + int ret; + struct iwl_host_cmd cmd = { + .id = SCAN_OFFLOAD_ABORT_CMD, + }; + u32 status = CAN_ABORT_STATUS; + + ret = iwl_mvm_send_cmd_status(mvm, &cmd, &status); + if (ret) + return ret; + + if (status != CAN_ABORT_STATUS) { + /* + * The scan abort will return 1 for success or + * 2 for "failure". A failure condition can be + * due to simply not being in an active scan which + * can occur if we send the scan abort before the + * microcode has notified us that a scan is completed. + */ + IWL_DEBUG_SCAN(mvm, "SCAN OFFLOAD ABORT ret %d.\n", status); + ret = -ENOENT; + } + + return ret; +} + +static void iwl_mvm_scan_fill_tx_cmd(struct iwl_mvm *mvm, + struct iwl_scan_req_tx_cmd *tx_cmd, + bool no_cck) +{ + tx_cmd[0].tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL | + TX_CMD_FLG_BT_DIS); + tx_cmd[0].rate_n_flags = iwl_mvm_scan_rate_n_flags(mvm, + NL80211_BAND_2GHZ, + no_cck); + + if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + ADD_STA, + 0) < 12) { + tx_cmd[0].sta_id = mvm->aux_sta.sta_id; + tx_cmd[1].sta_id = mvm->aux_sta.sta_id; + + /* + * Fw doesn't use this sta anymore, pending deprecation via HOST API + * change + */ + } else { + tx_cmd[0].sta_id = 0xff; + tx_cmd[1].sta_id = 0xff; + } + + tx_cmd[1].tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL | + TX_CMD_FLG_BT_DIS); + + tx_cmd[1].rate_n_flags = iwl_mvm_scan_rate_n_flags(mvm, + NL80211_BAND_5GHZ, + no_cck); +} + +static void +iwl_mvm_lmac_scan_cfg_channels(struct iwl_mvm *mvm, + struct ieee80211_channel **channels, + int n_channels, u32 ssid_bitmap, + struct iwl_scan_req_lmac *cmd) +{ + struct iwl_scan_channel_cfg_lmac *channel_cfg = (void *)&cmd->data; + int i; + + for (i = 0; i < n_channels; i++) { + channel_cfg[i].channel_num = + cpu_to_le16(channels[i]->hw_value); + channel_cfg[i].iter_count = cpu_to_le16(1); + channel_cfg[i].iter_interval = 0; + channel_cfg[i].flags = + cpu_to_le32(IWL_UNIFIED_SCAN_CHANNEL_PARTIAL | + ssid_bitmap); + } +} + +static u8 *iwl_mvm_copy_and_insert_ds_elem(struct iwl_mvm *mvm, const u8 *ies, + size_t len, u8 *const pos) +{ + static const u8 before_ds_params[] = { + WLAN_EID_SSID, + WLAN_EID_SUPP_RATES, + WLAN_EID_REQUEST, + WLAN_EID_EXT_SUPP_RATES, + }; + size_t offs; + u8 *newpos = pos; + + if (!iwl_mvm_rrm_scan_needed(mvm)) { + memcpy(newpos, ies, len); + return newpos + len; + } + + offs = ieee80211_ie_split(ies, len, + before_ds_params, + ARRAY_SIZE(before_ds_params), + 0); + + memcpy(newpos, ies, offs); + newpos += offs; + + /* Add a placeholder for DS Parameter Set element */ + *newpos++ = WLAN_EID_DS_PARAMS; + *newpos++ = 1; + *newpos++ = 0; + + memcpy(newpos, ies + offs, len - offs); + newpos += len - offs; + + return newpos; +} + +#define WFA_TPC_IE_LEN 9 + +static void iwl_mvm_add_tpc_report_ie(u8 *pos) +{ + pos[0] = WLAN_EID_VENDOR_SPECIFIC; + pos[1] = WFA_TPC_IE_LEN - 2; + pos[2] = (WLAN_OUI_MICROSOFT >> 16) & 0xff; + pos[3] = (WLAN_OUI_MICROSOFT >> 8) & 0xff; + pos[4] = WLAN_OUI_MICROSOFT & 0xff; + pos[5] = WLAN_OUI_TYPE_MICROSOFT_TPC; + pos[6] = 0; + /* pos[7] - tx power will be inserted by the FW */ + pos[7] = 0; + pos[8] = 0; +} + +static void +iwl_mvm_build_scan_probe(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_scan_ies *ies, + struct iwl_mvm_scan_params *params) +{ + struct ieee80211_mgmt *frame = (void *)params->preq.buf; + u8 *pos, *newpos; + const u8 *mac_addr = params->flags & NL80211_SCAN_FLAG_RANDOM_ADDR ? + params->mac_addr : NULL; + + /* + * Unfortunately, right now the offload scan doesn't support randomising + * within the firmware, so until the firmware API is ready we implement + * it in the driver. This means that the scan iterations won't really be + * random, only when it's restarted, but at least that helps a bit. + */ + if (mac_addr) + get_random_mask_addr(frame->sa, mac_addr, + params->mac_addr_mask); + else + memcpy(frame->sa, vif->addr, ETH_ALEN); + + frame->frame_control = cpu_to_le16(IEEE80211_STYPE_PROBE_REQ); + eth_broadcast_addr(frame->da); + eth_broadcast_addr(frame->bssid); + frame->seq_ctrl = 0; + + pos = frame->u.probe_req.variable; + *pos++ = WLAN_EID_SSID; + *pos++ = 0; + + params->preq.mac_header.offset = 0; + params->preq.mac_header.len = cpu_to_le16(24 + 2); + + /* Insert ds parameter set element on 2.4 GHz band */ + newpos = iwl_mvm_copy_and_insert_ds_elem(mvm, + ies->ies[NL80211_BAND_2GHZ], + ies->len[NL80211_BAND_2GHZ], + pos); + params->preq.band_data[0].offset = cpu_to_le16(pos - params->preq.buf); + params->preq.band_data[0].len = cpu_to_le16(newpos - pos); + pos = newpos; + + memcpy(pos, ies->ies[NL80211_BAND_5GHZ], + ies->len[NL80211_BAND_5GHZ]); + params->preq.band_data[1].offset = cpu_to_le16(pos - params->preq.buf); + params->preq.band_data[1].len = + cpu_to_le16(ies->len[NL80211_BAND_5GHZ]); + pos += ies->len[NL80211_BAND_5GHZ]; + + memcpy(pos, ies->ies[NL80211_BAND_6GHZ], + ies->len[NL80211_BAND_6GHZ]); + params->preq.band_data[2].offset = cpu_to_le16(pos - params->preq.buf); + params->preq.band_data[2].len = + cpu_to_le16(ies->len[NL80211_BAND_6GHZ]); + pos += ies->len[NL80211_BAND_6GHZ]; + memcpy(pos, ies->common_ies, ies->common_ie_len); + params->preq.common_data.offset = cpu_to_le16(pos - params->preq.buf); + + if (iwl_mvm_rrm_scan_needed(mvm) && + !fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT)) { + iwl_mvm_add_tpc_report_ie(pos + ies->common_ie_len); + params->preq.common_data.len = cpu_to_le16(ies->common_ie_len + + WFA_TPC_IE_LEN); + } else { + params->preq.common_data.len = cpu_to_le16(ies->common_ie_len); + } +} + +static void iwl_mvm_scan_lmac_dwell(struct iwl_mvm *mvm, + struct iwl_scan_req_lmac *cmd, + struct iwl_mvm_scan_params *params) +{ + cmd->active_dwell = IWL_SCAN_DWELL_ACTIVE; + cmd->passive_dwell = IWL_SCAN_DWELL_PASSIVE; + cmd->fragmented_dwell = IWL_SCAN_DWELL_FRAGMENTED; + cmd->extended_dwell = IWL_SCAN_DWELL_EXTENDED; + cmd->max_out_time = cpu_to_le32(scan_timing[params->type].max_out_time); + cmd->suspend_time = cpu_to_le32(scan_timing[params->type].suspend_time); + cmd->scan_prio = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); +} + +static inline bool iwl_mvm_scan_fits(struct iwl_mvm *mvm, int n_ssids, + struct ieee80211_scan_ies *ies, + int n_channels) +{ + return ((n_ssids <= PROBE_OPTION_MAX) && + (n_channels <= mvm->fw->ucode_capa.n_scan_channels) & + (ies->common_ie_len + + ies->len[NL80211_BAND_2GHZ] + + ies->len[NL80211_BAND_5GHZ] <= + iwl_mvm_max_scan_ie_fw_cmd_room(mvm))); +} + +static inline bool iwl_mvm_scan_use_ebs(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + const struct iwl_ucode_capabilities *capa = &mvm->fw->ucode_capa; + bool low_latency; + + if (iwl_mvm_is_cdb_supported(mvm)) + low_latency = iwl_mvm_low_latency_band(mvm, NL80211_BAND_5GHZ); + else + low_latency = iwl_mvm_low_latency(mvm); + + /* We can only use EBS if: + * 1. the feature is supported; + * 2. the last EBS was successful; + * 3. if only single scan, the single scan EBS API is supported; + * 4. it's not a p2p find operation. + * 5. we are not in low latency mode, + * or if fragmented ebs is supported by the FW + */ + return ((capa->flags & IWL_UCODE_TLV_FLAGS_EBS_SUPPORT) && + mvm->last_ebs_successful && IWL_MVM_ENABLE_EBS && + vif->type != NL80211_IFTYPE_P2P_DEVICE && + (!low_latency || iwl_mvm_is_frag_ebs_supported(mvm))); +} + +static inline bool iwl_mvm_is_regular_scan(struct iwl_mvm_scan_params *params) +{ + return params->n_scan_plans == 1 && + params->scan_plans[0].iterations == 1; +} + +static bool iwl_mvm_is_scan_fragmented(enum iwl_mvm_scan_type type) +{ + return (type == IWL_SCAN_TYPE_FRAGMENTED || + type == IWL_SCAN_TYPE_FAST_BALANCE); +} + +static int iwl_mvm_scan_lmac_flags(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + int flags = 0; + + if (params->n_ssids == 0) + flags |= IWL_MVM_LMAC_SCAN_FLAG_PASSIVE; + + if (params->n_ssids == 1 && params->ssids[0].ssid_len != 0) + flags |= IWL_MVM_LMAC_SCAN_FLAG_PRE_CONNECTION; + + if (iwl_mvm_is_scan_fragmented(params->type)) + flags |= IWL_MVM_LMAC_SCAN_FLAG_FRAGMENTED; + + if (iwl_mvm_rrm_scan_needed(mvm) && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT)) + flags |= IWL_MVM_LMAC_SCAN_FLAGS_RRM_ENABLED; + + if (params->pass_all) + flags |= IWL_MVM_LMAC_SCAN_FLAG_PASS_ALL; + else + flags |= IWL_MVM_LMAC_SCAN_FLAG_MATCH; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvm->scan_iter_notif_enabled) + flags |= IWL_MVM_LMAC_SCAN_FLAG_ITER_COMPLETE; +#endif + + if (mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_ENABLED) + flags |= IWL_MVM_LMAC_SCAN_FLAG_ITER_COMPLETE; + + if (iwl_mvm_is_regular_scan(params) && + vif->type != NL80211_IFTYPE_P2P_DEVICE && + !iwl_mvm_is_scan_fragmented(params->type)) + flags |= IWL_MVM_LMAC_SCAN_FLAG_EXTENDED_DWELL; + + return flags; +} + +static void +iwl_mvm_scan_set_legacy_probe_req(struct iwl_scan_probe_req_v1 *p_req, + struct iwl_scan_probe_req *src_p_req) +{ + int i; + + p_req->mac_header = src_p_req->mac_header; + for (i = 0; i < SCAN_NUM_BAND_PROBE_DATA_V_1; i++) + p_req->band_data[i] = src_p_req->band_data[i]; + p_req->common_data = src_p_req->common_data; + memcpy(p_req->buf, src_p_req->buf, sizeof(p_req->buf)); +} + +static int iwl_mvm_scan_lmac(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params) +{ + struct iwl_scan_req_lmac *cmd = mvm->scan_cmd; + struct iwl_scan_probe_req_v1 *preq = + (void *)(cmd->data + sizeof(struct iwl_scan_channel_cfg_lmac) * + mvm->fw->ucode_capa.n_scan_channels); + u32 ssid_bitmap = 0; + int i; + u8 band; + + if (WARN_ON(params->n_scan_plans > IWL_MAX_SCHED_SCAN_PLANS)) + return -EINVAL; + + iwl_mvm_scan_lmac_dwell(mvm, cmd, params); + + cmd->rx_chain_select = iwl_mvm_scan_rx_chain(mvm); + cmd->iter_num = cpu_to_le32(1); + cmd->n_channels = (u8)params->n_channels; + + cmd->delay = cpu_to_le32(params->delay); + + cmd->scan_flags = cpu_to_le32(iwl_mvm_scan_lmac_flags(mvm, params, + vif)); + + band = iwl_mvm_phy_band_from_nl80211(params->channels[0]->band); + cmd->flags = cpu_to_le32(band); + cmd->filter_flags = cpu_to_le32(MAC_FILTER_ACCEPT_GRP | + MAC_FILTER_IN_BEACON); + iwl_mvm_scan_fill_tx_cmd(mvm, cmd->tx_cmd, params->no_cck); + iwl_scan_build_ssids(params, cmd->direct_scan, &ssid_bitmap); + + /* this API uses bits 1-20 instead of 0-19 */ + ssid_bitmap <<= 1; + + for (i = 0; i < params->n_scan_plans; i++) { + struct cfg80211_sched_scan_plan *scan_plan = + ¶ms->scan_plans[i]; + + cmd->schedule[i].delay = + cpu_to_le16(scan_plan->interval); + cmd->schedule[i].iterations = scan_plan->iterations; + cmd->schedule[i].full_scan_mul = 1; + } + + /* + * If the number of iterations of the last scan plan is set to + * zero, it should run infinitely. However, this is not always the case. + * For example, when regular scan is requested the driver sets one scan + * plan with one iteration. + */ + if (!cmd->schedule[i - 1].iterations) + cmd->schedule[i - 1].iterations = 0xff; + + if (iwl_mvm_scan_use_ebs(mvm, vif)) { + cmd->channel_opt[0].flags = + cpu_to_le16(IWL_SCAN_CHANNEL_FLAG_EBS | + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE | + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD); + cmd->channel_opt[0].non_ebs_ratio = + cpu_to_le16(IWL_DENSE_EBS_SCAN_RATIO); + cmd->channel_opt[1].flags = + cpu_to_le16(IWL_SCAN_CHANNEL_FLAG_EBS | + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE | + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD); + cmd->channel_opt[1].non_ebs_ratio = + cpu_to_le16(IWL_SPARSE_EBS_SCAN_RATIO); + } + + iwl_mvm_lmac_scan_cfg_channels(mvm, params->channels, + params->n_channels, ssid_bitmap, cmd); + + iwl_mvm_scan_set_legacy_probe_req(preq, ¶ms->preq); + + return 0; +} + +static int rate_to_scan_rate_flag(unsigned int rate) +{ + static const int rate_to_scan_rate[IWL_RATE_COUNT] = { + [IWL_RATE_1M_INDEX] = SCAN_CONFIG_RATE_1M, + [IWL_RATE_2M_INDEX] = SCAN_CONFIG_RATE_2M, + [IWL_RATE_5M_INDEX] = SCAN_CONFIG_RATE_5M, + [IWL_RATE_11M_INDEX] = SCAN_CONFIG_RATE_11M, + [IWL_RATE_6M_INDEX] = SCAN_CONFIG_RATE_6M, + [IWL_RATE_9M_INDEX] = SCAN_CONFIG_RATE_9M, + [IWL_RATE_12M_INDEX] = SCAN_CONFIG_RATE_12M, + [IWL_RATE_18M_INDEX] = SCAN_CONFIG_RATE_18M, + [IWL_RATE_24M_INDEX] = SCAN_CONFIG_RATE_24M, + [IWL_RATE_36M_INDEX] = SCAN_CONFIG_RATE_36M, + [IWL_RATE_48M_INDEX] = SCAN_CONFIG_RATE_48M, + [IWL_RATE_54M_INDEX] = SCAN_CONFIG_RATE_54M, + }; + + return rate_to_scan_rate[rate]; +} + +static __le32 iwl_mvm_scan_config_rates(struct iwl_mvm *mvm) +{ + struct ieee80211_supported_band *band; + unsigned int rates = 0; + int i; + + band = &mvm->nvm_data->bands[NL80211_BAND_2GHZ]; + for (i = 0; i < band->n_bitrates; i++) + rates |= rate_to_scan_rate_flag(band->bitrates[i].hw_value); + band = &mvm->nvm_data->bands[NL80211_BAND_5GHZ]; + for (i = 0; i < band->n_bitrates; i++) + rates |= rate_to_scan_rate_flag(band->bitrates[i].hw_value); + + /* Set both basic rates and supported rates */ + rates |= SCAN_CONFIG_SUPPORTED_RATE(rates); + + return cpu_to_le32(rates); +} + +static void iwl_mvm_fill_scan_dwell(struct iwl_mvm *mvm, + struct iwl_scan_dwell *dwell) +{ + dwell->active = IWL_SCAN_DWELL_ACTIVE; + dwell->passive = IWL_SCAN_DWELL_PASSIVE; + dwell->fragmented = IWL_SCAN_DWELL_FRAGMENTED; + dwell->extended = IWL_SCAN_DWELL_EXTENDED; +} + +static void iwl_mvm_fill_channels(struct iwl_mvm *mvm, u8 *channels, + u32 max_channels) +{ + struct ieee80211_supported_band *band; + int i, j = 0; + + band = &mvm->nvm_data->bands[NL80211_BAND_2GHZ]; + for (i = 0; i < band->n_channels && j < max_channels; i++, j++) + channels[j] = band->channels[i].hw_value; + band = &mvm->nvm_data->bands[NL80211_BAND_5GHZ]; + for (i = 0; i < band->n_channels && j < max_channels; i++, j++) + channels[j] = band->channels[i].hw_value; +} + +static void iwl_mvm_fill_scan_config_v1(struct iwl_mvm *mvm, void *config, + u32 flags, u8 channel_flags, + u32 max_channels) +{ + enum iwl_mvm_scan_type type = iwl_mvm_get_scan_type(mvm, NULL); + struct iwl_scan_config_v1 *cfg = config; + + cfg->flags = cpu_to_le32(flags); + cfg->tx_chains = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); + cfg->rx_chains = cpu_to_le32(iwl_mvm_scan_rx_ant(mvm)); + cfg->legacy_rates = iwl_mvm_scan_config_rates(mvm); + cfg->out_of_channel_time = cpu_to_le32(scan_timing[type].max_out_time); + cfg->suspend_time = cpu_to_le32(scan_timing[type].suspend_time); + + iwl_mvm_fill_scan_dwell(mvm, &cfg->dwell); + + memcpy(&cfg->mac_addr, &mvm->addresses[0].addr, ETH_ALEN); + + /* This function should not be called when using ADD_STA ver >=12 */ + WARN_ON_ONCE(iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + ADD_STA, 0) >= 12); + + cfg->bcast_sta_id = mvm->aux_sta.sta_id; + cfg->channel_flags = channel_flags; + + iwl_mvm_fill_channels(mvm, cfg->channel_array, max_channels); +} + +static void iwl_mvm_fill_scan_config_v2(struct iwl_mvm *mvm, void *config, + u32 flags, u8 channel_flags, + u32 max_channels) +{ + struct iwl_scan_config_v2 *cfg = config; + + cfg->flags = cpu_to_le32(flags); + cfg->tx_chains = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); + cfg->rx_chains = cpu_to_le32(iwl_mvm_scan_rx_ant(mvm)); + cfg->legacy_rates = iwl_mvm_scan_config_rates(mvm); + + if (iwl_mvm_is_cdb_supported(mvm)) { + enum iwl_mvm_scan_type lb_type, hb_type; + + lb_type = iwl_mvm_get_scan_type_band(mvm, NULL, + NL80211_BAND_2GHZ); + hb_type = iwl_mvm_get_scan_type_band(mvm, NULL, + NL80211_BAND_5GHZ); + + cfg->out_of_channel_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(scan_timing[lb_type].max_out_time); + cfg->suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(scan_timing[lb_type].suspend_time); + + cfg->out_of_channel_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(scan_timing[hb_type].max_out_time); + cfg->suspend_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(scan_timing[hb_type].suspend_time); + } else { + enum iwl_mvm_scan_type type = + iwl_mvm_get_scan_type(mvm, NULL); + + cfg->out_of_channel_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(scan_timing[type].max_out_time); + cfg->suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(scan_timing[type].suspend_time); + } + + iwl_mvm_fill_scan_dwell(mvm, &cfg->dwell); + + memcpy(&cfg->mac_addr, &mvm->addresses[0].addr, ETH_ALEN); + + /* This function should not be called when using ADD_STA ver >=12 */ + WARN_ON_ONCE(iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + ADD_STA, 0) >= 12); + + cfg->bcast_sta_id = mvm->aux_sta.sta_id; + cfg->channel_flags = channel_flags; + + iwl_mvm_fill_channels(mvm, cfg->channel_array, max_channels); +} + +static int iwl_mvm_legacy_config_scan(struct iwl_mvm *mvm) +{ + void *cfg; + int ret, cmd_size; + struct iwl_host_cmd cmd = { + .id = iwl_cmd_id(SCAN_CFG_CMD, IWL_ALWAYS_LONG_GROUP, 0), + }; + enum iwl_mvm_scan_type type; + enum iwl_mvm_scan_type hb_type = IWL_SCAN_TYPE_NOT_SET; + int num_channels = + mvm->nvm_data->bands[NL80211_BAND_2GHZ].n_channels + + mvm->nvm_data->bands[NL80211_BAND_5GHZ].n_channels; + u32 flags; + u8 channel_flags; + + if (WARN_ON(num_channels > mvm->fw->ucode_capa.n_scan_channels)) + num_channels = mvm->fw->ucode_capa.n_scan_channels; + + if (iwl_mvm_is_cdb_supported(mvm)) { + type = iwl_mvm_get_scan_type_band(mvm, NULL, + NL80211_BAND_2GHZ); + hb_type = iwl_mvm_get_scan_type_band(mvm, NULL, + NL80211_BAND_5GHZ); + if (type == mvm->scan_type && hb_type == mvm->hb_scan_type) + return 0; + } else { + type = iwl_mvm_get_scan_type(mvm, NULL); + if (type == mvm->scan_type) + return 0; + } + + if (iwl_mvm_cdb_scan_api(mvm)) + cmd_size = sizeof(struct iwl_scan_config_v2); + else + cmd_size = sizeof(struct iwl_scan_config_v1); + cmd_size += mvm->fw->ucode_capa.n_scan_channels; + + cfg = kzalloc(cmd_size, GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + flags = SCAN_CONFIG_FLAG_ACTIVATE | + SCAN_CONFIG_FLAG_ALLOW_CHUB_REQS | + SCAN_CONFIG_FLAG_SET_TX_CHAINS | + SCAN_CONFIG_FLAG_SET_RX_CHAINS | + SCAN_CONFIG_FLAG_SET_AUX_STA_ID | + SCAN_CONFIG_FLAG_SET_ALL_TIMES | + SCAN_CONFIG_FLAG_SET_LEGACY_RATES | + SCAN_CONFIG_FLAG_SET_MAC_ADDR | + SCAN_CONFIG_FLAG_SET_CHANNEL_FLAGS | + SCAN_CONFIG_N_CHANNELS(num_channels) | + (iwl_mvm_is_scan_fragmented(type) ? + SCAN_CONFIG_FLAG_SET_FRAGMENTED : + SCAN_CONFIG_FLAG_CLEAR_FRAGMENTED); + + channel_flags = IWL_CHANNEL_FLAG_EBS | + IWL_CHANNEL_FLAG_ACCURATE_EBS | + IWL_CHANNEL_FLAG_EBS_ADD | + IWL_CHANNEL_FLAG_PRE_SCAN_PASSIVE2ACTIVE; + + /* + * Check for fragmented scan on LMAC2 - high band. + * LMAC1 - low band is checked above. + */ + if (iwl_mvm_cdb_scan_api(mvm)) { + if (iwl_mvm_is_cdb_supported(mvm)) + flags |= (iwl_mvm_is_scan_fragmented(hb_type)) ? + SCAN_CONFIG_FLAG_SET_LMAC2_FRAGMENTED : + SCAN_CONFIG_FLAG_CLEAR_LMAC2_FRAGMENTED; + iwl_mvm_fill_scan_config_v2(mvm, cfg, flags, channel_flags, + num_channels); + } else { + iwl_mvm_fill_scan_config_v1(mvm, cfg, flags, channel_flags, + num_channels); + } + + cmd.data[0] = cfg; + cmd.len[0] = cmd_size; + cmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY; + + IWL_DEBUG_SCAN(mvm, "Sending UMAC scan config\n"); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (!ret) { + mvm->scan_type = type; + mvm->hb_scan_type = hb_type; + } + + kfree(cfg); + return ret; +} + +int iwl_mvm_config_scan(struct iwl_mvm *mvm) +{ + struct iwl_scan_config cfg; + struct iwl_host_cmd cmd = { + .id = iwl_cmd_id(SCAN_CFG_CMD, IWL_ALWAYS_LONG_GROUP, 0), + .len[0] = sizeof(cfg), + .data[0] = &cfg, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + + if (!iwl_mvm_is_reduced_config_scan_supported(mvm)) + return iwl_mvm_legacy_config_scan(mvm); + + memset(&cfg, 0, sizeof(cfg)); + + if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + ADD_STA, 0) < 12) { + cfg.bcast_sta_id = mvm->aux_sta.sta_id; + } else if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + SCAN_CFG_CMD, 0) < 5) { + /* + * Fw doesn't use this sta anymore. Deprecated on SCAN_CFG_CMD + * version 5. + */ + cfg.bcast_sta_id = 0xff; + } + + cfg.tx_chains = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); + cfg.rx_chains = cpu_to_le32(iwl_mvm_scan_rx_ant(mvm)); + + IWL_DEBUG_SCAN(mvm, "Sending UMAC scan config\n"); + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_scan_uid_by_status(struct iwl_mvm *mvm, int status) +{ + int i; + + for (i = 0; i < mvm->max_scans; i++) + if (mvm->scan_uid_status[i] == status) + return i; + + return -ENOENT; +} + +static void iwl_mvm_scan_umac_dwell(struct iwl_mvm *mvm, + struct iwl_scan_req_umac *cmd, + struct iwl_mvm_scan_params *params) +{ + struct iwl_mvm_scan_timing_params *timing, *hb_timing; + u8 active_dwell, passive_dwell; + + timing = &scan_timing[params->type]; + active_dwell = IWL_SCAN_DWELL_ACTIVE; + passive_dwell = IWL_SCAN_DWELL_PASSIVE; + + if (iwl_mvm_is_adaptive_dwell_supported(mvm)) { + cmd->v7.adwell_default_n_aps_social = + IWL_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL; + cmd->v7.adwell_default_n_aps = + IWL_SCAN_ADWELL_DEFAULT_LB_N_APS; + + if (iwl_mvm_is_adwell_hb_ap_num_supported(mvm)) + cmd->v9.adwell_default_hb_n_aps = + IWL_SCAN_ADWELL_DEFAULT_HB_N_APS; + + /* if custom max budget was configured with debugfs */ + if (IWL_MVM_ADWELL_MAX_BUDGET) + cmd->v7.adwell_max_budget = + cpu_to_le16(IWL_MVM_ADWELL_MAX_BUDGET); + else if (params->ssids && params->ssids[0].ssid_len) + cmd->v7.adwell_max_budget = + cpu_to_le16(IWL_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN); + else + cmd->v7.adwell_max_budget = + cpu_to_le16(IWL_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN); + + cmd->v7.scan_priority = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + cmd->v7.max_out_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->max_out_time); + cmd->v7.suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->suspend_time); + + if (iwl_mvm_is_cdb_supported(mvm)) { + hb_timing = &scan_timing[params->hb_type]; + + cmd->v7.max_out_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->max_out_time); + cmd->v7.suspend_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->suspend_time); + } + + if (!iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) { + cmd->v7.active_dwell = active_dwell; + cmd->v7.passive_dwell = passive_dwell; + cmd->v7.fragmented_dwell = IWL_SCAN_DWELL_FRAGMENTED; + } else { + cmd->v8.active_dwell[SCAN_LB_LMAC_IDX] = active_dwell; + cmd->v8.passive_dwell[SCAN_LB_LMAC_IDX] = passive_dwell; + if (iwl_mvm_is_cdb_supported(mvm)) { + cmd->v8.active_dwell[SCAN_HB_LMAC_IDX] = + active_dwell; + cmd->v8.passive_dwell[SCAN_HB_LMAC_IDX] = + passive_dwell; + } + } + } else { + cmd->v1.extended_dwell = IWL_SCAN_DWELL_EXTENDED; + cmd->v1.active_dwell = active_dwell; + cmd->v1.passive_dwell = passive_dwell; + cmd->v1.fragmented_dwell = IWL_SCAN_DWELL_FRAGMENTED; + + if (iwl_mvm_is_cdb_supported(mvm)) { + hb_timing = &scan_timing[params->hb_type]; + + cmd->v6.max_out_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->max_out_time); + cmd->v6.suspend_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->suspend_time); + } + + if (iwl_mvm_cdb_scan_api(mvm)) { + cmd->v6.scan_priority = + cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + cmd->v6.max_out_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->max_out_time); + cmd->v6.suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->suspend_time); + } else { + cmd->v1.scan_priority = + cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + cmd->v1.max_out_time = + cpu_to_le32(timing->max_out_time); + cmd->v1.suspend_time = + cpu_to_le32(timing->suspend_time); + } + } + + if (iwl_mvm_is_regular_scan(params)) + cmd->ooc_priority = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + else + cmd->ooc_priority = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_2); +} + +static u32 iwl_mvm_scan_umac_ooc_priority(struct iwl_mvm_scan_params *params) +{ + return iwl_mvm_is_regular_scan(params) ? + IWL_SCAN_PRIORITY_EXT_6 : + IWL_SCAN_PRIORITY_EXT_2; +} + +static void +iwl_mvm_scan_umac_dwell_v10(struct iwl_mvm *mvm, + struct iwl_scan_general_params_v10 *general_params, + struct iwl_mvm_scan_params *params) +{ + struct iwl_mvm_scan_timing_params *timing, *hb_timing; + u8 active_dwell, passive_dwell; + + timing = &scan_timing[params->type]; + active_dwell = IWL_SCAN_DWELL_ACTIVE; + passive_dwell = IWL_SCAN_DWELL_PASSIVE; + + general_params->adwell_default_social_chn = + IWL_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL; + general_params->adwell_default_2g = IWL_SCAN_ADWELL_DEFAULT_LB_N_APS; + general_params->adwell_default_5g = IWL_SCAN_ADWELL_DEFAULT_HB_N_APS; + + /* if custom max budget was configured with debugfs */ + if (IWL_MVM_ADWELL_MAX_BUDGET) + general_params->adwell_max_budget = + cpu_to_le16(IWL_MVM_ADWELL_MAX_BUDGET); + else if (params->ssids && params->ssids[0].ssid_len) + general_params->adwell_max_budget = + cpu_to_le16(IWL_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN); + else + general_params->adwell_max_budget = + cpu_to_le16(IWL_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN); + + general_params->scan_priority = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + general_params->max_out_of_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->max_out_time); + general_params->suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->suspend_time); + + hb_timing = &scan_timing[params->hb_type]; + + general_params->max_out_of_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->max_out_time); + general_params->suspend_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->suspend_time); + + general_params->active_dwell[SCAN_LB_LMAC_IDX] = active_dwell; + general_params->passive_dwell[SCAN_LB_LMAC_IDX] = passive_dwell; + general_params->active_dwell[SCAN_HB_LMAC_IDX] = active_dwell; + general_params->passive_dwell[SCAN_HB_LMAC_IDX] = passive_dwell; +} + +struct iwl_mvm_scan_channel_segment { + u8 start_idx; + u8 end_idx; + u8 first_channel_id; + u8 last_channel_id; + u8 channel_spacing_shift; + u8 band; +}; + +static const struct iwl_mvm_scan_channel_segment scan_channel_segments[] = { + { + .start_idx = 0, + .end_idx = 13, + .first_channel_id = 1, + .last_channel_id = 14, + .channel_spacing_shift = 0, + .band = PHY_BAND_24 + }, + { + .start_idx = 14, + .end_idx = 41, + .first_channel_id = 36, + .last_channel_id = 144, + .channel_spacing_shift = 2, + .band = PHY_BAND_5 + }, + { + .start_idx = 42, + .end_idx = 50, + .first_channel_id = 149, + .last_channel_id = 181, + .channel_spacing_shift = 2, + .band = PHY_BAND_5 + }, + { + .start_idx = 51, + .end_idx = 111, + .first_channel_id = 1, + .last_channel_id = 241, + .channel_spacing_shift = 2, + .band = PHY_BAND_6 + }, +}; + +static int iwl_mvm_scan_ch_and_band_to_idx(u8 channel_id, u8 band) +{ + int i, index; + + if (!channel_id) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(scan_channel_segments); i++) { + const struct iwl_mvm_scan_channel_segment *ch_segment = + &scan_channel_segments[i]; + u32 ch_offset; + + if (ch_segment->band != band || + ch_segment->first_channel_id > channel_id || + ch_segment->last_channel_id < channel_id) + continue; + + ch_offset = (channel_id - ch_segment->first_channel_id) >> + ch_segment->channel_spacing_shift; + + index = scan_channel_segments[i].start_idx + ch_offset; + if (index < IWL_SCAN_NUM_CHANNELS) + return index; + + break; + } + + return -EINVAL; +} + +static const u8 p2p_go_friendly_chs[] = { + 36, 40, 44, 48, 149, 153, 157, 161, 165, +}; + +static const u8 social_chs[] = { + 1, 6, 11 +}; + +static void iwl_mvm_scan_ch_add_n_aps_override(enum nl80211_iftype vif_type, + u8 ch_id, u8 band, u8 *ch_bitmap, + size_t bitmap_n_entries) +{ + int i; + + if (vif_type != NL80211_IFTYPE_P2P_DEVICE) + return; + + for (i = 0; i < ARRAY_SIZE(p2p_go_friendly_chs); i++) { + if (p2p_go_friendly_chs[i] == ch_id) { + int ch_idx, bitmap_idx; + + ch_idx = iwl_mvm_scan_ch_and_band_to_idx(ch_id, band); + if (ch_idx < 0) + return; + + bitmap_idx = ch_idx / 8; + if (bitmap_idx >= bitmap_n_entries) + return; + + ch_idx = ch_idx % 8; + ch_bitmap[bitmap_idx] |= BIT(ch_idx); + + return; + } + } +} + +static u32 iwl_mvm_scan_ch_n_aps_flag(enum nl80211_iftype vif_type, u8 ch_id) +{ + int i; + u32 flags = 0; + + if (vif_type != NL80211_IFTYPE_P2P_DEVICE) + goto out; + + for (i = 0; i < ARRAY_SIZE(p2p_go_friendly_chs); i++) { + if (p2p_go_friendly_chs[i] == ch_id) { + flags |= IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY_BIT; + break; + } + } + + if (flags) + goto out; + + for (i = 0; i < ARRAY_SIZE(social_chs); i++) { + if (social_chs[i] == ch_id) { + flags |= IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS_BIT; + break; + } + } + +out: + return flags; +} + +static void +iwl_mvm_umac_scan_cfg_channels(struct iwl_mvm *mvm, + struct ieee80211_channel **channels, + int n_channels, u32 flags, + struct iwl_scan_channel_cfg_umac *channel_cfg) +{ + int i; + + for (i = 0; i < n_channels; i++) { + channel_cfg[i].flags = cpu_to_le32(flags); + channel_cfg[i].v1.channel_num = channels[i]->hw_value; + if (iwl_mvm_is_scan_ext_chan_supported(mvm)) { + enum nl80211_band band = channels[i]->band; + + channel_cfg[i].v2.band = + iwl_mvm_phy_band_from_nl80211(band); + channel_cfg[i].v2.iter_count = 1; + channel_cfg[i].v2.iter_interval = 0; + } else { + channel_cfg[i].v1.iter_count = 1; + channel_cfg[i].v1.iter_interval = 0; + } + } +} + +static void +iwl_mvm_umac_scan_cfg_channels_v4(struct iwl_mvm *mvm, + struct ieee80211_channel **channels, + struct iwl_scan_channel_params_v4 *cp, + int n_channels, u32 flags, + enum nl80211_iftype vif_type) +{ + u8 *bitmap = cp->adwell_ch_override_bitmap; + size_t bitmap_n_entries = ARRAY_SIZE(cp->adwell_ch_override_bitmap); + int i; + + for (i = 0; i < n_channels; i++) { + enum nl80211_band band = channels[i]->band; + struct iwl_scan_channel_cfg_umac *cfg = + &cp->channel_config[i]; + + cfg->flags = cpu_to_le32(flags); + cfg->v2.channel_num = channels[i]->hw_value; + cfg->v2.band = iwl_mvm_phy_band_from_nl80211(band); + cfg->v2.iter_count = 1; + cfg->v2.iter_interval = 0; + + iwl_mvm_scan_ch_add_n_aps_override(vif_type, + cfg->v2.channel_num, + cfg->v2.band, bitmap, + bitmap_n_entries); + } +} + +static void +iwl_mvm_umac_scan_cfg_channels_v6(struct iwl_mvm *mvm, + struct ieee80211_channel **channels, + struct iwl_scan_channel_params_v6 *cp, + int n_channels, u32 flags, + enum nl80211_iftype vif_type) +{ + int i; + + for (i = 0; i < n_channels; i++) { + enum nl80211_band band = channels[i]->band; + struct iwl_scan_channel_cfg_umac *cfg = &cp->channel_config[i]; + u32 n_aps_flag = + iwl_mvm_scan_ch_n_aps_flag(vif_type, + channels[i]->hw_value); + + cfg->flags = cpu_to_le32(flags | n_aps_flag); + cfg->v2.channel_num = channels[i]->hw_value; + cfg->v2.band = iwl_mvm_phy_band_from_nl80211(band); + if (cfg80211_channel_is_psc(channels[i])) + cfg->flags = 0; + cfg->v2.iter_count = 1; + cfg->v2.iter_interval = 0; + } +} + +static int +iwl_mvm_umac_scan_fill_6g_chan_list(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct iwl_scan_probe_params_v4 *pp) +{ + int j, idex_s = 0, idex_b = 0; + struct cfg80211_scan_6ghz_params *scan_6ghz_params = + params->scan_6ghz_params; + bool hidden_supported = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_HIDDEN_6GHZ_SCAN); + + for (j = 0; j < params->n_ssids && idex_s < SCAN_SHORT_SSID_MAX_SIZE; + j++) { + if (!params->ssids[j].ssid_len) + continue; + + pp->short_ssid[idex_s] = + cpu_to_le32(~crc32_le(~0, params->ssids[j].ssid, + params->ssids[j].ssid_len)); + + if (hidden_supported) { + pp->direct_scan[idex_s].id = WLAN_EID_SSID; + pp->direct_scan[idex_s].len = params->ssids[j].ssid_len; + memcpy(pp->direct_scan[idex_s].ssid, params->ssids[j].ssid, + params->ssids[j].ssid_len); + } + idex_s++; + } + + /* + * Populate the arrays of the short SSIDs and the BSSIDs using the 6GHz + * collocated parameters. This might not be optimal, as this processing + * does not (yet) correspond to the actual channels, so it is possible + * that some entries would be left out. + * + * TODO: improve this logic. + */ + for (j = 0; j < params->n_6ghz_params; j++) { + int k; + + /* First, try to place the short SSID */ + if (scan_6ghz_params[j].short_ssid_valid) { + for (k = 0; k < idex_s; k++) { + if (pp->short_ssid[k] == + cpu_to_le32(scan_6ghz_params[j].short_ssid)) + break; + } + + if (k == idex_s && idex_s < SCAN_SHORT_SSID_MAX_SIZE) { + pp->short_ssid[idex_s++] = + cpu_to_le32(scan_6ghz_params[j].short_ssid); + } + } + + /* try to place BSSID for the same entry */ + for (k = 0; k < idex_b; k++) { + if (!memcmp(&pp->bssid_array[k], + scan_6ghz_params[j].bssid, ETH_ALEN)) + break; + } + + if (k == idex_b && idex_b < SCAN_BSSID_MAX_SIZE) { + memcpy(&pp->bssid_array[idex_b++], + scan_6ghz_params[j].bssid, ETH_ALEN); + } + } + + pp->short_ssid_num = idex_s; + pp->bssid_num = idex_b; + return 0; +} + +/* TODO: this function can be merged with iwl_mvm_scan_umac_fill_ch_p_v6 */ +static void +iwl_mvm_umac_scan_cfg_channels_v6_6g(struct iwl_mvm_scan_params *params, + u32 n_channels, + struct iwl_scan_probe_params_v4 *pp, + struct iwl_scan_channel_params_v6 *cp, + enum nl80211_iftype vif_type) +{ + struct iwl_scan_channel_cfg_umac *channel_cfg = cp->channel_config; + int i; + struct cfg80211_scan_6ghz_params *scan_6ghz_params = + params->scan_6ghz_params; + + for (i = 0; i < params->n_channels; i++) { + struct iwl_scan_channel_cfg_umac *cfg = + &cp->channel_config[i]; + + u32 s_ssid_bitmap = 0, bssid_bitmap = 0, flags = 0; + u8 j, k, s_max = 0, b_max = 0, n_used_bssid_entries; + bool force_passive, found = false, allow_passive = true, + unsolicited_probe_on_chan = false, psc_no_listen = false; + + cfg->v1.channel_num = params->channels[i]->hw_value; + cfg->v2.band = 2; + cfg->v2.iter_count = 1; + cfg->v2.iter_interval = 0; + + /* + * The optimize the scan time, i.e., reduce the scan dwell time + * on each channel, the below logic tries to set 3 direct BSSID + * probe requests for each broadcast probe request with a short + * SSID. + * TODO: improve this logic + */ + n_used_bssid_entries = 3; + for (j = 0; j < params->n_6ghz_params; j++) { + if (!(scan_6ghz_params[j].channel_idx == i)) + continue; + + found = false; + unsolicited_probe_on_chan |= + scan_6ghz_params[j].unsolicited_probe; + psc_no_listen |= scan_6ghz_params[j].psc_no_listen; + + for (k = 0; k < pp->short_ssid_num; k++) { + if (!scan_6ghz_params[j].unsolicited_probe && + le32_to_cpu(pp->short_ssid[k]) == + scan_6ghz_params[j].short_ssid) { + /* Relevant short SSID bit set */ + if (s_ssid_bitmap & BIT(k)) { + found = true; + break; + } + + /* + * Use short SSID only to create a new + * iteration during channel dwell or in + * case that the short SSID has a + * matching SSID, i.e., scan for hidden + * APs. + */ + if (n_used_bssid_entries >= 3) { + s_ssid_bitmap |= BIT(k); + s_max++; + n_used_bssid_entries -= 3; + found = true; + break; + } else if (pp->direct_scan[k].len) { + s_ssid_bitmap |= BIT(k); + s_max++; + found = true; + allow_passive = false; + break; + } + } + } + + if (found) + continue; + + for (k = 0; k < pp->bssid_num; k++) { + if (!memcmp(&pp->bssid_array[k], + scan_6ghz_params[j].bssid, + ETH_ALEN)) { + if (!(bssid_bitmap & BIT(k))) { + bssid_bitmap |= BIT(k); + b_max++; + n_used_bssid_entries++; + } + break; + } + } + } + + flags = bssid_bitmap | (s_ssid_bitmap << 16); + + if (cfg80211_channel_is_psc(params->channels[i]) && + psc_no_listen) + flags |= IWL_UHB_CHAN_CFG_FLAG_PSC_CHAN_NO_LISTEN; + + if (unsolicited_probe_on_chan) + flags |= IWL_UHB_CHAN_CFG_FLAG_UNSOLICITED_PROBE_RES; + + /* + * In the following cases apply passive scan: + * 1. Non fragmented scan: + * - PSC channel with NO_LISTEN_FLAG on should be treated + * like non PSC channel + * - Non PSC channel with more than 3 short SSIDs or more + * than 9 BSSIDs. + * - Non PSC Channel with unsolicited probe response and + * more than 2 short SSIDs or more than 6 BSSIDs. + * - PSC channel with more than 2 short SSIDs or more than + * 6 BSSIDs. + * 3. Fragmented scan: + * - PSC channel with more than 1 SSID or 3 BSSIDs. + * - Non PSC channel with more than 2 SSIDs or 6 BSSIDs. + * - Non PSC channel with unsolicited probe response and + * more than 1 SSID or more than 3 BSSIDs. + */ + if (!iwl_mvm_is_scan_fragmented(params->type)) { + if (!cfg80211_channel_is_psc(params->channels[i]) || + flags & IWL_UHB_CHAN_CFG_FLAG_PSC_CHAN_NO_LISTEN) { + force_passive = (s_max > 3 || b_max > 9); + force_passive |= (unsolicited_probe_on_chan && + (s_max > 2 || b_max > 6)); + } else { + force_passive = (s_max > 2 || b_max > 6); + } + } else if (cfg80211_channel_is_psc(params->channels[i])) { + force_passive = (s_max > 1 || b_max > 3); + } else { + force_passive = (s_max > 2 || b_max > 6); + force_passive |= (unsolicited_probe_on_chan && + (s_max > 1 || b_max > 3)); + } + if ((allow_passive && force_passive) || + (!flags && !cfg80211_channel_is_psc(params->channels[i]))) + flags |= IWL_UHB_CHAN_CFG_FLAG_FORCE_PASSIVE; + + channel_cfg[i].flags |= cpu_to_le32(flags); + } +} + +static u8 iwl_mvm_scan_umac_chan_flags_v2(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + u8 flags = 0; + + flags |= IWL_SCAN_CHANNEL_FLAG_ENABLE_CHAN_ORDER; + + if (iwl_mvm_scan_use_ebs(mvm, vif)) + flags |= IWL_SCAN_CHANNEL_FLAG_EBS | + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE | + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD; + + /* set fragmented ebs for fragmented scan on HB channels */ + if (iwl_mvm_is_scan_fragmented(params->hb_type)) + flags |= IWL_SCAN_CHANNEL_FLAG_EBS_FRAG; + + return flags; +} + +static void iwl_mvm_scan_6ghz_passive_scan(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + struct ieee80211_supported_band *sband = + &mvm->nvm_data->bands[NL80211_BAND_6GHZ]; + u32 n_disabled, i; + + params->enable_6ghz_passive = false; + + if (params->scan_6ghz) + return; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_PASSIVE_6GHZ_SCAN)) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: Not supported by FW\n"); + return; + } + + /* 6GHz passive scan allowed only on station interface */ + if (vif->type != NL80211_IFTYPE_STATION) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: not station interface\n"); + return; + } + + /* + * 6GHz passive scan is allowed while associated in a defined time + * interval following HW reset or resume flow + */ + if (vif->bss_conf.assoc && + (time_before(mvm->last_reset_or_resume_time_jiffies + + (IWL_MVM_6GHZ_PASSIVE_SCAN_ASSOC_TIMEOUT * HZ), + jiffies))) { + IWL_DEBUG_SCAN(mvm, "6GHz passive scan: associated\n"); + return; + } + + /* No need for 6GHz passive scan if not enough time elapsed */ + if (time_after(mvm->last_6ghz_passive_scan_jiffies + + (IWL_MVM_6GHZ_PASSIVE_SCAN_TIMEOUT * HZ), jiffies)) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: timeout did not expire\n"); + return; + } + + /* not enough channels in the regular scan request */ + if (params->n_channels < IWL_MVM_6GHZ_PASSIVE_SCAN_MIN_CHANS) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: not enough channels\n"); + return; + } + + for (i = 0; i < params->n_ssids; i++) { + if (!params->ssids[i].ssid_len) + break; + } + + /* not a wildcard scan, so cannot enable passive 6GHz scan */ + if (i == params->n_ssids) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: no wildcard SSID\n"); + return; + } + + if (!sband || !sband->n_channels) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: no 6GHz channels\n"); + return; + } + + for (i = 0, n_disabled = 0; i < sband->n_channels; i++) { + if (sband->channels[i].flags & (IEEE80211_CHAN_DISABLED)) + n_disabled++; + } + + /* + * Not all the 6GHz channels are disabled, so no need for 6GHz passive + * scan + */ + if (n_disabled != sband->n_channels) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: 6GHz channels enabled\n"); + return; + } + + /* all conditions to enable 6ghz passive scan are satisfied */ + IWL_DEBUG_SCAN(mvm, "6GHz passive scan: can be enabled\n"); + params->enable_6ghz_passive = true; +} + +static u16 iwl_mvm_scan_umac_flags_v2(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif, + int type) +{ + u16 flags = 0; + + /* + * If no direct SSIDs are provided perform a passive scan. Otherwise, + * if there is a single SSID which is not the broadcast SSID, assume + * that the scan is intended for roaming purposes and thus enable Rx on + * all chains to improve chances of hearing the beacons/probe responses. + */ + if (params->n_ssids == 0) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE; + else if (params->n_ssids == 1 && params->ssids[0].ssid_len) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_USE_ALL_RX_CHAINS; + + if (iwl_mvm_is_scan_fragmented(params->type)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC1; + + if (iwl_mvm_is_scan_fragmented(params->hb_type)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC2; + + if (params->pass_all) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_PASS_ALL; + else + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_MATCH; + + if (!iwl_mvm_is_regular_scan(params)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_PERIODIC; + + if (params->iter_notif || + mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_ENABLED) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_NTFY_ITER_COMPLETE; + + if (IWL_MVM_ADWELL_ENABLE) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_ADAPTIVE_DWELL; + + if (type == IWL_MVM_SCAN_SCHED || type == IWL_MVM_SCAN_NETDETECT) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_PREEMPTIVE; + + if ((type == IWL_MVM_SCAN_SCHED || type == IWL_MVM_SCAN_NETDETECT) && + params->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_TRIGGER_UHB_SCAN; + + if (params->enable_6ghz_passive) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN; + + return flags; +} + +static u16 iwl_mvm_scan_umac_flags(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + u16 flags = 0; + + if (params->n_ssids == 0) + flags = IWL_UMAC_SCAN_GEN_FLAGS_PASSIVE; + + if (params->n_ssids == 1 && params->ssids[0].ssid_len != 0) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PRE_CONNECT; + + if (iwl_mvm_is_scan_fragmented(params->type)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_FRAGMENTED; + + if (iwl_mvm_is_cdb_supported(mvm) && + iwl_mvm_is_scan_fragmented(params->hb_type)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_LMAC2_FRAGMENTED; + + if (iwl_mvm_rrm_scan_needed(mvm) && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_RRM_ENABLED; + + if (params->pass_all) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PASS_ALL; + else + flags |= IWL_UMAC_SCAN_GEN_FLAGS_MATCH; + + if (!iwl_mvm_is_regular_scan(params)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PERIODIC; + + if (params->iter_notif) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_ITER_COMPLETE; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvm->scan_iter_notif_enabled) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_ITER_COMPLETE; +#endif + + if (mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_ENABLED) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_ITER_COMPLETE; + + if (iwl_mvm_is_adaptive_dwell_supported(mvm) && IWL_MVM_ADWELL_ENABLE) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_ADAPTIVE_DWELL; + + /* + * Extended dwell is relevant only for low band to start with, as it is + * being used for social channles only (1, 6, 11), so we can check + * only scan type on low band also for CDB. + */ + if (iwl_mvm_is_regular_scan(params) && + vif->type != NL80211_IFTYPE_P2P_DEVICE && + !iwl_mvm_is_scan_fragmented(params->type) && + !iwl_mvm_is_adaptive_dwell_supported(mvm) && + !iwl_mvm_is_oce_supported(mvm)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_EXTENDED_DWELL; + + if (iwl_mvm_is_oce_supported(mvm)) { + if ((params->flags & + NL80211_SCAN_FLAG_OCE_PROBE_REQ_HIGH_TX_RATE)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PROB_REQ_HIGH_TX_RATE; + /* Since IWL_UMAC_SCAN_GEN_FLAGS_EXTENDED_DWELL and + * NL80211_SCAN_FLAG_OCE_PROBE_REQ_DEFERRAL_SUPPRESSION shares + * the same bit, we need to make sure that we use this bit here + * only when IWL_UMAC_SCAN_GEN_FLAGS_EXTENDED_DWELL cannot be + * used. */ + if ((params->flags & + NL80211_SCAN_FLAG_OCE_PROBE_REQ_DEFERRAL_SUPPRESSION) && + !WARN_ON_ONCE(!iwl_mvm_is_adaptive_dwell_supported(mvm))) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PROB_REQ_DEFER_SUPP; + if ((params->flags & NL80211_SCAN_FLAG_FILS_MAX_CHANNEL_TIME)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_MAX_CHNL_TIME; + } + + return flags; +} + +static int +iwl_mvm_fill_scan_sched_params(struct iwl_mvm_scan_params *params, + struct iwl_scan_umac_schedule *schedule, + __le16 *delay) +{ + int i; + if (WARN_ON(!params->n_scan_plans || + params->n_scan_plans > IWL_MAX_SCHED_SCAN_PLANS)) + return -EINVAL; + + for (i = 0; i < params->n_scan_plans; i++) { + struct cfg80211_sched_scan_plan *scan_plan = + ¶ms->scan_plans[i]; + + schedule[i].iter_count = scan_plan->iterations; + schedule[i].interval = + cpu_to_le16(scan_plan->interval); + } + + /* + * If the number of iterations of the last scan plan is set to + * zero, it should run infinitely. However, this is not always the case. + * For example, when regular scan is requested the driver sets one scan + * plan with one iteration. + */ + if (!schedule[params->n_scan_plans - 1].iter_count) + schedule[params->n_scan_plans - 1].iter_count = 0xff; + + *delay = cpu_to_le16(params->delay); + + return 0; +} + +static int iwl_mvm_scan_umac(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, + int type, int uid) +{ + struct iwl_scan_req_umac *cmd = mvm->scan_cmd; + struct iwl_scan_umac_chan_param *chan_param; + void *cmd_data = iwl_mvm_get_scan_req_umac_data(mvm); + void *sec_part = (u8 *)cmd_data + + sizeof(struct iwl_scan_channel_cfg_umac) * + mvm->fw->ucode_capa.n_scan_channels; + struct iwl_scan_req_umac_tail_v2 *tail_v2 = + (struct iwl_scan_req_umac_tail_v2 *)sec_part; + struct iwl_scan_req_umac_tail_v1 *tail_v1; + struct iwl_ssid_ie *direct_scan; + int ret = 0; + u32 ssid_bitmap = 0; + u8 channel_flags = 0; + u16 gen_flags; + struct iwl_mvm_vif *scan_vif = iwl_mvm_vif_from_mac80211(vif); + + chan_param = iwl_mvm_get_scan_req_umac_channel(mvm); + + iwl_mvm_scan_umac_dwell(mvm, cmd, params); + + mvm->scan_uid_status[uid] = type; + + cmd->uid = cpu_to_le32(uid); + gen_flags = iwl_mvm_scan_umac_flags(mvm, params, vif); + cmd->general_flags = cpu_to_le16(gen_flags); + if (iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) { + if (gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_FRAGMENTED) + cmd->v8.num_of_fragments[SCAN_LB_LMAC_IDX] = + IWL_SCAN_NUM_OF_FRAGS; + if (gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_LMAC2_FRAGMENTED) + cmd->v8.num_of_fragments[SCAN_HB_LMAC_IDX] = + IWL_SCAN_NUM_OF_FRAGS; + + cmd->v8.general_flags2 = + IWL_UMAC_SCAN_GEN_FLAGS2_ALLOW_CHNL_REORDER; + } + + cmd->scan_start_mac_id = scan_vif->id; + + if (type == IWL_MVM_SCAN_SCHED || type == IWL_MVM_SCAN_NETDETECT) + cmd->flags = cpu_to_le32(IWL_UMAC_SCAN_FLAG_PREEMPTIVE); + + if (iwl_mvm_scan_use_ebs(mvm, vif)) { + channel_flags = IWL_SCAN_CHANNEL_FLAG_EBS | + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE | + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD; + + /* set fragmented ebs for fragmented scan on HB channels */ + if (iwl_mvm_is_frag_ebs_supported(mvm)) { + if (gen_flags & + IWL_UMAC_SCAN_GEN_FLAGS_LMAC2_FRAGMENTED || + (!iwl_mvm_is_cdb_supported(mvm) && + gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_FRAGMENTED)) + channel_flags |= IWL_SCAN_CHANNEL_FLAG_EBS_FRAG; + } + } + + chan_param->flags = channel_flags; + chan_param->count = params->n_channels; + + ret = iwl_mvm_fill_scan_sched_params(params, tail_v2->schedule, + &tail_v2->delay); + if (ret) { + mvm->scan_uid_status[uid] = 0; + return ret; + } + + if (iwl_mvm_is_scan_ext_chan_supported(mvm)) { + tail_v2->preq = params->preq; + direct_scan = tail_v2->direct_scan; + } else { + tail_v1 = (struct iwl_scan_req_umac_tail_v1 *)sec_part; + iwl_mvm_scan_set_legacy_probe_req(&tail_v1->preq, + ¶ms->preq); + direct_scan = tail_v1->direct_scan; + } + iwl_scan_build_ssids(params, direct_scan, &ssid_bitmap); + iwl_mvm_umac_scan_cfg_channels(mvm, params->channels, + params->n_channels, ssid_bitmap, + cmd_data); + return 0; +} + +static void +iwl_mvm_scan_umac_fill_general_p_v10(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif, + struct iwl_scan_general_params_v10 *gp, + u16 gen_flags) +{ + struct iwl_mvm_vif *scan_vif = iwl_mvm_vif_from_mac80211(vif); + + iwl_mvm_scan_umac_dwell_v10(mvm, gp, params); + + gp->flags = cpu_to_le16(gen_flags); + + if (gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC1) + gp->num_of_fragments[SCAN_LB_LMAC_IDX] = IWL_SCAN_NUM_OF_FRAGS; + if (gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC2) + gp->num_of_fragments[SCAN_HB_LMAC_IDX] = IWL_SCAN_NUM_OF_FRAGS; + + gp->scan_start_mac_id = scan_vif->id; +} + +static void +iwl_mvm_scan_umac_fill_probe_p_v3(struct iwl_mvm_scan_params *params, + struct iwl_scan_probe_params_v3 *pp) +{ + pp->preq = params->preq; + pp->ssid_num = params->n_ssids; + iwl_scan_build_ssids(params, pp->direct_scan, NULL); +} + +static void +iwl_mvm_scan_umac_fill_probe_p_v4(struct iwl_mvm_scan_params *params, + struct iwl_scan_probe_params_v4 *pp, + u32 *bitmap_ssid) +{ + pp->preq = params->preq; + iwl_scan_build_ssids(params, pp->direct_scan, bitmap_ssid); +} + +static void +iwl_mvm_scan_umac_fill_ch_p_v4(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif, + struct iwl_scan_channel_params_v4 *cp, + u32 channel_cfg_flags) +{ + cp->flags = iwl_mvm_scan_umac_chan_flags_v2(mvm, params, vif); + cp->count = params->n_channels; + cp->num_of_aps_override = IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY; + + iwl_mvm_umac_scan_cfg_channels_v4(mvm, params->channels, cp, + params->n_channels, + channel_cfg_flags, + vif->type); +} + +static void +iwl_mvm_scan_umac_fill_ch_p_v6(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif, + struct iwl_scan_channel_params_v6 *cp, + u32 channel_cfg_flags) +{ + cp->flags = iwl_mvm_scan_umac_chan_flags_v2(mvm, params, vif); + cp->count = params->n_channels; + cp->n_aps_override[0] = IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY; + cp->n_aps_override[1] = IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS; + + iwl_mvm_umac_scan_cfg_channels_v6(mvm, params->channels, cp, + params->n_channels, + channel_cfg_flags, + vif->type); + + if (params->enable_6ghz_passive) { + struct ieee80211_supported_band *sband = + &mvm->nvm_data->bands[NL80211_BAND_6GHZ]; + u32 i; + + for (i = 0; i < sband->n_channels; i++) { + struct ieee80211_channel *channel = + &sband->channels[i]; + + struct iwl_scan_channel_cfg_umac *cfg = + &cp->channel_config[cp->count]; + + if (!cfg80211_channel_is_psc(channel)) + continue; + + cfg->flags = 0; + cfg->v2.channel_num = channel->hw_value; + cfg->v2.band = PHY_BAND_6; + cfg->v2.iter_count = 1; + cfg->v2.iter_interval = 0; + cp->count++; + } + } +} + +static int iwl_mvm_scan_umac_v12(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, int type, + int uid) +{ + struct iwl_scan_req_umac_v12 *cmd = mvm->scan_cmd; + struct iwl_scan_req_params_v12 *scan_p = &cmd->scan_params; + int ret; + u16 gen_flags; + + mvm->scan_uid_status[uid] = type; + + cmd->ooc_priority = cpu_to_le32(iwl_mvm_scan_umac_ooc_priority(params)); + cmd->uid = cpu_to_le32(uid); + + gen_flags = iwl_mvm_scan_umac_flags_v2(mvm, params, vif, type); + iwl_mvm_scan_umac_fill_general_p_v10(mvm, params, vif, + &scan_p->general_params, + gen_flags); + + ret = iwl_mvm_fill_scan_sched_params(params, + scan_p->periodic_params.schedule, + &scan_p->periodic_params.delay); + if (ret) + return ret; + + iwl_mvm_scan_umac_fill_probe_p_v3(params, &scan_p->probe_params); + iwl_mvm_scan_umac_fill_ch_p_v4(mvm, params, vif, + &scan_p->channel_params, 0); + + return 0; +} + +static int iwl_mvm_scan_umac_v14(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, int type, + int uid) +{ + struct iwl_scan_req_umac_v14 *cmd = mvm->scan_cmd; + struct iwl_scan_req_params_v14 *scan_p = &cmd->scan_params; + struct iwl_scan_channel_params_v6 *cp = &scan_p->channel_params; + struct iwl_scan_probe_params_v4 *pb = &scan_p->probe_params; + int ret; + u16 gen_flags; + u32 bitmap_ssid = 0; + + mvm->scan_uid_status[uid] = type; + + cmd->ooc_priority = cpu_to_le32(iwl_mvm_scan_umac_ooc_priority(params)); + cmd->uid = cpu_to_le32(uid); + + gen_flags = iwl_mvm_scan_umac_flags_v2(mvm, params, vif, type); + iwl_mvm_scan_umac_fill_general_p_v10(mvm, params, vif, + &scan_p->general_params, + gen_flags); + + ret = iwl_mvm_fill_scan_sched_params(params, + scan_p->periodic_params.schedule, + &scan_p->periodic_params.delay); + if (ret) + return ret; + + if (!params->scan_6ghz) { + iwl_mvm_scan_umac_fill_probe_p_v4(params, &scan_p->probe_params, + &bitmap_ssid); + iwl_mvm_scan_umac_fill_ch_p_v6(mvm, params, vif, + &scan_p->channel_params, bitmap_ssid); + + return 0; + } else { + pb->preq = params->preq; + } + + cp->flags = iwl_mvm_scan_umac_chan_flags_v2(mvm, params, vif); + cp->n_aps_override[0] = IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY; + cp->n_aps_override[1] = IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS; + + ret = iwl_mvm_umac_scan_fill_6g_chan_list(mvm, params, pb); + if (ret) + return ret; + + iwl_mvm_umac_scan_cfg_channels_v6_6g(params, + params->n_channels, + pb, cp, vif->type); + cp->count = params->n_channels; + if (!params->n_ssids || + (params->n_ssids == 1 && !params->ssids[0].ssid_len)) + cp->flags |= IWL_SCAN_CHANNEL_FLAG_6G_PSC_NO_FILTER; + + return 0; +} + +static int iwl_mvm_num_scans(struct iwl_mvm *mvm) +{ + return hweight32(mvm->scan_status & IWL_MVM_SCAN_MASK); +} + +static int iwl_mvm_check_running_scans(struct iwl_mvm *mvm, int type) +{ + bool unified_image = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); + + /* This looks a bit arbitrary, but the idea is that if we run + * out of possible simultaneous scans and the userspace is + * trying to run a scan type that is already running, we + * return -EBUSY. But if the userspace wants to start a + * different type of scan, we stop the opposite type to make + * space for the new request. The reason is backwards + * compatibility with old wpa_supplicant that wouldn't stop a + * scheduled scan before starting a normal scan. + */ + + /* FW supports only a single periodic scan */ + if ((type == IWL_MVM_SCAN_SCHED || type == IWL_MVM_SCAN_NETDETECT) && + mvm->scan_status & (IWL_MVM_SCAN_SCHED | IWL_MVM_SCAN_NETDETECT)) + return -EBUSY; + + if (iwl_mvm_num_scans(mvm) < mvm->max_scans) + return 0; + + /* Use a switch, even though this is a bitmask, so that more + * than one bits set will fall in default and we will warn. + */ + switch (type) { + case IWL_MVM_SCAN_REGULAR: + if (mvm->scan_status & IWL_MVM_SCAN_REGULAR_MASK) + return -EBUSY; + return iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, true); + case IWL_MVM_SCAN_SCHED: + if (mvm->scan_status & IWL_MVM_SCAN_SCHED_MASK) + return -EBUSY; + return iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_REGULAR, true); + case IWL_MVM_SCAN_NETDETECT: + /* For non-unified images, there's no need to stop + * anything for net-detect since the firmware is + * restarted anyway. This way, any sched scans that + * were running will be restarted when we resume. + */ + if (!unified_image) + return 0; + + /* If this is a unified image and we ran out of scans, + * we need to stop something. Prefer stopping regular + * scans, because the results are useless at this + * point, and we should be able to keep running + * another scheduled scan while suspended. + */ + if (mvm->scan_status & IWL_MVM_SCAN_REGULAR_MASK) + return iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_REGULAR, + true); + if (mvm->scan_status & IWL_MVM_SCAN_SCHED_MASK) + return iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, + true); + /* Something is wrong if no scan was running but we + * ran out of scans. + */ + fallthrough; + default: + WARN_ON(1); + break; + } + + return -EIO; +} + +#define SCAN_TIMEOUT 20000 + +void iwl_mvm_scan_timeout_wk(struct work_struct *work) +{ + struct delayed_work *delayed_work = to_delayed_work(work); + struct iwl_mvm *mvm = container_of(delayed_work, struct iwl_mvm, + scan_timeout_dwork); + + IWL_ERR(mvm, "regular scan timed out\n"); + + iwl_force_nmi(mvm->trans); +} + +static void iwl_mvm_fill_scan_type(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + if (iwl_mvm_is_cdb_supported(mvm)) { + params->type = + iwl_mvm_get_scan_type_band(mvm, vif, + NL80211_BAND_2GHZ); + params->hb_type = + iwl_mvm_get_scan_type_band(mvm, vif, + NL80211_BAND_5GHZ); + } else { + params->type = iwl_mvm_get_scan_type(mvm, vif); + } +} + +struct iwl_scan_umac_handler { + u8 version; + int (*handler)(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, int type, int uid); +}; + +#define IWL_SCAN_UMAC_HANDLER(_ver) { \ + .version = _ver, \ + .handler = iwl_mvm_scan_umac_v##_ver, \ +} + +static const struct iwl_scan_umac_handler iwl_scan_umac_handlers[] = { + /* set the newest version first to shorten the list traverse time */ + IWL_SCAN_UMAC_HANDLER(14), + IWL_SCAN_UMAC_HANDLER(12), +}; + +static int iwl_mvm_build_scan_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_host_cmd *hcmd, + struct iwl_mvm_scan_params *params, + int type) +{ + int uid, i, err; + u8 scan_ver; + + lockdep_assert_held(&mvm->mutex); + memset(mvm->scan_cmd, 0, ksize(mvm->scan_cmd)); + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + hcmd->id = SCAN_OFFLOAD_REQUEST_CMD; + + return iwl_mvm_scan_lmac(mvm, vif, params); + } + + uid = iwl_mvm_scan_uid_by_status(mvm, 0); + if (uid < 0) + return uid; + + hcmd->id = iwl_cmd_id(SCAN_REQ_UMAC, IWL_ALWAYS_LONG_GROUP, 0); + + scan_ver = iwl_fw_lookup_cmd_ver(mvm->fw, IWL_ALWAYS_LONG_GROUP, + SCAN_REQ_UMAC, + IWL_FW_CMD_VER_UNKNOWN); + + for (i = 0; i < ARRAY_SIZE(iwl_scan_umac_handlers); i++) { + const struct iwl_scan_umac_handler *ver_handler = + &iwl_scan_umac_handlers[i]; + + if (ver_handler->version != scan_ver) + continue; + + return ver_handler->handler(mvm, vif, params, type, uid); + } + + err = iwl_mvm_scan_umac(mvm, vif, params, type, uid); + if (err) + return err; + + return uid; +} + +int iwl_mvm_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_scan_request *req, + struct ieee80211_scan_ies *ies) +{ + struct iwl_host_cmd hcmd = { + .len = { iwl_mvm_scan_size(mvm), }, + .data = { mvm->scan_cmd, }, + .dataflags = { IWL_HCMD_DFL_NOCOPY, }, + }; + struct iwl_mvm_scan_params params = {}; + int ret, uid; + struct cfg80211_sched_scan_plan scan_plan = { .iterations = 1 }; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_is_lar_supported(mvm) && !mvm->lar_regdom_set) { + IWL_ERR(mvm, "scan while LAR regdomain is not set\n"); + return -EBUSY; + } + + ret = iwl_mvm_check_running_scans(mvm, IWL_MVM_SCAN_REGULAR); + if (ret) + return ret; + + /* we should have failed registration if scan_cmd was NULL */ + if (WARN_ON(!mvm->scan_cmd)) + return -ENOMEM; + + if (!iwl_mvm_scan_fits(mvm, req->n_ssids, ies, req->n_channels)) + return -ENOBUFS; + + params.n_ssids = req->n_ssids; + params.flags = req->flags; + params.n_channels = req->n_channels; + params.delay = 0; + params.ssids = req->ssids; + params.channels = req->channels; + params.mac_addr = req->mac_addr; + params.mac_addr_mask = req->mac_addr_mask; + params.no_cck = req->no_cck; + params.pass_all = true; + params.n_match_sets = 0; + params.match_sets = NULL; + + params.scan_plans = &scan_plan; + params.n_scan_plans = 1; + + params.n_6ghz_params = req->n_6ghz_params; + params.scan_6ghz_params = req->scan_6ghz_params; + params.scan_6ghz = req->scan_6ghz; + iwl_mvm_fill_scan_type(mvm, ¶ms, vif); + + if (req->duration) + params.iter_notif = true; + + iwl_mvm_build_scan_probe(mvm, vif, ies, ¶ms); + + iwl_mvm_scan_6ghz_passive_scan(mvm, ¶ms, vif); + + uid = iwl_mvm_build_scan_cmd(mvm, vif, &hcmd, ¶ms, + IWL_MVM_SCAN_REGULAR); + + if (uid < 0) + return uid; + + iwl_mvm_pause_tcm(mvm, false); + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + if (ret) { + /* If the scan failed, it usually means that the FW was unable + * to allocate the time events. Warn on it, but maybe we + * should try to send the command again with different params. + */ + IWL_ERR(mvm, "Scan failed! ret %d\n", ret); + iwl_mvm_resume_tcm(mvm); + mvm->scan_uid_status[uid] = 0; + return ret; + } + + IWL_DEBUG_SCAN(mvm, "Scan request was sent successfully\n"); + mvm->scan_status |= IWL_MVM_SCAN_REGULAR; + mvm->scan_vif = iwl_mvm_vif_from_mac80211(vif); + + if (params.enable_6ghz_passive) + mvm->last_6ghz_passive_scan_jiffies = jiffies; + + schedule_delayed_work(&mvm->scan_timeout_dwork, + msecs_to_jiffies(SCAN_TIMEOUT)); + + return 0; +} + +int iwl_mvm_sched_scan_start(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_scan_ies *ies, + int type) +{ + struct iwl_host_cmd hcmd = { + .len = { iwl_mvm_scan_size(mvm), }, + .data = { mvm->scan_cmd, }, + .dataflags = { IWL_HCMD_DFL_NOCOPY, }, + }; + struct iwl_mvm_scan_params params = {}; + int ret, uid; + int i, j; + bool non_psc_included = false; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_is_lar_supported(mvm) && !mvm->lar_regdom_set) { + IWL_ERR(mvm, "sched-scan while LAR regdomain is not set\n"); + return -EBUSY; + } + + ret = iwl_mvm_check_running_scans(mvm, type); + if (ret) + return ret; + + /* we should have failed registration if scan_cmd was NULL */ + if (WARN_ON(!mvm->scan_cmd)) + return -ENOMEM; + + + params.n_ssids = req->n_ssids; + params.flags = req->flags; + params.n_channels = req->n_channels; + params.ssids = req->ssids; + params.channels = req->channels; + params.mac_addr = req->mac_addr; + params.mac_addr_mask = req->mac_addr_mask; + params.no_cck = false; + params.pass_all = iwl_mvm_scan_pass_all(mvm, req); + params.n_match_sets = req->n_match_sets; + params.match_sets = req->match_sets; + if (!req->n_scan_plans) + return -EINVAL; + + params.n_scan_plans = req->n_scan_plans; + params.scan_plans = req->scan_plans; + + iwl_mvm_fill_scan_type(mvm, ¶ms, vif); + + /* In theory, LMAC scans can handle a 32-bit delay, but since + * waiting for over 18 hours to start the scan is a bit silly + * and to keep it aligned with UMAC scans (which only support + * 16-bit delays), trim it down to 16-bits. + */ + if (req->delay > U16_MAX) { + IWL_DEBUG_SCAN(mvm, + "delay value is > 16-bits, set to max possible\n"); + params.delay = U16_MAX; + } else { + params.delay = req->delay; + } + + ret = iwl_mvm_config_sched_scan_profiles(mvm, req); + if (ret) + return ret; + + iwl_mvm_build_scan_probe(mvm, vif, ies, ¶ms); + + /* for 6 GHZ band only PSC channels need to be added */ + for (i = 0; i < params.n_channels; i++) { + struct ieee80211_channel *channel = params.channels[i]; + + if (channel->band == NL80211_BAND_6GHZ && + !cfg80211_channel_is_psc(channel)) { + non_psc_included = true; + break; + } + } + + if (non_psc_included) { + params.channels = kmemdup(params.channels, + sizeof(params.channels[0]) * + params.n_channels, + GFP_KERNEL); + if (!params.channels) + return -ENOMEM; + + for (i = j = 0; i < params.n_channels; i++) { + if (params.channels[i]->band == NL80211_BAND_6GHZ && + !cfg80211_channel_is_psc(params.channels[i])) + continue; + params.channels[j++] = params.channels[i]; + } + params.n_channels = j; + } + + if (non_psc_included && + !iwl_mvm_scan_fits(mvm, req->n_ssids, ies, params.n_channels)) { + kfree(params.channels); + return -ENOBUFS; + } + + uid = iwl_mvm_build_scan_cmd(mvm, vif, &hcmd, ¶ms, type); + + if (non_psc_included) + kfree(params.channels); + if (uid < 0) + return uid; + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + if (!ret) { + IWL_DEBUG_SCAN(mvm, + "Sched scan request was sent successfully\n"); + mvm->scan_status |= type; + } else { + /* If the scan failed, it usually means that the FW was unable + * to allocate the time events. Warn on it, but maybe we + * should try to send the command again with different params. + */ + IWL_ERR(mvm, "Sched scan failed! ret %d\n", ret); + mvm->scan_uid_status[uid] = 0; + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } + + return ret; +} + +void iwl_mvm_rx_umac_scan_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_umac_scan_complete *notif = (void *)pkt->data; + u32 uid = __le32_to_cpu(notif->uid); + bool aborted = (notif->status == IWL_SCAN_OFFLOAD_ABORTED); + + if (WARN_ON(!(mvm->scan_uid_status[uid] & mvm->scan_status))) + return; + + /* if the scan is already stopping, we don't need to notify mac80211 */ + if (mvm->scan_uid_status[uid] == IWL_MVM_SCAN_REGULAR) { + struct cfg80211_scan_info info = { + .aborted = aborted, + .scan_start_tsf = mvm->scan_start, + }; + + memcpy(info.tsf_bssid, mvm->scan_vif->bssid, ETH_ALEN); + ieee80211_scan_completed(mvm->hw, &info); + mvm->scan_vif = NULL; + cancel_delayed_work(&mvm->scan_timeout_dwork); + iwl_mvm_resume_tcm(mvm); + } else if (mvm->scan_uid_status[uid] == IWL_MVM_SCAN_SCHED) { + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } + + mvm->scan_status &= ~mvm->scan_uid_status[uid]; + IWL_DEBUG_SCAN(mvm, + "Scan completed, uid %u type %u, status %s, EBS status %s\n", + uid, mvm->scan_uid_status[uid], + notif->status == IWL_SCAN_OFFLOAD_COMPLETED ? + "completed" : "aborted", + iwl_mvm_ebs_status_str(notif->ebs_status)); + IWL_DEBUG_SCAN(mvm, + "Last line %d, Last iteration %d, Time from last iteration %d\n", + notif->last_schedule, notif->last_iter, + __le32_to_cpu(notif->time_from_last_iter)); + + if (notif->ebs_status != IWL_SCAN_EBS_SUCCESS && + notif->ebs_status != IWL_SCAN_EBS_INACTIVE) + mvm->last_ebs_successful = false; + + mvm->scan_uid_status[uid] = 0; +} + +void iwl_mvm_rx_umac_scan_iter_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_umac_scan_iter_complete_notif *notif = (void *)pkt->data; + + mvm->scan_start = le64_to_cpu(notif->start_tsf); + + IWL_DEBUG_SCAN(mvm, + "UMAC Scan iteration complete: status=0x%x scanned_channels=%d\n", + notif->status, notif->scanned_channels); + + if (mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_FOUND) { + IWL_DEBUG_SCAN(mvm, "Pass all scheduled scan results found\n"); + ieee80211_sched_scan_results(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_ENABLED; + } + + IWL_DEBUG_SCAN(mvm, + "UMAC Scan iteration complete: scan started at %llu (TSF)\n", + mvm->scan_start); +} + +static int iwl_mvm_umac_scan_abort(struct iwl_mvm *mvm, int type) +{ + struct iwl_umac_scan_abort cmd = {}; + int uid, ret; + + lockdep_assert_held(&mvm->mutex); + + /* We should always get a valid index here, because we already + * checked that this type of scan was running in the generic + * code. + */ + uid = iwl_mvm_scan_uid_by_status(mvm, type); + if (WARN_ON_ONCE(uid < 0)) + return uid; + + cmd.uid = cpu_to_le32(uid); + + IWL_DEBUG_SCAN(mvm, "Sending scan abort, uid %u\n", uid); + + ret = iwl_mvm_send_cmd_pdu(mvm, + iwl_cmd_id(SCAN_ABORT_UMAC, + IWL_ALWAYS_LONG_GROUP, 0), + 0, sizeof(cmd), &cmd); + if (!ret) + mvm->scan_uid_status[uid] = type << IWL_MVM_SCAN_STOPPING_SHIFT; + + return ret; +} + +static int iwl_mvm_scan_stop_wait(struct iwl_mvm *mvm, int type) +{ + struct iwl_notification_wait wait_scan_done; + static const u16 scan_done_notif[] = { SCAN_COMPLETE_UMAC, + SCAN_OFFLOAD_COMPLETE, }; + int ret; + + lockdep_assert_held(&mvm->mutex); + + iwl_init_notification_wait(&mvm->notif_wait, &wait_scan_done, + scan_done_notif, + ARRAY_SIZE(scan_done_notif), + NULL, NULL); + + IWL_DEBUG_SCAN(mvm, "Preparing to stop scan, type %x\n", type); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) + ret = iwl_mvm_umac_scan_abort(mvm, type); + else + ret = iwl_mvm_lmac_scan_abort(mvm); + + if (ret) { + IWL_DEBUG_SCAN(mvm, "couldn't stop scan type %d\n", type); + iwl_remove_notification(&mvm->notif_wait, &wait_scan_done); + return ret; + } + + return iwl_wait_notification(&mvm->notif_wait, &wait_scan_done, + 1 * HZ); +} + +#define IWL_SCAN_REQ_UMAC_HANDLE_SIZE(_ver) { \ + case (_ver): return sizeof(struct iwl_scan_req_umac_v##_ver); \ +} + +static int iwl_scan_req_umac_get_size(u8 scan_ver) +{ + switch (scan_ver) { + IWL_SCAN_REQ_UMAC_HANDLE_SIZE(14); + IWL_SCAN_REQ_UMAC_HANDLE_SIZE(12); + } + + return 0; +} + +int iwl_mvm_scan_size(struct iwl_mvm *mvm) +{ + int base_size, tail_size; + u8 scan_ver = iwl_fw_lookup_cmd_ver(mvm->fw, IWL_ALWAYS_LONG_GROUP, + SCAN_REQ_UMAC, + IWL_FW_CMD_VER_UNKNOWN); + + base_size = iwl_scan_req_umac_get_size(scan_ver); + if (base_size) + return base_size; + + + if (iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) + base_size = IWL_SCAN_REQ_UMAC_SIZE_V8; + else if (iwl_mvm_is_adaptive_dwell_supported(mvm)) + base_size = IWL_SCAN_REQ_UMAC_SIZE_V7; + else if (iwl_mvm_cdb_scan_api(mvm)) + base_size = IWL_SCAN_REQ_UMAC_SIZE_V6; + else + base_size = IWL_SCAN_REQ_UMAC_SIZE_V1; + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + if (iwl_mvm_is_scan_ext_chan_supported(mvm)) + tail_size = sizeof(struct iwl_scan_req_umac_tail_v2); + else + tail_size = sizeof(struct iwl_scan_req_umac_tail_v1); + + return base_size + + sizeof(struct iwl_scan_channel_cfg_umac) * + mvm->fw->ucode_capa.n_scan_channels + + tail_size; + } + return sizeof(struct iwl_scan_req_lmac) + + sizeof(struct iwl_scan_channel_cfg_lmac) * + mvm->fw->ucode_capa.n_scan_channels + + sizeof(struct iwl_scan_probe_req_v1); +} + +/* + * This function is used in nic restart flow, to inform mac80211 about scans + * that was aborted by restart flow or by an assert. + */ +void iwl_mvm_report_scan_aborted(struct iwl_mvm *mvm) +{ + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + int uid, i; + + uid = iwl_mvm_scan_uid_by_status(mvm, IWL_MVM_SCAN_REGULAR); + if (uid >= 0) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + cancel_delayed_work(&mvm->scan_timeout_dwork); + + ieee80211_scan_completed(mvm->hw, &info); + mvm->scan_uid_status[uid] = 0; + } + uid = iwl_mvm_scan_uid_by_status(mvm, IWL_MVM_SCAN_SCHED); + if (uid >= 0) { + /* Sched scan will be restarted by mac80211 in + * restart_hw, so do not report if FW is about to be + * restarted. + */ + if (!mvm->fw_restart) + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + mvm->scan_uid_status[uid] = 0; + } + uid = iwl_mvm_scan_uid_by_status(mvm, + IWL_MVM_SCAN_STOPPING_REGULAR); + if (uid >= 0) + mvm->scan_uid_status[uid] = 0; + + uid = iwl_mvm_scan_uid_by_status(mvm, + IWL_MVM_SCAN_STOPPING_SCHED); + if (uid >= 0) + mvm->scan_uid_status[uid] = 0; + + /* We shouldn't have any UIDs still set. Loop over all the + * UIDs to make sure there's nothing left there and warn if + * any is found. + */ + for (i = 0; i < mvm->max_scans; i++) { + if (WARN_ONCE(mvm->scan_uid_status[i], + "UMAC scan UID %d status was not cleaned\n", + i)) + mvm->scan_uid_status[i] = 0; + } + } else { + if (mvm->scan_status & IWL_MVM_SCAN_REGULAR) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + cancel_delayed_work(&mvm->scan_timeout_dwork); + ieee80211_scan_completed(mvm->hw, &info); + } + + /* Sched scan will be restarted by mac80211 in + * restart_hw, so do not report if FW is about to be + * restarted. + */ + if ((mvm->scan_status & IWL_MVM_SCAN_SCHED) && + !mvm->fw_restart) { + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } + } +} + +int iwl_mvm_scan_stop(struct iwl_mvm *mvm, int type, bool notify) +{ + int ret; + + if (!(mvm->scan_status & type)) + return 0; + + if (iwl_mvm_is_radio_killed(mvm)) { + ret = 0; + goto out; + } + + ret = iwl_mvm_scan_stop_wait(mvm, type); + if (!ret) + mvm->scan_status |= type << IWL_MVM_SCAN_STOPPING_SHIFT; +out: + /* Clear the scan status so the next scan requests will + * succeed and mark the scan as stopping, so that the Rx + * handler doesn't do anything, as the scan was stopped from + * above. + */ + mvm->scan_status &= ~type; + + if (type == IWL_MVM_SCAN_REGULAR) { + cancel_delayed_work(&mvm->scan_timeout_dwork); + if (notify) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + ieee80211_scan_completed(mvm->hw, &info); + } + } else if (notify) { + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } + + return ret; +} diff --git a/sys/contrib/dev/iwlwifi/mvm/sf.c b/sys/contrib/dev/iwlwifi/mvm/sf.c new file mode 100644 index 00000000000..655da8856c7 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/sf.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2013-2014, 2018-2019 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + */ +#include "mvm.h" + +/* For counting bound interfaces */ +struct iwl_mvm_active_iface_iterator_data { + struct ieee80211_vif *ignore_vif; + u8 sta_vif_ap_sta_id; + enum iwl_sf_state sta_vif_state; + u32 num_active_macs; +}; + +/* + * Count bound interfaces which are not p2p, besides data->ignore_vif. + * data->station_vif will point to one bound vif of type station, if exists. + */ +static void iwl_mvm_bound_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_active_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif == data->ignore_vif || !mvmvif->phy_ctxt || + vif->type == NL80211_IFTYPE_P2P_DEVICE) + return; + + data->num_active_macs++; + + if (vif->type == NL80211_IFTYPE_STATION) { + data->sta_vif_ap_sta_id = mvmvif->ap_sta_id; + if (vif->bss_conf.assoc) + data->sta_vif_state = SF_FULL_ON; + else + data->sta_vif_state = SF_INIT_OFF; + } +} + +/* + * Aging and idle timeouts for the different possible scenarios + * in default configuration + */ +static const +__le32 sf_full_timeout_def[SF_NUM_SCENARIO][SF_NUM_TIMEOUT_TYPES] = { + { + cpu_to_le32(SF_SINGLE_UNICAST_AGING_TIMER_DEF), + cpu_to_le32(SF_SINGLE_UNICAST_IDLE_TIMER_DEF) + }, + { + cpu_to_le32(SF_AGG_UNICAST_AGING_TIMER_DEF), + cpu_to_le32(SF_AGG_UNICAST_IDLE_TIMER_DEF) + }, + { + cpu_to_le32(SF_MCAST_AGING_TIMER_DEF), + cpu_to_le32(SF_MCAST_IDLE_TIMER_DEF) + }, + { + cpu_to_le32(SF_BA_AGING_TIMER_DEF), + cpu_to_le32(SF_BA_IDLE_TIMER_DEF) + }, + { + cpu_to_le32(SF_TX_RE_AGING_TIMER_DEF), + cpu_to_le32(SF_TX_RE_IDLE_TIMER_DEF) + }, +}; + +/* + * Aging and idle timeouts for the different possible scenarios + * in single BSS MAC configuration. + */ +static const __le32 sf_full_timeout[SF_NUM_SCENARIO][SF_NUM_TIMEOUT_TYPES] = { + { + cpu_to_le32(SF_SINGLE_UNICAST_AGING_TIMER), + cpu_to_le32(SF_SINGLE_UNICAST_IDLE_TIMER) + }, + { + cpu_to_le32(SF_AGG_UNICAST_AGING_TIMER), + cpu_to_le32(SF_AGG_UNICAST_IDLE_TIMER) + }, + { + cpu_to_le32(SF_MCAST_AGING_TIMER), + cpu_to_le32(SF_MCAST_IDLE_TIMER) + }, + { + cpu_to_le32(SF_BA_AGING_TIMER), + cpu_to_le32(SF_BA_IDLE_TIMER) + }, + { + cpu_to_le32(SF_TX_RE_AGING_TIMER), + cpu_to_le32(SF_TX_RE_IDLE_TIMER) + }, +}; + +static void iwl_mvm_fill_sf_command(struct iwl_mvm *mvm, + struct iwl_sf_cfg_cmd *sf_cmd, + struct ieee80211_sta *sta) +{ + int i, j, watermark; + + sf_cmd->watermark[SF_LONG_DELAY_ON] = cpu_to_le32(SF_W_MARK_SCAN); + + /* + * If we are in association flow - check antenna configuration + * capabilities of the AP station, and choose the watermark accordingly. + */ + if (sta) { + if (sta->ht_cap.ht_supported || + sta->vht_cap.vht_supported || + sta->he_cap.has_he) { + switch (sta->rx_nss) { + case 1: + watermark = SF_W_MARK_SISO; + break; + case 2: + watermark = SF_W_MARK_MIMO2; + break; + default: + watermark = SF_W_MARK_MIMO3; + break; + } + } else { + watermark = SF_W_MARK_LEGACY; + } + /* default watermark value for unassociated mode. */ + } else { + watermark = SF_W_MARK_MIMO2; + } + sf_cmd->watermark[SF_FULL_ON] = cpu_to_le32(watermark); + + for (i = 0; i < SF_NUM_SCENARIO; i++) { + for (j = 0; j < SF_NUM_TIMEOUT_TYPES; j++) { + sf_cmd->long_delay_timeouts[i][j] = + cpu_to_le32(SF_LONG_DELAY_AGING_TIMER); + } + } + + if (sta) { + BUILD_BUG_ON(sizeof(sf_full_timeout) != + sizeof(__le32) * SF_NUM_SCENARIO * + SF_NUM_TIMEOUT_TYPES); + + memcpy(sf_cmd->full_on_timeouts, sf_full_timeout, + sizeof(sf_full_timeout)); + } else { + BUILD_BUG_ON(sizeof(sf_full_timeout_def) != + sizeof(__le32) * SF_NUM_SCENARIO * + SF_NUM_TIMEOUT_TYPES); + + memcpy(sf_cmd->full_on_timeouts, sf_full_timeout_def, + sizeof(sf_full_timeout_def)); + } + +} + +static int iwl_mvm_sf_config(struct iwl_mvm *mvm, u8 sta_id, + enum iwl_sf_state new_state) +{ + struct iwl_sf_cfg_cmd sf_cmd = { + .state = cpu_to_le32(new_state), + }; + struct ieee80211_sta *sta; + int ret = 0; + + if (mvm->cfg->disable_dummy_notification) + sf_cmd.state |= cpu_to_le32(SF_CFG_DUMMY_NOTIF_OFF); + + /* + * If an associated AP sta changed its antenna configuration, the state + * will remain FULL_ON but SF parameters need to be reconsidered. + */ + if (new_state != SF_FULL_ON && mvm->sf_state == new_state) + return 0; + + switch (new_state) { + case SF_UNINIT: + iwl_mvm_fill_sf_command(mvm, &sf_cmd, NULL); + break; + case SF_FULL_ON: + if (sta_id == IWL_MVM_INVALID_STA) { + IWL_ERR(mvm, + "No station: Cannot switch SF to FULL_ON\n"); + return -EINVAL; + } + rcu_read_lock(); + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (IS_ERR_OR_NULL(sta)) { + IWL_ERR(mvm, "Invalid station id\n"); + rcu_read_unlock(); + return -EINVAL; + } + iwl_mvm_fill_sf_command(mvm, &sf_cmd, sta); + rcu_read_unlock(); + break; + case SF_INIT_OFF: + iwl_mvm_fill_sf_command(mvm, &sf_cmd, NULL); + break; + default: + WARN_ONCE(1, "Invalid state: %d. not sending Smart Fifo cmd\n", + new_state); + return -EINVAL; + } + + ret = iwl_mvm_send_cmd_pdu(mvm, REPLY_SF_CFG_CMD, CMD_ASYNC, + sizeof(sf_cmd), &sf_cmd); + if (!ret) + mvm->sf_state = new_state; + + return ret; +} + +/* + * Update Smart fifo: + * Count bound interfaces that are not to be removed, ignoring p2p devices, + * and set new state accordingly. + */ +int iwl_mvm_sf_update(struct iwl_mvm *mvm, struct ieee80211_vif *changed_vif, + bool remove_vif) +{ + enum iwl_sf_state new_state; + u8 sta_id = IWL_MVM_INVALID_STA; + struct iwl_mvm_vif *mvmvif = NULL; + struct iwl_mvm_active_iface_iterator_data data = { + .ignore_vif = changed_vif, + .sta_vif_state = SF_UNINIT, + .sta_vif_ap_sta_id = IWL_MVM_INVALID_STA, + }; + + /* + * Ignore the call if we are in HW Restart flow, or if the handled + * vif is a p2p device. + */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) || + (changed_vif && changed_vif->type == NL80211_IFTYPE_P2P_DEVICE)) + return 0; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bound_iface_iterator, + &data); + + /* If changed_vif exists and is not to be removed, add to the count */ + if (changed_vif && !remove_vif) + data.num_active_macs++; + + switch (data.num_active_macs) { + case 0: + /* If there are no active macs - change state to SF_INIT_OFF */ + new_state = SF_INIT_OFF; + break; + case 1: + if (remove_vif) { + /* The one active mac left is of type station + * and we filled the relevant data during iteration + */ + new_state = data.sta_vif_state; + sta_id = data.sta_vif_ap_sta_id; + } else { + if (WARN_ON(!changed_vif)) + return -EINVAL; + if (changed_vif->type != NL80211_IFTYPE_STATION) { + new_state = SF_UNINIT; + } else if (changed_vif->bss_conf.assoc && + changed_vif->bss_conf.dtim_period) { + mvmvif = iwl_mvm_vif_from_mac80211(changed_vif); + sta_id = mvmvif->ap_sta_id; + new_state = SF_FULL_ON; + } else { + new_state = SF_INIT_OFF; + } + } + break; + default: + /* If there are multiple active macs - change to SF_UNINIT */ + new_state = SF_UNINIT; + } + return iwl_mvm_sf_config(mvm, sta_id, new_state); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/sta.c b/sys/contrib/dev/iwlwifi/mvm/sta.c new file mode 100644 index 00000000000..f7f2263c3ca --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/sta.c @@ -0,0 +1,3993 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2015, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#if defined(__FreeBSD__) +#include +#endif + +#include "mvm.h" +#include "sta.h" +#include "rs.h" + +/* + * New version of ADD_STA_sta command added new fields at the end of the + * structure, so sending the size of the relevant API's structure is enough to + * support both API versions. + */ +static inline int iwl_mvm_add_sta_cmd_size(struct iwl_mvm *mvm) +{ + if (iwl_mvm_has_new_rx_api(mvm) || + fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + return sizeof(struct iwl_mvm_add_sta_cmd); + else + return sizeof(struct iwl_mvm_add_sta_cmd_v7); +} + +static int iwl_mvm_find_free_sta_id(struct iwl_mvm *mvm, + enum nl80211_iftype iftype) +{ + int sta_id; + u32 reserved_ids = 0; + + BUILD_BUG_ON(IWL_MVM_STATION_COUNT_MAX > 32); + WARN_ON_ONCE(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)); + + lockdep_assert_held(&mvm->mutex); + + /* d0i3/d3 assumes the AP's sta_id (of sta vif) is 0. reserve it. */ + if (iftype != NL80211_IFTYPE_STATION) + reserved_ids = BIT(0); + + /* Don't take rcu_read_lock() since we are protected by mvm->mutex */ + for (sta_id = 0; sta_id < mvm->fw->ucode_capa.num_stations; sta_id++) { + if (BIT(sta_id) & reserved_ids) + continue; + + if (!rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex))) + return sta_id; + } + return IWL_MVM_INVALID_STA; +} + +/* send station add/update command to firmware */ +int iwl_mvm_sta_send_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + bool update, unsigned int flags) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_add_sta_cmd add_sta_cmd = { + .sta_id = mvm_sta->sta_id, + .mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color), + .add_modify = update ? 1 : 0, + .station_flags_msk = cpu_to_le32(STA_FLG_FAT_EN_MSK | + STA_FLG_MIMO_EN_MSK | + STA_FLG_RTS_MIMO_PROT), + .tid_disable_tx = cpu_to_le16(mvm_sta->tid_disable_agg), + }; + int ret; + u32 status; + u32 agg_size = 0, mpdu_dens = 0; + + if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + add_sta_cmd.station_type = mvm_sta->sta_type; + + if (!update || (flags & STA_MODIFY_QUEUES)) { + memcpy(&add_sta_cmd.addr, sta->addr, ETH_ALEN); + + if (!iwl_mvm_has_new_tx_api(mvm)) { + add_sta_cmd.tfd_queue_msk = + cpu_to_le32(mvm_sta->tfd_queue_msk); + + if (flags & STA_MODIFY_QUEUES) + add_sta_cmd.modify_mask |= STA_MODIFY_QUEUES; + } else { + WARN_ON(flags & STA_MODIFY_QUEUES); + } + } + + switch (sta->bandwidth) { + case IEEE80211_STA_RX_BW_160: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_160MHZ); + fallthrough; + case IEEE80211_STA_RX_BW_80: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_80MHZ); + fallthrough; + case IEEE80211_STA_RX_BW_40: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_40MHZ); + fallthrough; + case IEEE80211_STA_RX_BW_20: + if (sta->ht_cap.ht_supported) + add_sta_cmd.station_flags |= + cpu_to_le32(STA_FLG_FAT_EN_20MHZ); + break; + } + + switch (sta->rx_nss) { + case 1: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_SISO); + break; + case 2: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_MIMO2); + break; + case 3 ... 8: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_MIMO3); + break; + } + + switch (sta->smps_mode) { + case IEEE80211_SMPS_AUTOMATIC: + case IEEE80211_SMPS_NUM_MODES: + WARN_ON(1); + break; + case IEEE80211_SMPS_STATIC: + /* override NSS */ + add_sta_cmd.station_flags &= ~cpu_to_le32(STA_FLG_MIMO_EN_MSK); + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_SISO); + break; + case IEEE80211_SMPS_DYNAMIC: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_RTS_MIMO_PROT); + break; + case IEEE80211_SMPS_OFF: + /* nothing */ + break; + } + + if (sta->ht_cap.ht_supported) { + add_sta_cmd.station_flags_msk |= + cpu_to_le32(STA_FLG_MAX_AGG_SIZE_MSK | + STA_FLG_AGG_MPDU_DENS_MSK); + + mpdu_dens = sta->ht_cap.ampdu_density; + } + + if (mvm_sta->vif->bss_conf.chandef.chan->band == NL80211_BAND_6GHZ) { + add_sta_cmd.station_flags_msk |= + cpu_to_le32(STA_FLG_MAX_AGG_SIZE_MSK | + STA_FLG_AGG_MPDU_DENS_MSK); + + mpdu_dens = le16_get_bits(sta->he_6ghz_capa.capa, + IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START); + agg_size = le16_get_bits(sta->he_6ghz_capa.capa, + IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP); + } else + if (sta->vht_cap.vht_supported) { + agg_size = sta->vht_cap.cap & + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; + agg_size >>= + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; + } else if (sta->ht_cap.ht_supported) { + agg_size = sta->ht_cap.ampdu_factor; + } + + /* D6.0 10.12.2 A-MPDU length limit rules + * A STA indicates the maximum length of the A-MPDU preEOF padding + * that it can receive in an HE PPDU in the Maximum A-MPDU Length + * Exponent field in its HT Capabilities, VHT Capabilities, + * and HE 6 GHz Band Capabilities elements (if present) and the + * Maximum AMPDU Length Exponent Extension field in its HE + * Capabilities element + */ + if (sta->he_cap.has_he) + agg_size += u8_get_bits(sta->he_cap.he_cap_elem.mac_cap_info[3], + IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_MASK); + + /* Limit to max A-MPDU supported by FW */ + if (agg_size > (STA_FLG_MAX_AGG_SIZE_4M >> STA_FLG_MAX_AGG_SIZE_SHIFT)) + agg_size = (STA_FLG_MAX_AGG_SIZE_4M >> + STA_FLG_MAX_AGG_SIZE_SHIFT); + + add_sta_cmd.station_flags |= + cpu_to_le32(agg_size << STA_FLG_MAX_AGG_SIZE_SHIFT); + add_sta_cmd.station_flags |= + cpu_to_le32(mpdu_dens << STA_FLG_AGG_MPDU_DENS_SHIFT); + if (mvm_sta->sta_state >= IEEE80211_STA_ASSOC) + add_sta_cmd.assoc_id = cpu_to_le16(sta->aid); + + if (sta->wme) { + add_sta_cmd.modify_mask |= STA_MODIFY_UAPSD_ACS; + + if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK) + add_sta_cmd.uapsd_acs |= BIT(AC_BK); + if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) + add_sta_cmd.uapsd_acs |= BIT(AC_BE); + if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI) + add_sta_cmd.uapsd_acs |= BIT(AC_VI); + if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) + add_sta_cmd.uapsd_acs |= BIT(AC_VO); + add_sta_cmd.uapsd_acs |= add_sta_cmd.uapsd_acs << 4; + add_sta_cmd.sp_length = sta->max_sp ? sta->max_sp * 2 : 128; + } + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &add_sta_cmd, &status); + if (ret) + return ret; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + IWL_DEBUG_ASSOC(mvm, "ADD_STA PASSED\n"); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "ADD_STA failed\n"); + break; + } + + return ret; +} + +static void iwl_mvm_rx_agg_session_expired(struct timer_list *t) +{ + struct iwl_mvm_baid_data *data = + from_timer(data, t, session_timer); + struct iwl_mvm_baid_data __rcu **rcu_ptr = data->rcu_ptr; + struct iwl_mvm_baid_data *ba_data; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvm_sta; + unsigned long timeout; + + rcu_read_lock(); + + ba_data = rcu_dereference(*rcu_ptr); + + if (WARN_ON(!ba_data)) + goto unlock; + + if (!ba_data->timeout) + goto unlock; + + timeout = ba_data->last_rx + TU_TO_JIFFIES(ba_data->timeout * 2); + if (time_is_after_jiffies(timeout)) { + mod_timer(&ba_data->session_timer, timeout); + goto unlock; + } + + /* Timer expired */ + sta = rcu_dereference(ba_data->mvm->fw_id_to_mac_id[ba_data->sta_id]); + + /* + * sta should be valid unless the following happens: + * The firmware asserts which triggers a reconfig flow, but + * the reconfig fails before we set the pointer to sta into + * the fw_id_to_mac_id pointer table. Mac80211 can't stop + * A-MDPU and hence the timer continues to run. Then, the + * timer expires and sta is NULL. + */ + if (!sta) + goto unlock; + + mvm_sta = iwl_mvm_sta_from_mac80211(sta); + ieee80211_rx_ba_timer_expired(mvm_sta->vif, + sta->addr, ba_data->tid); +unlock: + rcu_read_unlock(); +} + +/* Disable aggregations for a bitmap of TIDs for a given station */ +static int iwl_mvm_invalidate_sta_queue(struct iwl_mvm *mvm, int queue, + unsigned long disable_agg_tids, + bool remove_queue) +{ + struct iwl_mvm_add_sta_cmd cmd = {}; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + u32 status; + u8 sta_id; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + sta_id = mvm->queue_info[queue].ra_sta_id; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) { + rcu_read_unlock(); + return -EINVAL; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + mvmsta->tid_disable_agg |= disable_agg_tids; + + cmd.mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color); + cmd.sta_id = mvmsta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + cmd.modify_mask = STA_MODIFY_QUEUES; + if (disable_agg_tids) + cmd.modify_mask |= STA_MODIFY_TID_DISABLE_TX; + if (remove_queue) + cmd.modify_mask |= STA_MODIFY_QUEUE_REMOVAL; + cmd.tfd_queue_msk = cpu_to_le32(mvmsta->tfd_queue_msk); + cmd.tid_disable_tx = cpu_to_le16(mvmsta->tid_disable_agg); + + rcu_read_unlock(); + + /* Notify FW of queue removal from the STA queues */ + status = ADD_STA_SUCCESS; + return iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); +} + +static int iwl_mvm_disable_txq(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + u16 *queueptr, u8 tid, u8 flags) +{ + int queue = *queueptr; + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_DISABLE_QUEUE, + }; + int ret; + + if (iwl_mvm_has_new_tx_api(mvm)) { + iwl_trans_txq_free(mvm->trans, queue); + *queueptr = IWL_MVM_INVALID_QUEUE; + + return 0; + } + + if (WARN_ON(mvm->queue_info[queue].tid_bitmap == 0)) + return 0; + + mvm->queue_info[queue].tid_bitmap &= ~BIT(tid); + + cmd.action = mvm->queue_info[queue].tid_bitmap ? + SCD_CFG_ENABLE_QUEUE : SCD_CFG_DISABLE_QUEUE; + if (cmd.action == SCD_CFG_DISABLE_QUEUE) + mvm->queue_info[queue].status = IWL_MVM_QUEUE_FREE; + + IWL_DEBUG_TX_QUEUES(mvm, + "Disabling TXQ #%d tids=0x%x\n", + queue, + mvm->queue_info[queue].tid_bitmap); + + /* If the queue is still enabled - nothing left to do in this func */ + if (cmd.action == SCD_CFG_ENABLE_QUEUE) + return 0; + + cmd.sta_id = mvm->queue_info[queue].ra_sta_id; + cmd.tid = mvm->queue_info[queue].txq_tid; + + /* Make sure queue info is correct even though we overwrite it */ + WARN(mvm->queue_info[queue].tid_bitmap, + "TXQ #%d info out-of-sync - tids=0x%x\n", + queue, mvm->queue_info[queue].tid_bitmap); + + /* If we are here - the queue is freed and we can zero out these vals */ + mvm->queue_info[queue].tid_bitmap = 0; + + if (sta) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_tid(sta, tid); + + mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE; + } + + /* Regardless if this is a reserved TXQ for a STA - mark it as false */ + mvm->queue_info[queue].reserved = false; + + iwl_trans_txq_disable(mvm->trans, queue, false); + ret = iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, flags, + sizeof(struct iwl_scd_txq_cfg_cmd), &cmd); + + if (ret) + IWL_ERR(mvm, "Failed to disable queue %d (ret=%d)\n", + queue, ret); + return ret; +} + +static int iwl_mvm_get_queue_agg_tids(struct iwl_mvm *mvm, int queue) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + unsigned long tid_bitmap; + unsigned long agg_tids = 0; + u8 sta_id; + int tid; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + sta_id = mvm->queue_info[queue].ra_sta_id; + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) + return -EINVAL; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock_bh(&mvmsta->lock); + for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + if (mvmsta->tid_data[tid].state == IWL_AGG_ON) + agg_tids |= BIT(tid); + } + spin_unlock_bh(&mvmsta->lock); + + return agg_tids; +} + +/* + * Remove a queue from a station's resources. + * Note that this only marks as free. It DOESN'T delete a BA agreement, and + * doesn't disable the queue + */ +static int iwl_mvm_remove_sta_queue_marking(struct iwl_mvm *mvm, int queue) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + unsigned long tid_bitmap; + unsigned long disable_agg_tids = 0; + u8 sta_id; + int tid; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + sta_id = mvm->queue_info[queue].ra_sta_id; + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) { + rcu_read_unlock(); + return 0; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock_bh(&mvmsta->lock); + /* Unmap MAC queues and TIDs from this queue */ + for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_tid(sta, tid); + + if (mvmsta->tid_data[tid].state == IWL_AGG_ON) + disable_agg_tids |= BIT(tid); + mvmsta->tid_data[tid].txq_id = IWL_MVM_INVALID_QUEUE; + + mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE; + } + + mvmsta->tfd_queue_msk &= ~BIT(queue); /* Don't use this queue anymore */ + spin_unlock_bh(&mvmsta->lock); + + rcu_read_unlock(); + + /* + * The TX path may have been using this TXQ_ID from the tid_data, + * so make sure it's no longer running so that we can safely reuse + * this TXQ later. We've set all the TIDs to IWL_MVM_INVALID_QUEUE + * above, but nothing guarantees we've stopped using them. Thus, + * without this, we could get to iwl_mvm_disable_txq() and remove + * the queue while still sending frames to it. + */ + synchronize_net(); + + return disable_agg_tids; +} + +static int iwl_mvm_free_inactive_queue(struct iwl_mvm *mvm, int queue, + struct ieee80211_sta *old_sta, + u8 new_sta_id) +{ + struct iwl_mvm_sta *mvmsta; + u8 sta_id, tid; + unsigned long disable_agg_tids = 0; + bool same_sta; + u16 queue_tmp = queue; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + sta_id = mvm->queue_info[queue].ra_sta_id; + tid = mvm->queue_info[queue].txq_tid; + + same_sta = sta_id == new_sta_id; + + mvmsta = iwl_mvm_sta_from_staid_protected(mvm, sta_id); + if (WARN_ON(!mvmsta)) + return -EINVAL; + + disable_agg_tids = iwl_mvm_remove_sta_queue_marking(mvm, queue); + /* Disable the queue */ + if (disable_agg_tids) + iwl_mvm_invalidate_sta_queue(mvm, queue, + disable_agg_tids, false); + + ret = iwl_mvm_disable_txq(mvm, old_sta, &queue_tmp, tid, 0); + if (ret) { + IWL_ERR(mvm, + "Failed to free inactive queue %d (ret=%d)\n", + queue, ret); + + return ret; + } + + /* If TXQ is allocated to another STA, update removal in FW */ + if (!same_sta) + iwl_mvm_invalidate_sta_queue(mvm, queue, 0, true); + + return 0; +} + +static int iwl_mvm_get_shared_queue(struct iwl_mvm *mvm, + unsigned long tfd_queue_mask, u8 ac) +{ + int queue = 0; + u8 ac_to_queue[IEEE80211_NUM_ACS]; + int i; + + /* + * This protects us against grabbing a queue that's being reconfigured + * by the inactivity checker. + */ + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + memset(&ac_to_queue, IEEE80211_INVAL_HW_QUEUE, sizeof(ac_to_queue)); + + /* See what ACs the existing queues for this STA have */ + for_each_set_bit(i, &tfd_queue_mask, IWL_MVM_DQA_MAX_DATA_QUEUE) { + /* Only DATA queues can be shared */ + if (i < IWL_MVM_DQA_MIN_DATA_QUEUE && + i != IWL_MVM_DQA_BSS_CLIENT_QUEUE) + continue; + + ac_to_queue[mvm->queue_info[i].mac80211_ac] = i; + } + + /* + * The queue to share is chosen only from DATA queues as follows (in + * descending priority): + * 1. An AC_BE queue + * 2. Same AC queue + * 3. Highest AC queue that is lower than new AC + * 4. Any existing AC (there always is at least 1 DATA queue) + */ + + /* Priority 1: An AC_BE queue */ + if (ac_to_queue[IEEE80211_AC_BE] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_BE]; + /* Priority 2: Same AC queue */ + else if (ac_to_queue[ac] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[ac]; + /* Priority 3a: If new AC is VO and VI exists - use VI */ + else if (ac == IEEE80211_AC_VO && + ac_to_queue[IEEE80211_AC_VI] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_VI]; + /* Priority 3b: No BE so only AC less than the new one is BK */ + else if (ac_to_queue[IEEE80211_AC_BK] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_BK]; + /* Priority 4a: No BE nor BK - use VI if exists */ + else if (ac_to_queue[IEEE80211_AC_VI] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_VI]; + /* Priority 4b: No BE, BK nor VI - use VO if exists */ + else if (ac_to_queue[IEEE80211_AC_VO] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_VO]; + + /* Make sure queue found (or not) is legal */ + if (!iwl_mvm_is_dqa_data_queue(mvm, queue) && + !iwl_mvm_is_dqa_mgmt_queue(mvm, queue) && + (queue != IWL_MVM_DQA_BSS_CLIENT_QUEUE)) { + IWL_ERR(mvm, "No DATA queues available to share\n"); + return -ENOSPC; + } + + return queue; +} + +/* + * If a given queue has a higher AC than the TID stream that is being compared + * to, the queue needs to be redirected to the lower AC. This function does that + * in such a case, otherwise - if no redirection required - it does nothing, + * unless the %force param is true. + */ +static int iwl_mvm_redirect_queue(struct iwl_mvm *mvm, int queue, int tid, + int ac, int ssn, unsigned int wdg_timeout, + bool force, struct iwl_mvm_txq *txq) +{ + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_DISABLE_QUEUE, + }; + bool shared_queue; + int ret; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + /* + * If the AC is lower than current one - FIFO needs to be redirected to + * the lowest one of the streams in the queue. Check if this is needed + * here. + * Notice that the enum ieee80211_ac_numbers is "flipped", so BK is with + * value 3 and VO with value 0, so to check if ac X is lower than ac Y + * we need to check if the numerical value of X is LARGER than of Y. + */ + if (ac <= mvm->queue_info[queue].mac80211_ac && !force) { + IWL_DEBUG_TX_QUEUES(mvm, + "No redirection needed on TXQ #%d\n", + queue); + return 0; + } + + cmd.sta_id = mvm->queue_info[queue].ra_sta_id; + cmd.tx_fifo = iwl_mvm_ac_to_tx_fifo[mvm->queue_info[queue].mac80211_ac]; + cmd.tid = mvm->queue_info[queue].txq_tid; + shared_queue = hweight16(mvm->queue_info[queue].tid_bitmap) > 1; + + IWL_DEBUG_TX_QUEUES(mvm, "Redirecting TXQ #%d to FIFO #%d\n", + queue, iwl_mvm_ac_to_tx_fifo[ac]); + + /* Stop the queue and wait for it to empty */ + txq->stopped = true; + + ret = iwl_trans_wait_tx_queues_empty(mvm->trans, BIT(queue)); + if (ret) { + IWL_ERR(mvm, "Error draining queue %d before reconfig\n", + queue); + ret = -EIO; + goto out; + } + + /* Before redirecting the queue we need to de-activate it */ + iwl_trans_txq_disable(mvm->trans, queue, false); + ret = iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, 0, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(mvm, "Failed SCD disable TXQ %d (ret=%d)\n", queue, + ret); + + /* Make sure the SCD wrptr is correctly set before reconfiguring */ + iwl_trans_txq_enable_cfg(mvm->trans, queue, ssn, NULL, wdg_timeout); + + /* Update the TID "owner" of the queue */ + mvm->queue_info[queue].txq_tid = tid; + + /* TODO: Work-around SCD bug when moving back by multiples of 0x40 */ + + /* Redirect to lower AC */ + iwl_mvm_reconfig_scd(mvm, queue, iwl_mvm_ac_to_tx_fifo[ac], + cmd.sta_id, tid, IWL_FRAME_LIMIT, ssn); + + /* Update AC marking of the queue */ + mvm->queue_info[queue].mac80211_ac = ac; + + /* + * Mark queue as shared in transport if shared + * Note this has to be done after queue enablement because enablement + * can also set this value, and there is no indication there to shared + * queues + */ + if (shared_queue) + iwl_trans_txq_set_shared_mode(mvm->trans, queue, true); + +out: + /* Continue using the queue */ + txq->stopped = false; + + return ret; +} + +static int iwl_mvm_find_free_queue(struct iwl_mvm *mvm, u8 sta_id, + u8 minq, u8 maxq) +{ + int i; + + lockdep_assert_held(&mvm->mutex); + + if (WARN(maxq >= mvm->trans->trans_cfg->base_params->num_of_queues, + "max queue %d >= num_of_queues (%d)", maxq, + mvm->trans->trans_cfg->base_params->num_of_queues)) + maxq = mvm->trans->trans_cfg->base_params->num_of_queues - 1; + + /* This should not be hit with new TX path */ + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -ENOSPC; + + /* Start by looking for a free queue */ + for (i = minq; i <= maxq; i++) + if (mvm->queue_info[i].tid_bitmap == 0 && + mvm->queue_info[i].status == IWL_MVM_QUEUE_FREE) + return i; + + return -ENOSPC; +} + +static int iwl_mvm_tvqm_enable_txq(struct iwl_mvm *mvm, + u8 sta_id, u8 tid, unsigned int timeout) +{ + int queue, size = max_t(u32, IWL_DEFAULT_QUEUE_SIZE, + mvm->trans->cfg->min_256_ba_txq_size); + + if (tid == IWL_MAX_TID_COUNT) { + tid = IWL_MGMT_TID; + size = max_t(u32, IWL_MGMT_QUEUE_SIZE, + mvm->trans->cfg->min_txq_size); + } + + do { + __le16 enable = cpu_to_le16(TX_QUEUE_CFG_ENABLE_QUEUE); + + queue = iwl_trans_txq_alloc(mvm->trans, enable, + sta_id, tid, SCD_QUEUE_CFG, + size, timeout); + + if (queue < 0) + IWL_DEBUG_TX_QUEUES(mvm, + "Failed allocating TXQ of size %d for sta %d tid %d, ret: %d\n", + size, sta_id, tid, queue); + size /= 2; + } while (queue < 0 && size >= 16); + + if (queue < 0) + return queue; + + IWL_DEBUG_TX_QUEUES(mvm, "Enabling TXQ #%d for sta %d tid %d\n", + queue, sta_id, tid); + + return queue; +} + +static int iwl_mvm_sta_alloc_queue_tvqm(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, u8 ac, + int tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_tid(sta, tid); + unsigned int wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, mvmsta->vif, false, false); + int queue = -1; + + lockdep_assert_held(&mvm->mutex); + + IWL_DEBUG_TX_QUEUES(mvm, + "Allocating queue for sta %d on tid %d\n", + mvmsta->sta_id, tid); + queue = iwl_mvm_tvqm_enable_txq(mvm, mvmsta->sta_id, tid, wdg_timeout); + if (queue < 0) + return queue; + + mvmtxq->txq_id = queue; + mvm->tvqm_info[queue].txq_tid = tid; + mvm->tvqm_info[queue].sta_id = mvmsta->sta_id; + + IWL_DEBUG_TX_QUEUES(mvm, "Allocated queue is %d\n", queue); + + spin_lock_bh(&mvmsta->lock); + mvmsta->tid_data[tid].txq_id = queue; + spin_unlock_bh(&mvmsta->lock); + + return 0; +} + +static bool iwl_mvm_update_txq_mapping(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + int queue, u8 sta_id, u8 tid) +{ + bool enable_queue = true; + + /* Make sure this TID isn't already enabled */ + if (mvm->queue_info[queue].tid_bitmap & BIT(tid)) { + IWL_ERR(mvm, "Trying to enable TXQ %d with existing TID %d\n", + queue, tid); + return false; + } + + /* Update mappings and refcounts */ + if (mvm->queue_info[queue].tid_bitmap) + enable_queue = false; + + mvm->queue_info[queue].tid_bitmap |= BIT(tid); + mvm->queue_info[queue].ra_sta_id = sta_id; + + if (enable_queue) { + if (tid != IWL_MAX_TID_COUNT) + mvm->queue_info[queue].mac80211_ac = + tid_to_mac80211_ac[tid]; + else + mvm->queue_info[queue].mac80211_ac = IEEE80211_AC_VO; + + mvm->queue_info[queue].txq_tid = tid; + } + + if (sta) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_tid(sta, tid); + + mvmtxq->txq_id = queue; + } + + IWL_DEBUG_TX_QUEUES(mvm, + "Enabling TXQ #%d tids=0x%x\n", + queue, mvm->queue_info[queue].tid_bitmap); + + return enable_queue; +} + +static bool iwl_mvm_enable_txq(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int queue, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int wdg_timeout) +{ + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_ENABLE_QUEUE, + .window = cfg->frame_limit, + .sta_id = cfg->sta_id, + .ssn = cpu_to_le16(ssn), + .tx_fifo = cfg->fifo, + .aggregate = cfg->aggregate, + .tid = cfg->tid, + }; + bool inc_ssn; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return false; + + /* Send the enabling command if we need to */ + if (!iwl_mvm_update_txq_mapping(mvm, sta, queue, cfg->sta_id, cfg->tid)) + return false; + + inc_ssn = iwl_trans_txq_enable_cfg(mvm->trans, queue, ssn, + NULL, wdg_timeout); + if (inc_ssn) + le16_add_cpu(&cmd.ssn, 1); + + WARN(iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, 0, sizeof(cmd), &cmd), + "Failed to configure queue %d on FIFO %d\n", queue, cfg->fifo); + + return inc_ssn; +} + +static void iwl_mvm_change_queue_tid(struct iwl_mvm *mvm, int queue) +{ + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_UPDATE_QUEUE_TID, + }; + int tid; + unsigned long tid_bitmap; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return; + + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + if (WARN(!tid_bitmap, "TXQ %d has no tids assigned to it\n", queue)) + return; + + /* Find any TID for queue */ + tid = find_first_bit(&tid_bitmap, IWL_MAX_TID_COUNT + 1); + cmd.tid = tid; + cmd.tx_fifo = iwl_mvm_ac_to_tx_fifo[tid_to_mac80211_ac[tid]]; + + ret = iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, 0, sizeof(cmd), &cmd); + if (ret) { + IWL_ERR(mvm, "Failed to update owner of TXQ %d (ret=%d)\n", + queue, ret); + return; + } + + mvm->queue_info[queue].txq_tid = tid; + IWL_DEBUG_TX_QUEUES(mvm, "Changed TXQ %d ownership to tid %d\n", + queue, tid); +} + +static void iwl_mvm_unshare_queue(struct iwl_mvm *mvm, int queue) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + u8 sta_id; + int tid = -1; + unsigned long tid_bitmap; + unsigned int wdg_timeout; + int ssn; + int ret = true; + + /* queue sharing is disabled on new TX path */ + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return; + + lockdep_assert_held(&mvm->mutex); + + sta_id = mvm->queue_info[queue].ra_sta_id; + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + /* Find TID for queue, and make sure it is the only one on the queue */ + tid = find_first_bit(&tid_bitmap, IWL_MAX_TID_COUNT + 1); + if (tid_bitmap != BIT(tid)) { + IWL_ERR(mvm, "Failed to unshare q %d, active tids=0x%lx\n", + queue, tid_bitmap); + return; + } + + IWL_DEBUG_TX_QUEUES(mvm, "Unsharing TXQ %d, keeping tid %d\n", queue, + tid); + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) + return; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + wdg_timeout = iwl_mvm_get_wd_timeout(mvm, mvmsta->vif, false, false); + + ssn = IEEE80211_SEQ_TO_SN(mvmsta->tid_data[tid].seq_number); + + ret = iwl_mvm_redirect_queue(mvm, queue, tid, + tid_to_mac80211_ac[tid], ssn, + wdg_timeout, true, + iwl_mvm_txq_from_tid(sta, tid)); + if (ret) { + IWL_ERR(mvm, "Failed to redirect TXQ %d\n", queue); + return; + } + + /* If aggs should be turned back on - do it */ + if (mvmsta->tid_data[tid].state == IWL_AGG_ON) { + struct iwl_mvm_add_sta_cmd cmd = {0}; + + mvmsta->tid_disable_agg &= ~BIT(tid); + + cmd.mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color); + cmd.sta_id = mvmsta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + cmd.modify_mask = STA_MODIFY_TID_DISABLE_TX; + cmd.tfd_queue_msk = cpu_to_le32(mvmsta->tfd_queue_msk); + cmd.tid_disable_tx = cpu_to_le16(mvmsta->tid_disable_agg); + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (!ret) { + IWL_DEBUG_TX_QUEUES(mvm, + "TXQ #%d is now aggregated again\n", + queue); + + /* Mark queue intenally as aggregating again */ + iwl_trans_txq_set_shared_mode(mvm->trans, queue, false); + } + } + + mvm->queue_info[queue].status = IWL_MVM_QUEUE_READY; +} + +/* + * Remove inactive TIDs of a given queue. + * If all queue TIDs are inactive - mark the queue as inactive + * If only some the queue TIDs are inactive - unmap them from the queue + * + * Returns %true if all TIDs were removed and the queue could be reused. + */ +static bool iwl_mvm_remove_inactive_tids(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, int queue, + unsigned long tid_bitmap, + unsigned long *unshare_queues, + unsigned long *changetid_queues) +{ + int tid; + + lockdep_assert_held(&mvmsta->lock); + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return false; + + /* Go over all non-active TIDs, incl. IWL_MAX_TID_COUNT (for mgmt) */ + for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + /* If some TFDs are still queued - don't mark TID as inactive */ + if (iwl_mvm_tid_queued(mvm, &mvmsta->tid_data[tid])) + tid_bitmap &= ~BIT(tid); + + /* Don't mark as inactive any TID that has an active BA */ + if (mvmsta->tid_data[tid].state != IWL_AGG_OFF) + tid_bitmap &= ~BIT(tid); + } + + /* If all TIDs in the queue are inactive - return it can be reused */ + if (tid_bitmap == mvm->queue_info[queue].tid_bitmap) { + IWL_DEBUG_TX_QUEUES(mvm, "Queue %d is inactive\n", queue); + return true; + } + + /* + * If we are here, this is a shared queue and not all TIDs timed-out. + * Remove the ones that did. + */ + for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + u16 tid_bitmap; + + mvmsta->tid_data[tid].txq_id = IWL_MVM_INVALID_QUEUE; + mvm->queue_info[queue].tid_bitmap &= ~BIT(tid); + + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + /* + * We need to take into account a situation in which a TXQ was + * allocated to TID x, and then turned shared by adding TIDs y + * and z. If TID x becomes inactive and is removed from the TXQ, + * ownership must be given to one of the remaining TIDs. + * This is mainly because if TID x continues - a new queue can't + * be allocated for it as long as it is an owner of another TXQ. + * + * Mark this queue in the right bitmap, we'll send the command + * to the firmware later. + */ + if (!(tid_bitmap & BIT(mvm->queue_info[queue].txq_tid))) + set_bit(queue, changetid_queues); + + IWL_DEBUG_TX_QUEUES(mvm, + "Removing inactive TID %d from shared Q:%d\n", + tid, queue); + } + + IWL_DEBUG_TX_QUEUES(mvm, + "TXQ #%d left with tid bitmap 0x%x\n", queue, + mvm->queue_info[queue].tid_bitmap); + + /* + * There may be different TIDs with the same mac queues, so make + * sure all TIDs have existing corresponding mac queues enabled + */ + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + /* If the queue is marked as shared - "unshare" it */ + if (hweight16(mvm->queue_info[queue].tid_bitmap) == 1 && + mvm->queue_info[queue].status == IWL_MVM_QUEUE_SHARED) { + IWL_DEBUG_TX_QUEUES(mvm, "Marking Q:%d for reconfig\n", + queue); + set_bit(queue, unshare_queues); + } + + return false; +} + +/* + * Check for inactivity - this includes checking if any queue + * can be unshared and finding one (and only one) that can be + * reused. + * This function is also invoked as a sort of clean-up task, + * in which case @alloc_for_sta is IWL_MVM_INVALID_STA. + * + * Returns the queue number, or -ENOSPC. + */ +static int iwl_mvm_inactivity_check(struct iwl_mvm *mvm, u8 alloc_for_sta) +{ + unsigned long now = jiffies; + unsigned long unshare_queues = 0; + unsigned long changetid_queues = 0; + int i, ret, free_queue = -ENOSPC; + struct ieee80211_sta *queue_owner = NULL; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_has_new_tx_api(mvm)) + return -ENOSPC; + + rcu_read_lock(); + + /* we skip the CMD queue below by starting at 1 */ + BUILD_BUG_ON(IWL_MVM_DQA_CMD_QUEUE != 0); + + for (i = 1; i < IWL_MAX_HW_QUEUES; i++) { + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + u8 sta_id; + int tid; + unsigned long inactive_tid_bitmap = 0; + unsigned long queue_tid_bitmap; + + queue_tid_bitmap = mvm->queue_info[i].tid_bitmap; + if (!queue_tid_bitmap) + continue; + + /* If TXQ isn't in active use anyway - nothing to do here... */ + if (mvm->queue_info[i].status != IWL_MVM_QUEUE_READY && + mvm->queue_info[i].status != IWL_MVM_QUEUE_SHARED) + continue; + + /* Check to see if there are inactive TIDs on this queue */ + for_each_set_bit(tid, &queue_tid_bitmap, + IWL_MAX_TID_COUNT + 1) { + if (time_after(mvm->queue_info[i].last_frame_time[tid] + + IWL_MVM_DQA_QUEUE_TIMEOUT, now)) + continue; + + inactive_tid_bitmap |= BIT(tid); + } + + /* If all TIDs are active - finish check on this queue */ + if (!inactive_tid_bitmap) + continue; + + /* + * If we are here - the queue hadn't been served recently and is + * in use + */ + + sta_id = mvm->queue_info[i].ra_sta_id; + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + /* + * If the STA doesn't exist anymore, it isn't an error. It could + * be that it was removed since getting the queues, and in this + * case it should've inactivated its queues anyway. + */ + if (IS_ERR_OR_NULL(sta)) + continue; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock_bh(&mvmsta->lock); + ret = iwl_mvm_remove_inactive_tids(mvm, mvmsta, i, + inactive_tid_bitmap, + &unshare_queues, + &changetid_queues); + if (ret && free_queue < 0) { + queue_owner = sta; + free_queue = i; + } + /* only unlock sta lock - we still need the queue info lock */ + spin_unlock_bh(&mvmsta->lock); + } + + + /* Reconfigure queues requiring reconfiguation */ + for_each_set_bit(i, &unshare_queues, IWL_MAX_HW_QUEUES) + iwl_mvm_unshare_queue(mvm, i); + for_each_set_bit(i, &changetid_queues, IWL_MAX_HW_QUEUES) + iwl_mvm_change_queue_tid(mvm, i); + + rcu_read_unlock(); + + if (free_queue >= 0 && alloc_for_sta != IWL_MVM_INVALID_STA) { + ret = iwl_mvm_free_inactive_queue(mvm, free_queue, queue_owner, + alloc_for_sta); + if (ret) + return ret; + } + + return free_queue; +} + +static int iwl_mvm_sta_alloc_queue(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, u8 ac, int tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = iwl_mvm_mac_ac_to_tx_fifo(mvm, ac), + .sta_id = mvmsta->sta_id, + .tid = tid, + .frame_limit = IWL_FRAME_LIMIT, + }; + unsigned int wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, mvmsta->vif, false, false); + int queue = -1; + u16 queue_tmp; + unsigned long disable_agg_tids = 0; + enum iwl_mvm_agg_state queue_state; + bool shared_queue = false, inc_ssn; + int ssn; + unsigned long tfd_queue_mask; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_has_new_tx_api(mvm)) + return iwl_mvm_sta_alloc_queue_tvqm(mvm, sta, ac, tid); + + spin_lock_bh(&mvmsta->lock); + tfd_queue_mask = mvmsta->tfd_queue_msk; + ssn = IEEE80211_SEQ_TO_SN(mvmsta->tid_data[tid].seq_number); + spin_unlock_bh(&mvmsta->lock); + + if (tid == IWL_MAX_TID_COUNT) { + queue = iwl_mvm_find_free_queue(mvm, mvmsta->sta_id, + IWL_MVM_DQA_MIN_MGMT_QUEUE, + IWL_MVM_DQA_MAX_MGMT_QUEUE); + if (queue >= IWL_MVM_DQA_MIN_MGMT_QUEUE) + IWL_DEBUG_TX_QUEUES(mvm, "Found free MGMT queue #%d\n", + queue); + + /* If no such queue is found, we'll use a DATA queue instead */ + } + + if ((queue < 0 && mvmsta->reserved_queue != IEEE80211_INVAL_HW_QUEUE) && + (mvm->queue_info[mvmsta->reserved_queue].status == + IWL_MVM_QUEUE_RESERVED)) { + queue = mvmsta->reserved_queue; + mvm->queue_info[queue].reserved = true; + IWL_DEBUG_TX_QUEUES(mvm, "Using reserved queue #%d\n", queue); + } + + if (queue < 0) + queue = iwl_mvm_find_free_queue(mvm, mvmsta->sta_id, + IWL_MVM_DQA_MIN_DATA_QUEUE, + IWL_MVM_DQA_MAX_DATA_QUEUE); + if (queue < 0) { + /* try harder - perhaps kill an inactive queue */ + queue = iwl_mvm_inactivity_check(mvm, mvmsta->sta_id); + } + + /* No free queue - we'll have to share */ + if (queue <= 0) { + queue = iwl_mvm_get_shared_queue(mvm, tfd_queue_mask, ac); + if (queue > 0) { + shared_queue = true; + mvm->queue_info[queue].status = IWL_MVM_QUEUE_SHARED; + } + } + + /* + * Mark TXQ as ready, even though it hasn't been fully configured yet, + * to make sure no one else takes it. + * This will allow avoiding re-acquiring the lock at the end of the + * configuration. On error we'll mark it back as free. + */ + if (queue > 0 && !shared_queue) + mvm->queue_info[queue].status = IWL_MVM_QUEUE_READY; + + /* This shouldn't happen - out of queues */ + if (WARN_ON(queue <= 0)) { + IWL_ERR(mvm, "No available queues for tid %d on sta_id %d\n", + tid, cfg.sta_id); + return queue; + } + + /* + * Actual en/disablement of aggregations is through the ADD_STA HCMD, + * but for configuring the SCD to send A-MPDUs we need to mark the queue + * as aggregatable. + * Mark all DATA queues as allowing to be aggregated at some point + */ + cfg.aggregate = (queue >= IWL_MVM_DQA_MIN_DATA_QUEUE || + queue == IWL_MVM_DQA_BSS_CLIENT_QUEUE); + + IWL_DEBUG_TX_QUEUES(mvm, + "Allocating %squeue #%d to sta %d on tid %d\n", + shared_queue ? "shared " : "", queue, + mvmsta->sta_id, tid); + + if (shared_queue) { + /* Disable any open aggs on this queue */ + disable_agg_tids = iwl_mvm_get_queue_agg_tids(mvm, queue); + + if (disable_agg_tids) { + IWL_DEBUG_TX_QUEUES(mvm, "Disabling aggs on queue %d\n", + queue); + iwl_mvm_invalidate_sta_queue(mvm, queue, + disable_agg_tids, false); + } + } + + inc_ssn = iwl_mvm_enable_txq(mvm, sta, queue, ssn, &cfg, wdg_timeout); + + /* + * Mark queue as shared in transport if shared + * Note this has to be done after queue enablement because enablement + * can also set this value, and there is no indication there to shared + * queues + */ + if (shared_queue) + iwl_trans_txq_set_shared_mode(mvm->trans, queue, true); + + spin_lock_bh(&mvmsta->lock); + /* + * This looks racy, but it is not. We have only one packet for + * this ra/tid in our Tx path since we stop the Qdisc when we + * need to allocate a new TFD queue. + */ + if (inc_ssn) { + mvmsta->tid_data[tid].seq_number += 0x10; + ssn = (ssn + 1) & IEEE80211_SCTL_SEQ; + } + mvmsta->tid_data[tid].txq_id = queue; + mvmsta->tfd_queue_msk |= BIT(queue); + queue_state = mvmsta->tid_data[tid].state; + + if (mvmsta->reserved_queue == queue) + mvmsta->reserved_queue = IEEE80211_INVAL_HW_QUEUE; + spin_unlock_bh(&mvmsta->lock); + + if (!shared_queue) { + ret = iwl_mvm_sta_send_to_fw(mvm, sta, true, STA_MODIFY_QUEUES); + if (ret) + goto out_err; + + /* If we need to re-enable aggregations... */ + if (queue_state == IWL_AGG_ON) { + ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); + if (ret) + goto out_err; + } + } else { + /* Redirect queue, if needed */ + ret = iwl_mvm_redirect_queue(mvm, queue, tid, ac, ssn, + wdg_timeout, false, + iwl_mvm_txq_from_tid(sta, tid)); + if (ret) + goto out_err; + } + + return 0; + +out_err: + queue_tmp = queue; + iwl_mvm_disable_txq(mvm, sta, &queue_tmp, tid, 0); + + return ret; +} + +void iwl_mvm_add_new_dqa_stream_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, + add_stream_wk); + + mutex_lock(&mvm->mutex); + + iwl_mvm_inactivity_check(mvm, IWL_MVM_INVALID_STA); + + while (!list_empty(&mvm->add_stream_txqs)) { + struct iwl_mvm_txq *mvmtxq; + struct ieee80211_txq *txq; + u8 tid; + + mvmtxq = list_first_entry(&mvm->add_stream_txqs, + struct iwl_mvm_txq, list); + + txq = container_of((void *)mvmtxq, struct ieee80211_txq, + drv_priv); + tid = txq->tid; + if (tid == IEEE80211_NUM_TIDS) + tid = IWL_MAX_TID_COUNT; + + /* + * We can't really do much here, but if this fails we can't + * transmit anyway - so just don't transmit the frame etc. + * and let them back up ... we've tried our best to allocate + * a queue in the function itself. + */ + if (iwl_mvm_sta_alloc_queue(mvm, txq->sta, txq->ac, tid)) { + list_del_init(&mvmtxq->list); + continue; + } + + list_del_init(&mvmtxq->list); + local_bh_disable(); + iwl_mvm_mac_itxq_xmit(mvm->hw, txq); + local_bh_enable(); + } + + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_reserve_sta_stream(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + enum nl80211_iftype vif_type) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + int queue; + + /* queue reserving is disabled on new TX path */ + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return 0; + + /* run the general cleanup/unsharing of queues */ + iwl_mvm_inactivity_check(mvm, IWL_MVM_INVALID_STA); + + /* Make sure we have free resources for this STA */ + if (vif_type == NL80211_IFTYPE_STATION && !sta->tdls && + !mvm->queue_info[IWL_MVM_DQA_BSS_CLIENT_QUEUE].tid_bitmap && + (mvm->queue_info[IWL_MVM_DQA_BSS_CLIENT_QUEUE].status == + IWL_MVM_QUEUE_FREE)) + queue = IWL_MVM_DQA_BSS_CLIENT_QUEUE; + else + queue = iwl_mvm_find_free_queue(mvm, mvmsta->sta_id, + IWL_MVM_DQA_MIN_DATA_QUEUE, + IWL_MVM_DQA_MAX_DATA_QUEUE); + if (queue < 0) { + /* try again - this time kick out a queue if needed */ + queue = iwl_mvm_inactivity_check(mvm, mvmsta->sta_id); + if (queue < 0) { + IWL_ERR(mvm, "No available queues for new station\n"); + return -ENOSPC; + } + } + mvm->queue_info[queue].status = IWL_MVM_QUEUE_RESERVED; + + mvmsta->reserved_queue = queue; + + IWL_DEBUG_TX_QUEUES(mvm, "Reserving data queue #%d for sta_id %d\n", + queue, mvmsta->sta_id); + + return 0; +} + +/* + * In DQA mode, after a HW restart the queues should be allocated as before, in + * order to avoid race conditions when there are shared queues. This function + * does the re-mapping and queue allocation. + * + * Note that re-enabling aggregations isn't done in this function. + */ +static void iwl_mvm_realloc_queues_after_restart(struct iwl_mvm *mvm, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + unsigned int wdg = + iwl_mvm_get_wd_timeout(mvm, mvm_sta->vif, false, false); + int i; + struct iwl_trans_txq_scd_cfg cfg = { + .sta_id = mvm_sta->sta_id, + .frame_limit = IWL_FRAME_LIMIT, + }; + + /* Make sure reserved queue is still marked as such (if allocated) */ + if (mvm_sta->reserved_queue != IEEE80211_INVAL_HW_QUEUE) + mvm->queue_info[mvm_sta->reserved_queue].status = + IWL_MVM_QUEUE_RESERVED; + + for (i = 0; i <= IWL_MAX_TID_COUNT; i++) { + struct iwl_mvm_tid_data *tid_data = &mvm_sta->tid_data[i]; + int txq_id = tid_data->txq_id; + int ac; + + if (txq_id == IWL_MVM_INVALID_QUEUE) + continue; + + ac = tid_to_mac80211_ac[i]; + + if (iwl_mvm_has_new_tx_api(mvm)) { + IWL_DEBUG_TX_QUEUES(mvm, + "Re-mapping sta %d tid %d\n", + mvm_sta->sta_id, i); + txq_id = iwl_mvm_tvqm_enable_txq(mvm, mvm_sta->sta_id, + i, wdg); + /* + * on failures, just set it to IWL_MVM_INVALID_QUEUE + * to try again later, we have no other good way of + * failing here + */ + if (txq_id < 0) + txq_id = IWL_MVM_INVALID_QUEUE; + tid_data->txq_id = txq_id; + + /* + * Since we don't set the seq number after reset, and HW + * sets it now, FW reset will cause the seq num to start + * at 0 again, so driver will need to update it + * internally as well, so it keeps in sync with real val + */ + tid_data->seq_number = 0; + } else { + u16 seq = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + + cfg.tid = i; + cfg.fifo = iwl_mvm_mac_ac_to_tx_fifo(mvm, ac); + cfg.aggregate = (txq_id >= IWL_MVM_DQA_MIN_DATA_QUEUE || + txq_id == + IWL_MVM_DQA_BSS_CLIENT_QUEUE); + + IWL_DEBUG_TX_QUEUES(mvm, + "Re-mapping sta %d tid %d to queue %d\n", + mvm_sta->sta_id, i, txq_id); + + iwl_mvm_enable_txq(mvm, sta, txq_id, seq, &cfg, wdg); + mvm->queue_info[txq_id].status = IWL_MVM_QUEUE_READY; + } + } +} + +static int iwl_mvm_add_int_sta_common(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *sta, + const u8 *addr, + u16 mac_id, u16 color) +{ + struct iwl_mvm_add_sta_cmd cmd; + int ret; + u32 status = ADD_STA_SUCCESS; + + lockdep_assert_held(&mvm->mutex); + + memset(&cmd, 0, sizeof(cmd)); + cmd.sta_id = sta->sta_id; + + if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, ADD_STA, + 0) >= 12 && + sta->type == IWL_STA_AUX_ACTIVITY) + cmd.mac_id_n_color = cpu_to_le32(mac_id); + else + cmd.mac_id_n_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mac_id, + color)); + + if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + cmd.station_type = sta->type; + + if (!iwl_mvm_has_new_tx_api(mvm)) + cmd.tfd_queue_msk = cpu_to_le32(sta->tfd_queue_msk); + cmd.tid_disable_tx = cpu_to_le16(0xffff); + + if (addr) + memcpy(cmd.addr, addr, ETH_ALEN); + + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); + if (ret) + return ret; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + IWL_DEBUG_INFO(mvm, "Internal station added.\n"); + return 0; + default: + ret = -EIO; + IWL_ERR(mvm, "Add internal station failed, status=0x%x\n", + status); + break; + } + return ret; +} + +int iwl_mvm_add_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_rxq_dup_data *dup_data; + int i, ret, sta_id; + bool sta_update = false; + unsigned int sta_flags = 0; + + lockdep_assert_held(&mvm->mutex); + + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + sta_id = iwl_mvm_find_free_sta_id(mvm, + ieee80211_vif_type_p2p(vif)); + else + sta_id = mvm_sta->sta_id; + + if (sta_id == IWL_MVM_INVALID_STA) + return -ENOSPC; + + spin_lock_init(&mvm_sta->lock); + + /* if this is a HW restart re-alloc existing queues */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + struct iwl_mvm_int_sta tmp_sta = { + .sta_id = sta_id, + .type = mvm_sta->sta_type, + }; + + /* + * First add an empty station since allocating + * a queue requires a valid station + */ + ret = iwl_mvm_add_int_sta_common(mvm, &tmp_sta, sta->addr, + mvmvif->id, mvmvif->color); + if (ret) + goto err; + + iwl_mvm_realloc_queues_after_restart(mvm, sta); + sta_update = true; + sta_flags = iwl_mvm_has_new_tx_api(mvm) ? 0 : STA_MODIFY_QUEUES; + goto update_fw; + } + + mvm_sta->sta_id = sta_id; + mvm_sta->mac_id_n_color = FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color); + mvm_sta->vif = vif; + if (!mvm->trans->trans_cfg->gen2) + mvm_sta->max_agg_bufsize = LINK_QUAL_AGG_FRAME_LIMIT_DEF; + else + mvm_sta->max_agg_bufsize = LINK_QUAL_AGG_FRAME_LIMIT_GEN2_DEF; + mvm_sta->tx_protection = 0; + mvm_sta->tt_tx_protection = false; + mvm_sta->sta_type = sta->tdls ? IWL_STA_TDLS_LINK : IWL_STA_LINK; + + /* HW restart, don't assume the memory has been zeroed */ + mvm_sta->tid_disable_agg = 0xffff; /* No aggs at first */ + mvm_sta->tfd_queue_msk = 0; + + /* for HW restart - reset everything but the sequence number */ + for (i = 0; i <= IWL_MAX_TID_COUNT; i++) { + u16 seq = mvm_sta->tid_data[i].seq_number; + memset(&mvm_sta->tid_data[i], 0, sizeof(mvm_sta->tid_data[i])); + mvm_sta->tid_data[i].seq_number = seq; + + /* + * Mark all queues for this STA as unallocated and defer TX + * frames until the queue is allocated + */ + mvm_sta->tid_data[i].txq_id = IWL_MVM_INVALID_QUEUE; + } + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_mac80211(sta->txq[i]); + + mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE; + INIT_LIST_HEAD(&mvmtxq->list); + atomic_set(&mvmtxq->tx_request, 0); + } + + mvm_sta->agg_tids = 0; + + if (iwl_mvm_has_new_rx_api(mvm) && + !test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + int q; + + dup_data = kcalloc(mvm->trans->num_rx_queues, + sizeof(*dup_data), GFP_KERNEL); + if (!dup_data) + return -ENOMEM; + /* + * Initialize all the last_seq values to 0xffff which can never + * compare equal to the frame's seq_ctrl in the check in + * iwl_mvm_is_dup() since the lower 4 bits are the fragment + * number and fragmented packets don't reach that function. + * + * This thus allows receiving a packet with seqno 0 and the + * retry bit set as the very first packet on a new TID. + */ + for (q = 0; q < mvm->trans->num_rx_queues; q++) + memset(dup_data[q].last_seq, 0xff, + sizeof(dup_data[q].last_seq)); + mvm_sta->dup_data = dup_data; + } + + if (!iwl_mvm_has_new_tx_api(mvm)) { + ret = iwl_mvm_reserve_sta_stream(mvm, sta, + ieee80211_vif_type_p2p(vif)); + if (ret) + goto err; + } + + /* + * if rs is registered with mac80211, then "add station" will be handled + * via the corresponding ops, otherwise need to notify rate scaling here + */ + if (iwl_mvm_has_tlc_offload(mvm)) + iwl_mvm_rs_add_sta(mvm, mvm_sta); + else + spin_lock_init(&mvm_sta->lq_sta.rs_drv.pers.lock); + + iwl_mvm_toggle_tx_ant(mvm, &mvm_sta->tx_ant); + +update_fw: + ret = iwl_mvm_sta_send_to_fw(mvm, sta, sta_update, sta_flags); + if (ret) + goto err; + + if (vif->type == NL80211_IFTYPE_STATION) { + if (!sta->tdls) { + WARN_ON(mvmvif->ap_sta_id != IWL_MVM_INVALID_STA); + mvmvif->ap_sta_id = sta_id; + } else { + WARN_ON(mvmvif->ap_sta_id == IWL_MVM_INVALID_STA); + } + } + + rcu_assign_pointer(mvm->fw_id_to_mac_id[sta_id], sta); + + return 0; + +err: + return ret; +} + +int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool drain) +{ + struct iwl_mvm_add_sta_cmd cmd = {}; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + cmd.mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color); + cmd.sta_id = mvmsta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + cmd.station_flags = drain ? cpu_to_le32(STA_FLG_DRAIN_FLOW) : 0; + cmd.station_flags_msk = cpu_to_le32(STA_FLG_DRAIN_FLOW); + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); + if (ret) + return ret; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + IWL_DEBUG_INFO(mvm, "Frames for staid %d will drained in fw\n", + mvmsta->sta_id); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "Couldn't drain frames for staid %d\n", + mvmsta->sta_id); + break; + } + + return ret; +} + +/* + * Remove a station from the FW table. Before sending the command to remove + * the station validate that the station is indeed known to the driver (sanity + * only). + */ +static int iwl_mvm_rm_sta_common(struct iwl_mvm *mvm, u8 sta_id) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_rm_sta_cmd rm_sta_cmd = { + .sta_id = sta_id, + }; + int ret; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + /* Note: internal stations are marked as error values */ + if (!sta) { + IWL_ERR(mvm, "Invalid station id\n"); + return -EINVAL; + } + + ret = iwl_mvm_send_cmd_pdu(mvm, REMOVE_STA, 0, + sizeof(rm_sta_cmd), &rm_sta_cmd); + if (ret) { + IWL_ERR(mvm, "Failed to remove station. Id=%d\n", sta_id); + return ret; + } + + return 0; +} + +static void iwl_mvm_disable_sta_queues(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + int i; + + lockdep_assert_held(&mvm->mutex); + + for (i = 0; i < ARRAY_SIZE(mvm_sta->tid_data); i++) { + if (mvm_sta->tid_data[i].txq_id == IWL_MVM_INVALID_QUEUE) + continue; + + iwl_mvm_disable_txq(mvm, sta, &mvm_sta->tid_data[i].txq_id, i, + 0); + mvm_sta->tid_data[i].txq_id = IWL_MVM_INVALID_QUEUE; + } + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_mac80211(sta->txq[i]); + + mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE; + } +} + +int iwl_mvm_wait_sta_queues_empty(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvm_sta) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mvm_sta->tid_data); i++) { + u16 txq_id; + int ret; + + spin_lock_bh(&mvm_sta->lock); + txq_id = mvm_sta->tid_data[i].txq_id; + spin_unlock_bh(&mvm_sta->lock); + + if (txq_id == IWL_MVM_INVALID_QUEUE) + continue; + + ret = iwl_trans_wait_txq_empty(mvm->trans, txq_id); + if (ret) + return ret; + } + + return 0; +} + +int iwl_mvm_rm_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + u8 sta_id = mvm_sta->sta_id; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_has_new_rx_api(mvm)) + kfree(mvm_sta->dup_data); + + ret = iwl_mvm_drain_sta(mvm, mvm_sta, true); + if (ret) + return ret; + + /* flush its queues here since we are freeing mvm_sta */ + ret = iwl_mvm_flush_sta(mvm, mvm_sta, false); + if (ret) + return ret; + if (iwl_mvm_has_new_tx_api(mvm)) { + ret = iwl_mvm_wait_sta_queues_empty(mvm, mvm_sta); + } else { + u32 q_mask = mvm_sta->tfd_queue_msk; + + ret = iwl_trans_wait_tx_queues_empty(mvm->trans, + q_mask); + } + if (ret) + return ret; + + ret = iwl_mvm_drain_sta(mvm, mvm_sta, false); + + iwl_mvm_disable_sta_queues(mvm, vif, sta); + + /* If there is a TXQ still marked as reserved - free it */ + if (mvm_sta->reserved_queue != IEEE80211_INVAL_HW_QUEUE) { + u8 reserved_txq = mvm_sta->reserved_queue; + enum iwl_mvm_queue_status *status; + + /* + * If no traffic has gone through the reserved TXQ - it + * is still marked as IWL_MVM_QUEUE_RESERVED, and + * should be manually marked as free again + */ + status = &mvm->queue_info[reserved_txq].status; + if (WARN((*status != IWL_MVM_QUEUE_RESERVED) && + (*status != IWL_MVM_QUEUE_FREE), + "sta_id %d reserved txq %d status %d", + sta_id, reserved_txq, *status)) + return -EINVAL; + + *status = IWL_MVM_QUEUE_FREE; + } + + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id == sta_id) { + /* if associated - we can't remove the AP STA now */ + if (vif->bss_conf.assoc) + return ret; + + /* unassoc - go ahead - remove the AP STA now */ + mvmvif->ap_sta_id = IWL_MVM_INVALID_STA; + } + + /* + * This shouldn't happen - the TDLS channel switch should be canceled + * before the STA is removed. + */ + if (WARN_ON_ONCE(mvm->tdls_cs.peer.sta_id == sta_id)) { + mvm->tdls_cs.peer.sta_id = IWL_MVM_INVALID_STA; + cancel_delayed_work(&mvm->tdls_cs.dwork); + } + + /* + * Make sure that the tx response code sees the station as -EBUSY and + * calls the drain worker. + */ + spin_lock_bh(&mvm_sta->lock); + spin_unlock_bh(&mvm_sta->lock); + + ret = iwl_mvm_rm_sta_common(mvm, mvm_sta->sta_id); + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[mvm_sta->sta_id], NULL); + + return ret; +} + +int iwl_mvm_rm_sta_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 sta_id) +{ + int ret = iwl_mvm_rm_sta_common(mvm, sta_id); + + lockdep_assert_held(&mvm->mutex); + + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[sta_id], NULL); + return ret; +} + +int iwl_mvm_allocate_int_sta(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *sta, + u32 qmask, enum nl80211_iftype iftype, + enum iwl_sta_type type) +{ + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) || + sta->sta_id == IWL_MVM_INVALID_STA) { + sta->sta_id = iwl_mvm_find_free_sta_id(mvm, iftype); + if (WARN_ON_ONCE(sta->sta_id == IWL_MVM_INVALID_STA)) + return -ENOSPC; + } + + sta->tfd_queue_msk = qmask; + sta->type = type; + + /* put a non-NULL value so iterating over the stations won't stop */ + rcu_assign_pointer(mvm->fw_id_to_mac_id[sta->sta_id], ERR_PTR(-EINVAL)); + return 0; +} + +void iwl_mvm_dealloc_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta) +{ + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[sta->sta_id], NULL); + memset(sta, 0, sizeof(struct iwl_mvm_int_sta)); + sta->sta_id = IWL_MVM_INVALID_STA; +} + +static void iwl_mvm_enable_aux_snif_queue(struct iwl_mvm *mvm, u16 queue, + u8 sta_id, u8 fifo) +{ + unsigned int wdg_timeout = + mvm->trans->trans_cfg->base_params->wd_timeout; + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = fifo, + .sta_id = sta_id, + .tid = IWL_MAX_TID_COUNT, + .aggregate = false, + .frame_limit = IWL_FRAME_LIMIT, + }; + + WARN_ON(iwl_mvm_has_new_tx_api(mvm)); + + iwl_mvm_enable_txq(mvm, NULL, queue, 0, &cfg, wdg_timeout); +} + +static int iwl_mvm_enable_aux_snif_queue_tvqm(struct iwl_mvm *mvm, u8 sta_id) +{ + unsigned int wdg_timeout = + mvm->trans->trans_cfg->base_params->wd_timeout; + + WARN_ON(!iwl_mvm_has_new_tx_api(mvm)); + + return iwl_mvm_tvqm_enable_txq(mvm, sta_id, IWL_MAX_TID_COUNT, + wdg_timeout); +} + +static int iwl_mvm_add_int_sta_with_queue(struct iwl_mvm *mvm, int macidx, + int maccolor, u8 *addr, + struct iwl_mvm_int_sta *sta, + u16 *queue, int fifo) +{ + int ret; + + /* Map queue to fifo - needs to happen before adding station */ + if (!iwl_mvm_has_new_tx_api(mvm)) + iwl_mvm_enable_aux_snif_queue(mvm, *queue, sta->sta_id, fifo); + + ret = iwl_mvm_add_int_sta_common(mvm, sta, addr, macidx, maccolor); + if (ret) { + if (!iwl_mvm_has_new_tx_api(mvm)) + iwl_mvm_disable_txq(mvm, NULL, queue, + IWL_MAX_TID_COUNT, 0); + return ret; + } + + /* + * For 22000 firmware and on we cannot add queue to a station unknown + * to firmware so enable queue here - after the station was added + */ + if (iwl_mvm_has_new_tx_api(mvm)) { + int txq; + + txq = iwl_mvm_enable_aux_snif_queue_tvqm(mvm, sta->sta_id); + if (txq < 0) { + iwl_mvm_rm_sta_common(mvm, sta->sta_id); + return txq; + } + + *queue = txq; + } + + return 0; +} + +int iwl_mvm_add_aux_sta(struct iwl_mvm *mvm, u32 lmac_id) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + /* Allocate aux station and assign to it the aux queue */ + ret = iwl_mvm_allocate_int_sta(mvm, &mvm->aux_sta, BIT(mvm->aux_queue), + NL80211_IFTYPE_UNSPECIFIED, + IWL_STA_AUX_ACTIVITY); + if (ret) + return ret; + + /* + * In CDB NICs we need to specify which lmac to use for aux activity + * using the mac_id argument place to send lmac_id to the function + */ + ret = iwl_mvm_add_int_sta_with_queue(mvm, lmac_id, 0, NULL, + &mvm->aux_sta, &mvm->aux_queue, + IWL_MVM_TX_FIFO_MCAST); + if (ret) { + iwl_mvm_dealloc_int_sta(mvm, &mvm->aux_sta); + return ret; + } + + return 0; +} + +int iwl_mvm_add_snif_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + lockdep_assert_held(&mvm->mutex); + + return iwl_mvm_add_int_sta_with_queue(mvm, mvmvif->id, mvmvif->color, + NULL, &mvm->snif_sta, + &mvm->snif_queue, + IWL_MVM_TX_FIFO_BE); +} + +int iwl_mvm_rm_snif_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(mvm->snif_sta.sta_id == IWL_MVM_INVALID_STA)) + return -EINVAL; + + iwl_mvm_disable_txq(mvm, NULL, &mvm->snif_queue, IWL_MAX_TID_COUNT, 0); + ret = iwl_mvm_rm_sta_common(mvm, mvm->snif_sta.sta_id); + if (ret) + IWL_WARN(mvm, "Failed sending remove station\n"); + + return ret; +} + +int iwl_mvm_rm_aux_sta(struct iwl_mvm *mvm) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(mvm->aux_sta.sta_id == IWL_MVM_INVALID_STA)) + return -EINVAL; + + iwl_mvm_disable_txq(mvm, NULL, &mvm->aux_queue, IWL_MAX_TID_COUNT, 0); + ret = iwl_mvm_rm_sta_common(mvm, mvm->aux_sta.sta_id); + if (ret) + IWL_WARN(mvm, "Failed sending remove station\n"); + iwl_mvm_dealloc_int_sta(mvm, &mvm->aux_sta); + + return ret; +} + +void iwl_mvm_dealloc_snif_sta(struct iwl_mvm *mvm) +{ + iwl_mvm_dealloc_int_sta(mvm, &mvm->snif_sta); +} + +/* + * Send the add station command for the vif's broadcast station. + * Assumes that the station was already allocated. + * + * @mvm: the mvm component + * @vif: the interface to which the broadcast station is added + * @bsta: the broadcast station to add. + */ +int iwl_mvm_send_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_int_sta *bsta = &mvmvif->bcast_sta; + static const u8 _baddr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + const u8 *baddr = _baddr; + int queue; + int ret; + unsigned int wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, vif, false, false); + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = IWL_MVM_TX_FIFO_VO, + .sta_id = mvmvif->bcast_sta.sta_id, + .tid = IWL_MAX_TID_COUNT, + .aggregate = false, + .frame_limit = IWL_FRAME_LIMIT, + }; + + lockdep_assert_held(&mvm->mutex); + + if (!iwl_mvm_has_new_tx_api(mvm)) { + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { + queue = mvm->probe_queue; + } else if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + queue = mvm->p2p_dev_queue; + } else { + WARN(1, "Missing required TXQ for adding bcast STA\n"); + return -EINVAL; + } + + bsta->tfd_queue_msk |= BIT(queue); + + iwl_mvm_enable_txq(mvm, NULL, queue, 0, &cfg, wdg_timeout); + } + + if (vif->type == NL80211_IFTYPE_ADHOC) + baddr = vif->bss_conf.bssid; + + if (WARN_ON_ONCE(bsta->sta_id == IWL_MVM_INVALID_STA)) + return -ENOSPC; + + ret = iwl_mvm_add_int_sta_common(mvm, bsta, baddr, + mvmvif->id, mvmvif->color); + if (ret) + return ret; + + /* + * For 22000 firmware and on we cannot add queue to a station unknown + * to firmware so enable queue here - after the station was added + */ + if (iwl_mvm_has_new_tx_api(mvm)) { + queue = iwl_mvm_tvqm_enable_txq(mvm, bsta->sta_id, + IWL_MAX_TID_COUNT, + wdg_timeout); + if (queue < 0) { + iwl_mvm_rm_sta_common(mvm, bsta->sta_id); + return queue; + } + + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) + mvm->probe_queue = queue; + else if (vif->type == NL80211_IFTYPE_P2P_DEVICE) + mvm->p2p_dev_queue = queue; + } + + return 0; +} + +static void iwl_mvm_free_bcast_sta_queues(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 *queueptr, queue; + + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_flush_sta(mvm, &mvmvif->bcast_sta, true); + + switch (vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + queueptr = &mvm->probe_queue; + break; + case NL80211_IFTYPE_P2P_DEVICE: + queueptr = &mvm->p2p_dev_queue; + break; + default: + WARN(1, "Can't free bcast queue on vif type %d\n", + vif->type); + return; + } + + queue = *queueptr; + iwl_mvm_disable_txq(mvm, NULL, queueptr, IWL_MAX_TID_COUNT, 0); + if (iwl_mvm_has_new_tx_api(mvm)) + return; + + WARN_ON(!(mvmvif->bcast_sta.tfd_queue_msk & BIT(queue))); + mvmvif->bcast_sta.tfd_queue_msk &= ~BIT(queue); +} + +/* Send the FW a request to remove the station from it's internal data + * structures, but DO NOT remove the entry from the local data structures. */ +int iwl_mvm_send_rm_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_free_bcast_sta_queues(mvm, vif); + + ret = iwl_mvm_rm_sta_common(mvm, mvmvif->bcast_sta.sta_id); + if (ret) + IWL_WARN(mvm, "Failed sending remove station\n"); + return ret; +} + +int iwl_mvm_alloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + lockdep_assert_held(&mvm->mutex); + + return iwl_mvm_allocate_int_sta(mvm, &mvmvif->bcast_sta, 0, + ieee80211_vif_type_p2p(vif), + IWL_STA_GENERAL_PURPOSE); +} + +/* Allocate a new station entry for the broadcast station to the given vif, + * and send it to the FW. + * Note that each P2P mac should have its own broadcast station. + * + * @mvm: the mvm component + * @vif: the interface to which the broadcast station is added + * @bsta: the broadcast station to add. */ +int iwl_mvm_add_p2p_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_int_sta *bsta = &mvmvif->bcast_sta; + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_alloc_bcast_sta(mvm, vif); + if (ret) + return ret; + + ret = iwl_mvm_send_add_bcast_sta(mvm, vif); + + if (ret) + iwl_mvm_dealloc_int_sta(mvm, bsta); + + return ret; +} + +void iwl_mvm_dealloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + iwl_mvm_dealloc_int_sta(mvm, &mvmvif->bcast_sta); +} + +/* + * Send the FW a request to remove the station from it's internal data + * structures, and in addition remove it from the local data structure. + */ +int iwl_mvm_rm_p2p_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_send_rm_bcast_sta(mvm, vif); + + iwl_mvm_dealloc_bcast_sta(mvm, vif); + + return ret; +} + +/* + * Allocate a new station entry for the multicast station to the given vif, + * and send it to the FW. + * Note that each AP/GO mac should have its own multicast station. + * + * @mvm: the mvm component + * @vif: the interface to which the multicast station is added + */ +int iwl_mvm_add_mcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_int_sta *msta = &mvmvif->mcast_sta; + static const u8 _maddr[] = {0x03, 0x00, 0x00, 0x00, 0x00, 0x00}; + const u8 *maddr = _maddr; + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = vif->type == NL80211_IFTYPE_AP ? + IWL_MVM_TX_FIFO_MCAST : IWL_MVM_TX_FIFO_BE, + .sta_id = msta->sta_id, + .tid = 0, + .aggregate = false, + .frame_limit = IWL_FRAME_LIMIT, + }; + unsigned int timeout = iwl_mvm_get_wd_timeout(mvm, vif, false, false); + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_ADHOC)) + return -ENOTSUPP; + + /* + * In IBSS, ieee80211_check_queues() sets the cab_queue to be + * invalid, so make sure we use the queue we want. + * Note that this is done here as we want to avoid making DQA + * changes in mac80211 layer. + */ + if (vif->type == NL80211_IFTYPE_ADHOC) + mvmvif->cab_queue = IWL_MVM_DQA_GCAST_QUEUE; + + /* + * While in previous FWs we had to exclude cab queue from TFD queue + * mask, now it is needed as any other queue. + */ + if (!iwl_mvm_has_new_tx_api(mvm) && + fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) { + iwl_mvm_enable_txq(mvm, NULL, mvmvif->cab_queue, 0, &cfg, + timeout); + msta->tfd_queue_msk |= BIT(mvmvif->cab_queue); + } + ret = iwl_mvm_add_int_sta_common(mvm, msta, maddr, + mvmvif->id, mvmvif->color); + if (ret) + goto err; + + /* + * Enable cab queue after the ADD_STA command is sent. + * This is needed for 22000 firmware which won't accept SCD_QUEUE_CFG + * command with unknown station id, and for FW that doesn't support + * station API since the cab queue is not included in the + * tfd_queue_mask. + */ + if (iwl_mvm_has_new_tx_api(mvm)) { + int queue = iwl_mvm_tvqm_enable_txq(mvm, msta->sta_id, + 0, + timeout); + if (queue < 0) { + ret = queue; + goto err; + } + mvmvif->cab_queue = queue; + } else if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_STA_TYPE)) + iwl_mvm_enable_txq(mvm, NULL, mvmvif->cab_queue, 0, &cfg, + timeout); + + return 0; +err: + iwl_mvm_dealloc_int_sta(mvm, msta); + return ret; +} + +static int __iwl_mvm_remove_sta_key(struct iwl_mvm *mvm, u8 sta_id, + struct ieee80211_key_conf *keyconf, + bool mcast) +{ + union { + struct iwl_mvm_add_sta_key_cmd_v1 cmd_v1; + struct iwl_mvm_add_sta_key_cmd cmd; + } u = {}; + bool new_api = fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_TKIP_MIC_KEYS); + __le16 key_flags; + int ret, size; + u32 status; + + /* This is a valid situation for GTK removal */ + if (sta_id == IWL_MVM_INVALID_STA) + return 0; + + key_flags = cpu_to_le16((keyconf->keyidx << STA_KEY_FLG_KEYID_POS) & + STA_KEY_FLG_KEYID_MSK); + key_flags |= cpu_to_le16(STA_KEY_FLG_NO_ENC | STA_KEY_FLG_WEP_KEY_MAP); + key_flags |= cpu_to_le16(STA_KEY_NOT_VALID); + + if (mcast) + key_flags |= cpu_to_le16(STA_KEY_MULTICAST); + + /* + * The fields assigned here are in the same location at the start + * of the command, so we can do this union trick. + */ + u.cmd.common.key_flags = key_flags; + u.cmd.common.key_offset = keyconf->hw_key_idx; + u.cmd.common.sta_id = sta_id; + + size = new_api ? sizeof(u.cmd) : sizeof(u.cmd_v1); + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA_KEY, size, &u.cmd, + &status); + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_WEP(mvm, "MODIFY_STA: remove sta key passed\n"); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "MODIFY_STA: remove sta key failed\n"); + break; + } + + return ret; +} + +/* + * Send the FW a request to remove the station from it's internal data + * structures, and in addition remove it from the local data structure. + */ +int iwl_mvm_rm_mcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_flush_sta(mvm, &mvmvif->mcast_sta, true); + + iwl_mvm_disable_txq(mvm, NULL, &mvmvif->cab_queue, 0, 0); + + ret = iwl_mvm_rm_sta_common(mvm, mvmvif->mcast_sta.sta_id); + if (ret) + IWL_WARN(mvm, "Failed sending remove station\n"); + + return ret; +} + +#define IWL_MAX_RX_BA_SESSIONS 16 + +static void iwl_mvm_sync_rxq_del_ba(struct iwl_mvm *mvm, u8 baid) +{ + struct iwl_mvm_delba_data notif = { + .baid = baid, + }; + + iwl_mvm_sync_rx_queues_internal(mvm, IWL_MVM_RXQ_NOTIF_DEL_BA, true, + ¬if, sizeof(notif)); +}; + +static void iwl_mvm_free_reorder(struct iwl_mvm *mvm, + struct iwl_mvm_baid_data *data) +{ + int i; + + iwl_mvm_sync_rxq_del_ba(mvm, data->baid); + + for (i = 0; i < mvm->trans->num_rx_queues; i++) { + int j; + struct iwl_mvm_reorder_buffer *reorder_buf = + &data->reorder_buf[i]; + struct iwl_mvm_reorder_buf_entry *entries = + &data->entries[i * data->entries_per_queue]; + + spin_lock_bh(&reorder_buf->lock); + if (likely(!reorder_buf->num_stored)) { + spin_unlock_bh(&reorder_buf->lock); + continue; + } + + /* + * This shouldn't happen in regular DELBA since the internal + * delBA notification should trigger a release of all frames in + * the reorder buffer. + */ + WARN_ON(1); + + for (j = 0; j < reorder_buf->buf_size; j++) + __skb_queue_purge(&entries[j].e.frames); + /* + * Prevent timer re-arm. This prevents a very far fetched case + * where we timed out on the notification. There may be prior + * RX frames pending in the RX queue before the notification + * that might get processed between now and the actual deletion + * and we would re-arm the timer although we are deleting the + * reorder buffer. + */ + reorder_buf->removed = true; + spin_unlock_bh(&reorder_buf->lock); + del_timer_sync(&reorder_buf->reorder_timer); + } +} + +static void iwl_mvm_init_reorder_buffer(struct iwl_mvm *mvm, + struct iwl_mvm_baid_data *data, + u16 ssn, u16 buf_size) +{ + int i; + + for (i = 0; i < mvm->trans->num_rx_queues; i++) { + struct iwl_mvm_reorder_buffer *reorder_buf = + &data->reorder_buf[i]; + struct iwl_mvm_reorder_buf_entry *entries = + &data->entries[i * data->entries_per_queue]; + int j; + + reorder_buf->num_stored = 0; + reorder_buf->head_sn = ssn; + reorder_buf->buf_size = buf_size; + /* rx reorder timer */ + timer_setup(&reorder_buf->reorder_timer, + iwl_mvm_reorder_timer_expired, 0); + spin_lock_init(&reorder_buf->lock); + reorder_buf->mvm = mvm; + reorder_buf->queue = i; + reorder_buf->valid = false; + for (j = 0; j < reorder_buf->buf_size; j++) + __skb_queue_head_init(&entries[j].e.frames); + } +} + +int iwl_mvm_sta_rx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u16 ssn, bool start, u16 buf_size, u16 timeout) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_add_sta_cmd cmd = {}; + struct iwl_mvm_baid_data *baid_data = NULL; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + if (start && mvm->rx_ba_sessions >= IWL_MAX_RX_BA_SESSIONS) { + IWL_WARN(mvm, "Not enough RX BA SESSIONS\n"); + return -ENOSPC; + } + + if (iwl_mvm_has_new_rx_api(mvm) && start) { + u16 reorder_buf_size = buf_size * sizeof(baid_data->entries[0]); + + /* sparse doesn't like the __align() so don't check */ +#ifndef __CHECKER__ + /* + * The division below will be OK if either the cache line size + * can be divided by the entry size (ALIGN will round up) or if + * if the entry size can be divided by the cache line size, in + * which case the ALIGN() will do nothing. + */ + BUILD_BUG_ON(SMP_CACHE_BYTES % sizeof(baid_data->entries[0]) && + sizeof(baid_data->entries[0]) % SMP_CACHE_BYTES); +#endif + + /* + * Upward align the reorder buffer size to fill an entire cache + * line for each queue, to avoid sharing cache lines between + * different queues. + */ + reorder_buf_size = ALIGN(reorder_buf_size, SMP_CACHE_BYTES); + + /* + * Allocate here so if allocation fails we can bail out early + * before starting the BA session in the firmware + */ + baid_data = kzalloc(sizeof(*baid_data) + + mvm->trans->num_rx_queues * + reorder_buf_size, + GFP_KERNEL); + if (!baid_data) + return -ENOMEM; + + /* + * This division is why we need the above BUILD_BUG_ON(), + * if that doesn't hold then this will not be right. + */ + baid_data->entries_per_queue = + reorder_buf_size / sizeof(baid_data->entries[0]); + } + + cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); + cmd.sta_id = mvm_sta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + if (start) { + cmd.add_immediate_ba_tid = (u8) tid; + cmd.add_immediate_ba_ssn = cpu_to_le16(ssn); + cmd.rx_ba_window = cpu_to_le16(buf_size); + } else { + cmd.remove_immediate_ba_tid = (u8) tid; + } + cmd.modify_mask = start ? STA_MODIFY_ADD_BA_TID : + STA_MODIFY_REMOVE_BA_TID; + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); + if (ret) + goto out_free; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + IWL_DEBUG_HT(mvm, "RX BA Session %sed in fw\n", + start ? "start" : "stopp"); + break; + case ADD_STA_IMMEDIATE_BA_FAILURE: + IWL_WARN(mvm, "RX BA Session refused by fw\n"); + ret = -ENOSPC; + break; + default: + ret = -EIO; + IWL_ERR(mvm, "RX BA Session failed %sing, status 0x%x\n", + start ? "start" : "stopp", status); + break; + } + + if (ret) + goto out_free; + + if (start) { + u8 baid; + + mvm->rx_ba_sessions++; + + if (!iwl_mvm_has_new_rx_api(mvm)) + return 0; + + if (WARN_ON(!(status & IWL_ADD_STA_BAID_VALID_MASK))) { + ret = -EINVAL; + goto out_free; + } + baid = (u8)((status & IWL_ADD_STA_BAID_MASK) >> + IWL_ADD_STA_BAID_SHIFT); + baid_data->baid = baid; + baid_data->timeout = timeout; + baid_data->last_rx = jiffies; + baid_data->rcu_ptr = &mvm->baid_map[baid]; + timer_setup(&baid_data->session_timer, + iwl_mvm_rx_agg_session_expired, 0); + baid_data->mvm = mvm; + baid_data->tid = tid; + baid_data->sta_id = mvm_sta->sta_id; + + mvm_sta->tid_to_baid[tid] = baid; + if (timeout) + mod_timer(&baid_data->session_timer, + TU_TO_EXP_TIME(timeout * 2)); + + iwl_mvm_init_reorder_buffer(mvm, baid_data, ssn, buf_size); + /* + * protect the BA data with RCU to cover a case where our + * internal RX sync mechanism will timeout (not that it's + * supposed to happen) and we will free the session data while + * RX is being processed in parallel + */ + IWL_DEBUG_HT(mvm, "Sta %d(%d) is assigned to BAID %d\n", + mvm_sta->sta_id, tid, baid); + WARN_ON(rcu_access_pointer(mvm->baid_map[baid])); + rcu_assign_pointer(mvm->baid_map[baid], baid_data); + } else { + u8 baid = mvm_sta->tid_to_baid[tid]; + + if (mvm->rx_ba_sessions > 0) + /* check that restart flow didn't zero the counter */ + mvm->rx_ba_sessions--; + if (!iwl_mvm_has_new_rx_api(mvm)) + return 0; + + if (WARN_ON(baid == IWL_RX_REORDER_DATA_INVALID_BAID)) + return -EINVAL; + + baid_data = rcu_access_pointer(mvm->baid_map[baid]); + if (WARN_ON(!baid_data)) + return -EINVAL; + + /* synchronize all rx queues so we can safely delete */ + iwl_mvm_free_reorder(mvm, baid_data); + del_timer_sync(&baid_data->session_timer); + RCU_INIT_POINTER(mvm->baid_map[baid], NULL); + kfree_rcu(baid_data, rcu_head); + IWL_DEBUG_HT(mvm, "BAID %d is free\n", baid); + } + return 0; + +out_free: + kfree(baid_data); + return ret; +} + +int iwl_mvm_sta_tx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u8 queue, bool start) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_add_sta_cmd cmd = {}; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + if (start) { + mvm_sta->tfd_queue_msk |= BIT(queue); + mvm_sta->tid_disable_agg &= ~BIT(tid); + } else { + /* In DQA-mode the queue isn't removed on agg termination */ + mvm_sta->tid_disable_agg |= BIT(tid); + } + + cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); + cmd.sta_id = mvm_sta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + if (!iwl_mvm_has_new_tx_api(mvm)) + cmd.modify_mask = STA_MODIFY_QUEUES; + cmd.modify_mask |= STA_MODIFY_TID_DISABLE_TX; + cmd.tfd_queue_msk = cpu_to_le32(mvm_sta->tfd_queue_msk); + cmd.tid_disable_tx = cpu_to_le16(mvm_sta->tid_disable_agg); + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); + if (ret) + return ret; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + break; + default: + ret = -EIO; + IWL_ERR(mvm, "TX BA Session failed %sing, status 0x%x\n", + start ? "start" : "stopp", status); + break; + } + + return ret; +} + +const u8 tid_to_mac80211_ac[] = { + IEEE80211_AC_BE, + IEEE80211_AC_BK, + IEEE80211_AC_BK, + IEEE80211_AC_BE, + IEEE80211_AC_VI, + IEEE80211_AC_VI, + IEEE80211_AC_VO, + IEEE80211_AC_VO, + IEEE80211_AC_VO, /* We treat MGMT as TID 8, which is set as AC_VO */ +}; + +static const u8 tid_to_ucode_ac[] = { + AC_BE, + AC_BK, + AC_BK, + AC_BE, + AC_VI, + AC_VI, + AC_VO, + AC_VO, +}; + +int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data; + u16 normalized_ssn; + u16 txq_id; + int ret; + + if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT)) + return -EINVAL; + + if (mvmsta->tid_data[tid].state != IWL_AGG_QUEUED && + mvmsta->tid_data[tid].state != IWL_AGG_OFF) { + IWL_ERR(mvm, + "Start AGG when state is not IWL_AGG_QUEUED or IWL_AGG_OFF %d!\n", + mvmsta->tid_data[tid].state); + return -ENXIO; + } + + lockdep_assert_held(&mvm->mutex); + + if (mvmsta->tid_data[tid].txq_id == IWL_MVM_INVALID_QUEUE && + iwl_mvm_has_new_tx_api(mvm)) { + u8 ac = tid_to_mac80211_ac[tid]; + + ret = iwl_mvm_sta_alloc_queue_tvqm(mvm, sta, ac, tid); + if (ret) + return ret; + } + + spin_lock_bh(&mvmsta->lock); + + /* + * Note the possible cases: + * 1. An enabled TXQ - TXQ needs to become agg'ed + * 2. The TXQ hasn't yet been enabled, so find a free one and mark + * it as reserved + */ + txq_id = mvmsta->tid_data[tid].txq_id; + if (txq_id == IWL_MVM_INVALID_QUEUE) { + ret = iwl_mvm_find_free_queue(mvm, mvmsta->sta_id, + IWL_MVM_DQA_MIN_DATA_QUEUE, + IWL_MVM_DQA_MAX_DATA_QUEUE); + if (ret < 0) { + IWL_ERR(mvm, "Failed to allocate agg queue\n"); + goto out; + } + + txq_id = ret; + + /* TXQ hasn't yet been enabled, so mark it only as reserved */ + mvm->queue_info[txq_id].status = IWL_MVM_QUEUE_RESERVED; + } else if (WARN_ON(txq_id >= IWL_MAX_HW_QUEUES)) { + ret = -ENXIO; + IWL_ERR(mvm, "tid_id %d out of range (0, %d)!\n", + tid, IWL_MAX_HW_QUEUES - 1); + goto out; + + } else if (unlikely(mvm->queue_info[txq_id].status == + IWL_MVM_QUEUE_SHARED)) { + ret = -ENXIO; + IWL_DEBUG_TX_QUEUES(mvm, + "Can't start tid %d agg on shared queue!\n", + tid); + goto out; + } + + IWL_DEBUG_TX_QUEUES(mvm, + "AGG for tid %d will be on queue #%d\n", + tid, txq_id); + + tid_data = &mvmsta->tid_data[tid]; + tid_data->ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + tid_data->txq_id = txq_id; + *ssn = tid_data->ssn; + + IWL_DEBUG_TX_QUEUES(mvm, + "Start AGG: sta %d tid %d queue %d - ssn = %d, next_recl = %d\n", + mvmsta->sta_id, tid, txq_id, tid_data->ssn, + tid_data->next_reclaimed); + + /* + * In 22000 HW, the next_reclaimed index is only 8 bit, so we'll need + * to align the wrap around of ssn so we compare relevant values. + */ + normalized_ssn = tid_data->ssn; + if (mvm->trans->trans_cfg->gen2) + normalized_ssn &= 0xff; + + if (normalized_ssn == tid_data->next_reclaimed) { + tid_data->state = IWL_AGG_STARTING; + ret = IEEE80211_AMPDU_TX_START_IMMEDIATE; + } else { + tid_data->state = IWL_EMPTYING_HW_QUEUE_ADDBA; + ret = IEEE80211_AMPDU_TX_START_DELAY_ADDBA; + } + +out: + spin_unlock_bh(&mvmsta->lock); + + return ret; +} + +int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 buf_size, + bool amsdu) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + unsigned int wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, vif, sta->tdls, false); + int queue, ret; + bool alloc_queue = true; + enum iwl_mvm_queue_status queue_status; + u16 ssn; + + struct iwl_trans_txq_scd_cfg cfg = { + .sta_id = mvmsta->sta_id, + .tid = tid, + .frame_limit = buf_size, + .aggregate = true, + }; + + /* + * When FW supports TLC_OFFLOAD, it also implements Tx aggregation + * manager, so this function should never be called in this case. + */ + if (WARN_ON_ONCE(iwl_mvm_has_tlc_offload(mvm))) + return -EINVAL; + + BUILD_BUG_ON((sizeof(mvmsta->agg_tids) * BITS_PER_BYTE) + != IWL_MAX_TID_COUNT); + + spin_lock_bh(&mvmsta->lock); + ssn = tid_data->ssn; + queue = tid_data->txq_id; + tid_data->state = IWL_AGG_ON; + mvmsta->agg_tids |= BIT(tid); + tid_data->ssn = 0xffff; + tid_data->amsdu_in_ampdu_allowed = amsdu; + spin_unlock_bh(&mvmsta->lock); + + if (iwl_mvm_has_new_tx_api(mvm)) { + /* + * If there is no queue for this tid, iwl_mvm_sta_tx_agg_start() + * would have failed, so if we are here there is no need to + * allocate a queue. + * However, if aggregation size is different than the default + * size, the scheduler should be reconfigured. + * We cannot do this with the new TX API, so return unsupported + * for now, until it will be offloaded to firmware.. + * Note that if SCD default value changes - this condition + * should be updated as well. + */ + if (buf_size < IWL_FRAME_LIMIT) + return -ENOTSUPP; + + ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); + if (ret) + return -EIO; + goto out; + } + + cfg.fifo = iwl_mvm_ac_to_tx_fifo[tid_to_mac80211_ac[tid]]; + + queue_status = mvm->queue_info[queue].status; + + /* Maybe there is no need to even alloc a queue... */ + if (mvm->queue_info[queue].status == IWL_MVM_QUEUE_READY) + alloc_queue = false; + + /* + * Only reconfig the SCD for the queue if the window size has + * changed from current (become smaller) + */ + if (!alloc_queue && buf_size < IWL_FRAME_LIMIT) { + /* + * If reconfiguring an existing queue, it first must be + * drained + */ + ret = iwl_trans_wait_tx_queues_empty(mvm->trans, + BIT(queue)); + if (ret) { + IWL_ERR(mvm, + "Error draining queue before reconfig\n"); + return ret; + } + + ret = iwl_mvm_reconfig_scd(mvm, queue, cfg.fifo, + mvmsta->sta_id, tid, + buf_size, ssn); + if (ret) { + IWL_ERR(mvm, + "Error reconfiguring TXQ #%d\n", queue); + return ret; + } + } + + if (alloc_queue) + iwl_mvm_enable_txq(mvm, sta, queue, ssn, + &cfg, wdg_timeout); + + /* Send ADD_STA command to enable aggs only if the queue isn't shared */ + if (queue_status != IWL_MVM_QUEUE_SHARED) { + ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); + if (ret) + return -EIO; + } + + /* No need to mark as reserved */ + mvm->queue_info[queue].status = IWL_MVM_QUEUE_READY; + +out: + /* + * Even though in theory the peer could have different + * aggregation reorder buffer sizes for different sessions, + * our ucode doesn't allow for that and has a global limit + * for each station. Therefore, use the minimum of all the + * aggregation sessions and our default value. + */ + mvmsta->max_agg_bufsize = + min(mvmsta->max_agg_bufsize, buf_size); + mvmsta->lq_sta.rs_drv.lq.agg_frame_cnt_limit = mvmsta->max_agg_bufsize; + + IWL_DEBUG_HT(mvm, "Tx aggregation enabled on ra = %pM tid = %d\n", + sta->addr, tid); + + return iwl_mvm_send_lq_cmd(mvm, &mvmsta->lq_sta.rs_drv.lq); +} + +static void iwl_mvm_unreserve_agg_queue(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, + struct iwl_mvm_tid_data *tid_data) +{ + u16 txq_id = tid_data->txq_id; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_has_new_tx_api(mvm)) + return; + + /* + * The TXQ is marked as reserved only if no traffic came through yet + * This means no traffic has been sent on this TID (agg'd or not), so + * we no longer have use for the queue. Since it hasn't even been + * allocated through iwl_mvm_enable_txq, so we can just mark it back as + * free. + */ + if (mvm->queue_info[txq_id].status == IWL_MVM_QUEUE_RESERVED) { + mvm->queue_info[txq_id].status = IWL_MVM_QUEUE_FREE; + tid_data->txq_id = IWL_MVM_INVALID_QUEUE; + } +} + +int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + u16 txq_id; + int err; + + /* + * If mac80211 is cleaning its state, then say that we finished since + * our state has been cleared anyway. + */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + return 0; + } + + spin_lock_bh(&mvmsta->lock); + + txq_id = tid_data->txq_id; + + IWL_DEBUG_TX_QUEUES(mvm, "Stop AGG: sta %d tid %d q %d state %d\n", + mvmsta->sta_id, tid, txq_id, tid_data->state); + + mvmsta->agg_tids &= ~BIT(tid); + + iwl_mvm_unreserve_agg_queue(mvm, mvmsta, tid_data); + + switch (tid_data->state) { + case IWL_AGG_ON: + tid_data->ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + + IWL_DEBUG_TX_QUEUES(mvm, + "ssn = %d, next_recl = %d\n", + tid_data->ssn, tid_data->next_reclaimed); + + tid_data->ssn = 0xffff; + tid_data->state = IWL_AGG_OFF; + spin_unlock_bh(&mvmsta->lock); + + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + + iwl_mvm_sta_tx_agg(mvm, sta, tid, txq_id, false); + return 0; + case IWL_AGG_STARTING: + case IWL_EMPTYING_HW_QUEUE_ADDBA: + /* + * The agg session has been stopped before it was set up. This + * can happen when the AddBA timer times out for example. + */ + + /* No barriers since we are under mutex */ + lockdep_assert_held(&mvm->mutex); + + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + tid_data->state = IWL_AGG_OFF; + err = 0; + break; + default: + IWL_ERR(mvm, + "Stopping AGG while state not ON or starting for %d on %d (%d)\n", + mvmsta->sta_id, tid, tid_data->state); + IWL_ERR(mvm, + "\ttid_data->txq_id = %d\n", tid_data->txq_id); + err = -EINVAL; + } + + spin_unlock_bh(&mvmsta->lock); + + return err; +} + +int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + u16 txq_id; + enum iwl_mvm_agg_state old_state; + + /* + * First set the agg state to OFF to avoid calling + * ieee80211_stop_tx_ba_cb in iwl_mvm_check_ratid_empty. + */ + spin_lock_bh(&mvmsta->lock); + txq_id = tid_data->txq_id; + IWL_DEBUG_TX_QUEUES(mvm, "Flush AGG: sta %d tid %d q %d state %d\n", + mvmsta->sta_id, tid, txq_id, tid_data->state); + old_state = tid_data->state; + tid_data->state = IWL_AGG_OFF; + mvmsta->agg_tids &= ~BIT(tid); + spin_unlock_bh(&mvmsta->lock); + + iwl_mvm_unreserve_agg_queue(mvm, mvmsta, tid_data); + + if (old_state >= IWL_AGG_ON) { + iwl_mvm_drain_sta(mvm, mvmsta, true); + + if (iwl_mvm_has_new_tx_api(mvm)) { + if (iwl_mvm_flush_sta_tids(mvm, mvmsta->sta_id, + BIT(tid))) + IWL_ERR(mvm, "Couldn't flush the AGG queue\n"); + iwl_trans_wait_txq_empty(mvm->trans, txq_id); + } else { + if (iwl_mvm_flush_tx_path(mvm, BIT(txq_id))) + IWL_ERR(mvm, "Couldn't flush the AGG queue\n"); + iwl_trans_wait_tx_queues_empty(mvm->trans, BIT(txq_id)); + } + + iwl_mvm_drain_sta(mvm, mvmsta, false); + + iwl_mvm_sta_tx_agg(mvm, sta, tid, txq_id, false); + } + + return 0; +} + +static int iwl_mvm_set_fw_key_idx(struct iwl_mvm *mvm) +{ + int i, max = -1, max_offs = -1; + + lockdep_assert_held(&mvm->mutex); + + /* Pick the unused key offset with the highest 'deleted' + * counter. Every time a key is deleted, all the counters + * are incremented and the one that was just deleted is + * reset to zero. Thus, the highest counter is the one + * that was deleted longest ago. Pick that one. + */ + for (i = 0; i < STA_KEY_MAX_NUM; i++) { + if (test_bit(i, mvm->fw_key_table)) + continue; + if (mvm->fw_key_deleted[i] > max) { + max = mvm->fw_key_deleted[i]; + max_offs = i; + } + } + + if (max_offs < 0) + return STA_KEY_IDX_INVALID; + + return max_offs; +} + +static struct iwl_mvm_sta *iwl_mvm_get_key_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (sta) + return iwl_mvm_sta_from_mac80211(sta); + + /* + * The device expects GTKs for station interfaces to be + * installed as GTKs for the AP station. If we have no + * station ID, then use AP's station ID. + */ + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) { + u8 sta_id = mvmvif->ap_sta_id; + + sta = rcu_dereference_check(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + /* + * It is possible that the 'sta' parameter is NULL, + * for example when a GTK is removed - the sta_id will then + * be the AP ID, and no station was passed by mac80211. + */ + if (IS_ERR_OR_NULL(sta)) + return NULL; + + return iwl_mvm_sta_from_mac80211(sta); + } + + return NULL; +} + +static int iwl_mvm_pn_cmp(const u8 *pn1, const u8 *pn2, int len) +{ + int i; + + for (i = len - 1; i >= 0; i--) { + if (pn1[i] > pn2[i]) + return 1; + if (pn1[i] < pn2[i]) + return -1; + } + + return 0; +} + +static int iwl_mvm_send_sta_key(struct iwl_mvm *mvm, + u32 sta_id, + struct ieee80211_key_conf *key, bool mcast, + u32 tkip_iv32, u16 *tkip_p1k, u32 cmd_flags, + u8 key_offset, bool mfp) +{ + union { + struct iwl_mvm_add_sta_key_cmd_v1 cmd_v1; + struct iwl_mvm_add_sta_key_cmd cmd; + } u = {}; + __le16 key_flags; + int ret; + u32 status; + u16 keyidx; + u64 pn = 0; + int i, size; + bool new_api = fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_TKIP_MIC_KEYS); + int api_ver = iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + ADD_STA_KEY, + new_api ? 2 : 1); + + if (sta_id == IWL_MVM_INVALID_STA) + return -EINVAL; + + keyidx = (key->keyidx << STA_KEY_FLG_KEYID_POS) & + STA_KEY_FLG_KEYID_MSK; + key_flags = cpu_to_le16(keyidx); + key_flags |= cpu_to_le16(STA_KEY_FLG_WEP_KEY_MAP); + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + key_flags |= cpu_to_le16(STA_KEY_FLG_TKIP); + if (api_ver >= 2) { + memcpy((void *)&u.cmd.tx_mic_key, + &key->key[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY], + IWL_MIC_KEY_SIZE); + + memcpy((void *)&u.cmd.rx_mic_key, + &key->key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY], + IWL_MIC_KEY_SIZE); + pn = atomic64_read(&key->tx_pn); + + } else { + u.cmd_v1.tkip_rx_tsc_byte2 = tkip_iv32; + for (i = 0; i < 5; i++) + u.cmd_v1.tkip_rx_ttak[i] = + cpu_to_le16(tkip_p1k[i]); + } + memcpy(u.cmd.common.key, key->key, key->keylen); + break; + case WLAN_CIPHER_SUITE_CCMP: + key_flags |= cpu_to_le16(STA_KEY_FLG_CCM); + memcpy(u.cmd.common.key, key->key, key->keylen); + if (api_ver >= 2) + pn = atomic64_read(&key->tx_pn); + break; + case WLAN_CIPHER_SUITE_WEP104: + key_flags |= cpu_to_le16(STA_KEY_FLG_WEP_13BYTES); + fallthrough; + case WLAN_CIPHER_SUITE_WEP40: + key_flags |= cpu_to_le16(STA_KEY_FLG_WEP); + memcpy(u.cmd.common.key + 3, key->key, key->keylen); + break; + case WLAN_CIPHER_SUITE_GCMP_256: + key_flags |= cpu_to_le16(STA_KEY_FLG_KEY_32BYTES); + fallthrough; + case WLAN_CIPHER_SUITE_GCMP: + key_flags |= cpu_to_le16(STA_KEY_FLG_GCMP); + memcpy(u.cmd.common.key, key->key, key->keylen); + if (api_ver >= 2) + pn = atomic64_read(&key->tx_pn); + break; + default: + key_flags |= cpu_to_le16(STA_KEY_FLG_EXT); + memcpy(u.cmd.common.key, key->key, key->keylen); + } + + if (mcast) + key_flags |= cpu_to_le16(STA_KEY_MULTICAST); + if (mfp) + key_flags |= cpu_to_le16(STA_KEY_MFP); + + u.cmd.common.key_offset = key_offset; + u.cmd.common.key_flags = key_flags; + u.cmd.common.sta_id = sta_id; + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) + i = 0; + else + i = -1; + + for (; i < IEEE80211_NUM_TIDS; i++) { + struct ieee80211_key_seq seq = {}; + u8 _rx_pn[IEEE80211_MAX_PN_LEN] = {}, *rx_pn = _rx_pn; + int rx_pn_len = 8; + /* there's a hole at 2/3 in FW format depending on version */ + int hole = api_ver >= 3 ? 0 : 2; + + ieee80211_get_key_rx_seq(key, i, &seq); + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + rx_pn[0] = seq.tkip.iv16; + rx_pn[1] = seq.tkip.iv16 >> 8; + rx_pn[2 + hole] = seq.tkip.iv32; + rx_pn[3 + hole] = seq.tkip.iv32 >> 8; + rx_pn[4 + hole] = seq.tkip.iv32 >> 16; + rx_pn[5 + hole] = seq.tkip.iv32 >> 24; + } else if (key_flags & cpu_to_le16(STA_KEY_FLG_EXT)) { + rx_pn = seq.hw.seq; + rx_pn_len = seq.hw.seq_len; + } else { + rx_pn[0] = seq.ccmp.pn[0]; + rx_pn[1] = seq.ccmp.pn[1]; + rx_pn[2 + hole] = seq.ccmp.pn[2]; + rx_pn[3 + hole] = seq.ccmp.pn[3]; + rx_pn[4 + hole] = seq.ccmp.pn[4]; + rx_pn[5 + hole] = seq.ccmp.pn[5]; + } + + if (iwl_mvm_pn_cmp(rx_pn, (u8 *)&u.cmd.common.rx_secur_seq_cnt, + rx_pn_len) > 0) + memcpy(&u.cmd.common.rx_secur_seq_cnt, rx_pn, + rx_pn_len); + } + + if (api_ver >= 2) { + u.cmd.transmit_seq_cnt = cpu_to_le64(pn); + size = sizeof(u.cmd); + } else { + size = sizeof(u.cmd_v1); + } + + status = ADD_STA_SUCCESS; + if (cmd_flags & CMD_ASYNC) + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA_KEY, CMD_ASYNC, size, + &u.cmd); + else + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA_KEY, size, + &u.cmd, &status); + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_WEP(mvm, "MODIFY_STA: set dynamic key passed\n"); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "MODIFY_STA: set dynamic key failed\n"); + break; + } + + return ret; +} + +static int iwl_mvm_send_sta_igtk(struct iwl_mvm *mvm, + struct ieee80211_key_conf *keyconf, + u8 sta_id, bool remove_key) +{ + struct iwl_mvm_mgmt_mcast_key_cmd igtk_cmd = {}; + + /* verify the key details match the required command's expectations */ + if (WARN_ON((keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE) || + (keyconf->keyidx != 4 && keyconf->keyidx != 5 && + keyconf->keyidx != 6 && keyconf->keyidx != 7) || + (keyconf->cipher != WLAN_CIPHER_SUITE_AES_CMAC && + keyconf->cipher != WLAN_CIPHER_SUITE_BIP_GMAC_128 && + keyconf->cipher != WLAN_CIPHER_SUITE_BIP_GMAC_256))) + return -EINVAL; + + if (WARN_ON(!iwl_mvm_has_new_rx_api(mvm) && + keyconf->cipher != WLAN_CIPHER_SUITE_AES_CMAC)) + return -EINVAL; + + igtk_cmd.key_id = cpu_to_le32(keyconf->keyidx); + igtk_cmd.sta_id = cpu_to_le32(sta_id); + + if (remove_key) { + /* This is a valid situation for IGTK */ + if (sta_id == IWL_MVM_INVALID_STA) + return 0; + + igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_NOT_VALID); + } else { + struct ieee80211_key_seq seq; + const u8 *pn; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_AES_CMAC: + igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_FLG_CCM); + break; + case WLAN_CIPHER_SUITE_BIP_GMAC_128: + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_FLG_GCMP); + break; + default: + return -EINVAL; + } + + memcpy(igtk_cmd.igtk, keyconf->key, keyconf->keylen); + if (keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) + igtk_cmd.ctrl_flags |= + cpu_to_le32(STA_KEY_FLG_KEY_32BYTES); + ieee80211_get_key_rx_seq(keyconf, 0, &seq); + pn = seq.aes_cmac.pn; + igtk_cmd.receive_seq_cnt = cpu_to_le64(((u64) pn[5] << 0) | + ((u64) pn[4] << 8) | + ((u64) pn[3] << 16) | + ((u64) pn[2] << 24) | + ((u64) pn[1] << 32) | + ((u64) pn[0] << 40)); + } + + IWL_DEBUG_INFO(mvm, "%s %sIGTK (%d) for sta %u\n", + remove_key ? "removing" : "installing", + keyconf->keyidx >= 6 ? "B" : "", + keyconf->keyidx, igtk_cmd.sta_id); + + if (!iwl_mvm_has_new_rx_api(mvm)) { + struct iwl_mvm_mgmt_mcast_key_cmd_v1 igtk_cmd_v1 = { + .ctrl_flags = igtk_cmd.ctrl_flags, + .key_id = igtk_cmd.key_id, + .sta_id = igtk_cmd.sta_id, + .receive_seq_cnt = igtk_cmd.receive_seq_cnt + }; + + memcpy(igtk_cmd_v1.igtk, igtk_cmd.igtk, + ARRAY_SIZE(igtk_cmd_v1.igtk)); + return iwl_mvm_send_cmd_pdu(mvm, MGMT_MCAST_KEY, 0, + sizeof(igtk_cmd_v1), &igtk_cmd_v1); + } + return iwl_mvm_send_cmd_pdu(mvm, MGMT_MCAST_KEY, 0, + sizeof(igtk_cmd), &igtk_cmd); +} + + +static inline u8 *iwl_mvm_get_mac_addr(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (sta) + return sta->addr; + + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) { + u8 sta_id = mvmvif->ap_sta_id; + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + return sta->addr; + } + + + return NULL; +} + +static int __iwl_mvm_set_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf, + u8 key_offset, + bool mcast) +{ + const u8 *addr; + struct ieee80211_key_seq seq; + u16 p1k[5]; + u32 sta_id; + bool mfp = false; + + if (sta) { + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + sta_id = mvm_sta->sta_id; + mfp = sta->mfp; + } else if (vif->type == NL80211_IFTYPE_AP && + !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + sta_id = mvmvif->mcast_sta.sta_id; + } else { + IWL_ERR(mvm, "Failed to find station id\n"); + return -EINVAL; + } + + if (keyconf->cipher == WLAN_CIPHER_SUITE_TKIP) { + addr = iwl_mvm_get_mac_addr(mvm, vif, sta); + /* get phase 1 key from mac80211 */ + ieee80211_get_key_rx_seq(keyconf, 0, &seq); + ieee80211_get_tkip_rx_p1k(keyconf, addr, seq.tkip.iv32, p1k); + + return iwl_mvm_send_sta_key(mvm, sta_id, keyconf, mcast, + seq.tkip.iv32, p1k, 0, key_offset, + mfp); + } + + return iwl_mvm_send_sta_key(mvm, sta_id, keyconf, mcast, + 0, NULL, 0, key_offset, mfp); +} + +int iwl_mvm_set_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf, + u8 key_offset) +{ + bool mcast = !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE); + struct iwl_mvm_sta *mvm_sta; + u8 sta_id = IWL_MVM_INVALID_STA; + int ret; + static const u8 __maybe_unused zero_addr[ETH_ALEN] = {0}; + + lockdep_assert_held(&mvm->mutex); + + if (vif->type != NL80211_IFTYPE_AP || + keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE) { + /* Get the station id from the mvm local station table */ + mvm_sta = iwl_mvm_get_key_sta(mvm, vif, sta); + if (!mvm_sta) { + IWL_ERR(mvm, "Failed to find station\n"); + return -EINVAL; + } + sta_id = mvm_sta->sta_id; + + /* + * It is possible that the 'sta' parameter is NULL, and thus + * there is a need to retrieve the sta from the local station + * table. + */ + if (!sta) { + sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta)) { + IWL_ERR(mvm, "Invalid station id\n"); + return -EINVAL; + } + } + + if (WARN_ON_ONCE(iwl_mvm_sta_from_mac80211(sta)->vif != vif)) + return -EINVAL; + } else { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + sta_id = mvmvif->mcast_sta.sta_id; + } + + if (keyconf->cipher == WLAN_CIPHER_SUITE_AES_CMAC || + keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_128 || + keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) { + ret = iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, false); + goto end; + } + + /* If the key_offset is not pre-assigned, we need to find a + * new offset to use. In normal cases, the offset is not + * pre-assigned, but during HW_RESTART we want to reuse the + * same indices, so we pass them when this function is called. + * + * In D3 entry, we need to hardcoded the indices (because the + * firmware hardcodes the PTK offset to 0). In this case, we + * need to make sure we don't overwrite the hw_key_idx in the + * keyconf structure, because otherwise we cannot configure + * the original ones back when resuming. + */ + if (key_offset == STA_KEY_IDX_INVALID) { + key_offset = iwl_mvm_set_fw_key_idx(mvm); + if (key_offset == STA_KEY_IDX_INVALID) + return -ENOSPC; + keyconf->hw_key_idx = key_offset; + } + + ret = __iwl_mvm_set_sta_key(mvm, vif, sta, keyconf, key_offset, mcast); + if (ret) + goto end; + + /* + * For WEP, the same key is used for multicast and unicast. Upload it + * again, using the same key offset, and now pointing the other one + * to the same key slot (offset). + * If this fails, remove the original as well. + */ + if ((keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || + keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) && + sta) { + ret = __iwl_mvm_set_sta_key(mvm, vif, sta, keyconf, + key_offset, !mcast); + if (ret) { + __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, mcast); + goto end; + } + } + + __set_bit(key_offset, mvm->fw_key_table); + +end: + IWL_DEBUG_WEP(mvm, "key: cipher=%x len=%d idx=%d sta=%pM ret=%d\n", + keyconf->cipher, keyconf->keylen, keyconf->keyidx, + sta ? sta->addr : zero_addr, ret); + return ret; +} + +int iwl_mvm_remove_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf) +{ + bool mcast = !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE); + struct iwl_mvm_sta *mvm_sta; + u8 sta_id = IWL_MVM_INVALID_STA; + int ret, i; + + lockdep_assert_held(&mvm->mutex); + + /* Get the station from the mvm local station table */ + mvm_sta = iwl_mvm_get_key_sta(mvm, vif, sta); + if (mvm_sta) + sta_id = mvm_sta->sta_id; + else if (!sta && vif->type == NL80211_IFTYPE_AP && mcast) + sta_id = iwl_mvm_vif_from_mac80211(vif)->mcast_sta.sta_id; + + + IWL_DEBUG_WEP(mvm, "mvm remove dynamic key: idx=%d sta=%d\n", + keyconf->keyidx, sta_id); + + if (keyconf->cipher == WLAN_CIPHER_SUITE_AES_CMAC || + keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_128 || + keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) + return iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, true); + + if (!__test_and_clear_bit(keyconf->hw_key_idx, mvm->fw_key_table)) { + IWL_ERR(mvm, "offset %d not used in fw key table.\n", + keyconf->hw_key_idx); + return -ENOENT; + } + + /* track which key was deleted last */ + for (i = 0; i < STA_KEY_MAX_NUM; i++) { + if (mvm->fw_key_deleted[i] < U8_MAX) + mvm->fw_key_deleted[i]++; + } + mvm->fw_key_deleted[keyconf->hw_key_idx] = 0; + + if (sta && !mvm_sta) { + IWL_DEBUG_WEP(mvm, "station non-existent, early return.\n"); + return 0; + } + + ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, mcast); + if (ret) + return ret; + + /* delete WEP key twice to get rid of (now useless) offset */ + if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || + keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) + ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, !mcast); + + return ret; +} + +void iwl_mvm_update_tkip_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, + u16 *phase1key) +{ + struct iwl_mvm_sta *mvm_sta; + bool mcast = !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE); + bool mfp = sta ? sta->mfp : false; + + rcu_read_lock(); + + mvm_sta = iwl_mvm_get_key_sta(mvm, vif, sta); + if (WARN_ON_ONCE(!mvm_sta)) + goto unlock; + iwl_mvm_send_sta_key(mvm, mvm_sta->sta_id, keyconf, mcast, + iv32, phase1key, CMD_ASYNC, keyconf->hw_key_idx, + mfp); + + unlock: + rcu_read_unlock(); +} + +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = mvmsta->sta_id, + .station_flags_msk = cpu_to_le32(STA_FLG_PS), + .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} + +void iwl_mvm_sta_modify_sleep_tx_count(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + enum ieee80211_frame_release_type reason, + u16 cnt, u16 tids, bool more_data, + bool single_sta_queue) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = mvmsta->sta_id, + .modify_mask = STA_MODIFY_SLEEPING_STA_TX_COUNT, + .sleep_tx_count = cpu_to_le16(cnt), + .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color), + }; + int tid, ret; + unsigned long _tids = tids; + + /* convert TIDs to ACs - we don't support TSPEC so that's OK + * Note that this field is reserved and unused by firmware not + * supporting GO uAPSD, so it's safe to always do this. + */ + for_each_set_bit(tid, &_tids, IWL_MAX_TID_COUNT) + cmd.awake_acs |= BIT(tid_to_ucode_ac[tid]); + + /* If we're releasing frames from aggregation or dqa queues then check + * if all the queues that we're releasing frames from, combined, have: + * - more frames than the service period, in which case more_data + * needs to be set + * - fewer than 'cnt' frames, in which case we need to adjust the + * firmware command (but do that unconditionally) + */ + if (single_sta_queue) { + int remaining = cnt; + int sleep_tx_count; + + spin_lock_bh(&mvmsta->lock); + for_each_set_bit(tid, &_tids, IWL_MAX_TID_COUNT) { + struct iwl_mvm_tid_data *tid_data; + u16 n_queued; + + tid_data = &mvmsta->tid_data[tid]; + + n_queued = iwl_mvm_tid_queued(mvm, tid_data); + if (n_queued > remaining) { + more_data = true; + remaining = 0; + break; + } + remaining -= n_queued; + } + sleep_tx_count = cnt - remaining; + if (reason == IEEE80211_FRAME_RELEASE_UAPSD) + mvmsta->sleep_tx_count = sleep_tx_count; + spin_unlock_bh(&mvmsta->lock); + + cmd.sleep_tx_count = cpu_to_le16(sleep_tx_count); + if (WARN_ON(cnt - remaining == 0)) { + ieee80211_sta_eosp(sta); + return; + } + } + + /* Note: this is ignored by firmware not supporting GO uAPSD */ + if (more_data) + cmd.sleep_state_flags |= STA_SLEEP_STATE_MOREDATA; + + if (reason == IEEE80211_FRAME_RELEASE_PSPOLL) { + mvmsta->next_status_eosp = true; + cmd.sleep_state_flags |= STA_SLEEP_STATE_PS_POLL; + } else { + cmd.sleep_state_flags |= STA_SLEEP_STATE_UAPSD; + } + + /* block the Tx queues until the FW updated the sleep Tx count */ + iwl_trans_block_txq_ptrs(mvm->trans, true); + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, + CMD_ASYNC | CMD_WANT_ASYNC_CALLBACK, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} + +void iwl_mvm_rx_eosp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_eosp_notification *notif = (void *)pkt->data; + struct ieee80211_sta *sta; + u32 sta_id = le32_to_cpu(notif->sta_id); + + if (WARN_ON_ONCE(sta_id >= mvm->fw->ucode_capa.num_stations)) + return; + + rcu_read_lock(); + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (!IS_ERR_OR_NULL(sta)) + ieee80211_sta_eosp(sta); + rcu_read_unlock(); +} + +void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, bool disable) +{ + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = mvmsta->sta_id, + .station_flags = disable ? cpu_to_le32(STA_FLG_DISABLE_TX) : 0, + .station_flags_msk = cpu_to_le32(STA_FLG_DISABLE_TX), + .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} + +void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + bool disable) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock_bh(&mvm_sta->lock); + + if (mvm_sta->disable_tx == disable) { + spin_unlock_bh(&mvm_sta->lock); + return; + } + + mvm_sta->disable_tx = disable; + + /* + * If sta PS state is handled by mac80211, tell it to start/stop + * queuing tx for this station. + */ + if (!ieee80211_hw_check(mvm->hw, AP_LINK_PS)) + ieee80211_sta_block_awake(mvm->hw, sta, disable); + + iwl_mvm_sta_modify_disable_tx(mvm, mvm_sta, disable); + + spin_unlock_bh(&mvm_sta->lock); +} + +static void iwl_mvm_int_sta_modify_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_int_sta *sta, + bool disable) +{ + u32 id = FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color); + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = sta->sta_id, + .station_flags = disable ? cpu_to_le32(STA_FLG_DISABLE_TX) : 0, + .station_flags_msk = cpu_to_le32(STA_FLG_DISABLE_TX), + .mac_id_n_color = cpu_to_le32(id), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} + +void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + bool disable) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvm_sta; + int i; + + rcu_read_lock(); + + /* Block/unblock all the stations of the given mvmvif */ + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + sta = rcu_dereference(mvm->fw_id_to_mac_id[i]); + if (IS_ERR_OR_NULL(sta)) + continue; + + mvm_sta = iwl_mvm_sta_from_mac80211(sta); + if (mvm_sta->mac_id_n_color != + FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)) + continue; + + iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, disable); + } + + rcu_read_unlock(); + + if (!fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + return; + + /* Need to block/unblock also multicast station */ + if (mvmvif->mcast_sta.sta_id != IWL_MVM_INVALID_STA) + iwl_mvm_int_sta_modify_disable_tx(mvm, mvmvif, + &mvmvif->mcast_sta, disable); + + /* + * Only unblock the broadcast station (FW blocks it for immediate + * quiet, not the driver) + */ + if (!disable && mvmvif->bcast_sta.sta_id != IWL_MVM_INVALID_STA) + iwl_mvm_int_sta_modify_disable_tx(mvm, mvmvif, + &mvmvif->bcast_sta, disable); +} + +void iwl_mvm_csa_client_absent(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvmsta; + + rcu_read_lock(); + + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, mvmvif->ap_sta_id); + + if (!WARN_ON(!mvmsta)) + iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, true); + + rcu_read_unlock(); +} + +u16 iwl_mvm_tid_queued(struct iwl_mvm *mvm, struct iwl_mvm_tid_data *tid_data) +{ + u16 sn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + + /* + * In 22000 HW, the next_reclaimed index is only 8 bit, so we'll need + * to align the wrap around of ssn so we compare relevant values. + */ + if (mvm->trans->trans_cfg->gen2) + sn &= 0xff; + + return ieee80211_sn_sub(sn, tid_data->next_reclaimed); +} + +int iwl_mvm_add_pasn_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_int_sta *sta, u8 *addr, u32 cipher, + u8 *key, u32 key_len) +{ + int ret; + u16 queue; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_key_conf *keyconf; + + ret = iwl_mvm_allocate_int_sta(mvm, sta, 0, + NL80211_IFTYPE_UNSPECIFIED, + IWL_STA_LINK); + if (ret) + return ret; + + ret = iwl_mvm_add_int_sta_with_queue(mvm, mvmvif->id, mvmvif->color, + addr, sta, &queue, + IWL_MVM_TX_FIFO_BE); + if (ret) + goto out; + + keyconf = kzalloc(sizeof(*keyconf) + key_len, GFP_KERNEL); + if (!keyconf) { + ret = -ENOBUFS; + goto out; + } + + keyconf->cipher = cipher; + memcpy(keyconf->key, key, key_len); + keyconf->keylen = key_len; + + ret = iwl_mvm_send_sta_key(mvm, sta->sta_id, keyconf, false, + 0, NULL, 0, 0, true); + kfree(keyconf); + return 0; +out: + iwl_mvm_dealloc_int_sta(mvm, sta); + return ret; +} diff --git a/sys/contrib/dev/iwlwifi/mvm/sta.h b/sys/contrib/dev/iwlwifi/mvm/sta.h new file mode 100644 index 00000000000..32b4d193578 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/sta.h @@ -0,0 +1,549 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2016 Intel Deutschland GmbH + */ +#ifndef __sta_h__ +#define __sta_h__ + +#include +#include +#include + +#include "iwl-trans.h" /* for IWL_MAX_TID_COUNT */ +#include "fw-api.h" /* IWL_MVM_STATION_COUNT_MAX */ +#include "rs.h" + +struct iwl_mvm; +struct iwl_mvm_vif; + +/** + * DOC: DQA - Dynamic Queue Allocation -introduction + * + * Dynamic Queue Allocation (AKA "DQA") is a feature implemented in iwlwifi + * driver to allow dynamic allocation of queues on-demand, rather than allocate + * them statically ahead of time. Ideally, we would like to allocate one queue + * per RA/TID, thus allowing an AP - for example - to send BE traffic to STA2 + * even if it also needs to send traffic to a sleeping STA1, without being + * blocked by the sleeping station. + * + * Although the queues in DQA mode are dynamically allocated, there are still + * some queues that are statically allocated: + * TXQ #0 - command queue + * TXQ #1 - aux frames + * TXQ #2 - P2P device frames + * TXQ #3 - P2P GO/SoftAP GCAST/BCAST frames + * TXQ #4 - BSS DATA frames queue + * TXQ #5-8 - Non-QoS and MGMT frames queue pool + * TXQ #9 - P2P GO/SoftAP probe responses + * TXQ #10-31 - DATA frames queue pool + * The queues are dynamically taken from either the MGMT frames queue pool or + * the DATA frames one. See the %iwl_mvm_dqa_txq for more information on every + * queue. + * + * When a frame for a previously unseen RA/TID comes in, it needs to be deferred + * until a queue is allocated for it, and only then can be TXed. Therefore, it + * is placed into %iwl_mvm_tid_data.deferred_tx_frames, and a worker called + * %mvm->add_stream_wk later allocates the queues and TXes the deferred frames. + * + * For convenience, MGMT is considered as if it has TID=8, and go to the MGMT + * queues in the pool. If there is no longer a free MGMT queue to allocate, a + * queue will be allocated from the DATA pool instead. Since QoS NDPs can create + * a problem for aggregations, they too will use a MGMT queue. + * + * When adding a STA, a DATA queue is reserved for it so that it can TX from + * it. If no such free queue exists for reserving, the STA addition will fail. + * + * If the DATA queue pool gets exhausted, no new STA will be accepted, and if a + * new RA/TID comes in for an existing STA, one of the STA's queues will become + * shared and will serve more than the single TID (but always for the same RA!). + * + * When a RA/TID needs to become aggregated, no new queue is required to be + * allocated, only mark the queue as aggregated via the ADD_STA command. Note, + * however, that a shared queue cannot be aggregated, and only after the other + * TIDs become inactive and are removed - only then can the queue be + * reconfigured and become aggregated. + * + * When removing a station, its queues are returned to the pool for reuse. Here + * we also need to make sure that we are synced with the worker thread that TXes + * the deferred frames so we don't get into a situation where the queues are + * removed and then the worker puts deferred frames onto the released queues or + * tries to allocate new queues for a STA we don't need anymore. + */ + +/** + * DOC: station table - introduction + * + * The station table is a list of data structure that reprensent the stations. + * In STA/P2P client mode, the driver will hold one station for the AP/ GO. + * In GO/AP mode, the driver will have as many stations as associated clients. + * All these stations are reflected in the fw's station table. The driver + * keeps the fw's station table up to date with the ADD_STA command. Stations + * can be removed by the REMOVE_STA command. + * + * All the data related to a station is held in the structure %iwl_mvm_sta + * which is embed in the mac80211's %ieee80211_sta (in the drv_priv) area. + * This data includes the index of the station in the fw, per tid information + * (sequence numbers, Block-ack state machine, etc...). The stations are + * created and deleted by the %sta_state callback from %ieee80211_ops. + * + * The driver holds a map: %fw_id_to_mac_id that allows to fetch a + * %ieee80211_sta (and the %iwl_mvm_sta embedded into it) based on a fw + * station index. That way, the driver is able to get the tid related data in + * O(1) in time sensitive paths (Tx / Tx response / BA notification). These + * paths are triggered by the fw, and the driver needs to get a pointer to the + * %ieee80211 structure. This map helps to get that pointer quickly. + */ + +/** + * DOC: station table - locking + * + * As stated before, the station is created / deleted by mac80211's %sta_state + * callback from %ieee80211_ops which can sleep. The next paragraph explains + * the locking of a single stations, the next ones relates to the station + * table. + * + * The station holds the sequence number per tid. So this data needs to be + * accessed in the Tx path (which is softIRQ). It also holds the Block-Ack + * information (the state machine / and the logic that checks if the queues + * were drained), so it also needs to be accessible from the Tx response flow. + * In short, the station needs to be access from sleepable context as well as + * from tasklets, so the station itself needs a spinlock. + * + * The writers of %fw_id_to_mac_id map are serialized by the global mutex of + * the mvm op_mode. This is possible since %sta_state can sleep. + * The pointers in this map are RCU protected, hence we won't replace the + * station while we have Tx / Tx response / BA notification running. + * + * If a station is deleted while it still has packets in its A-MPDU queues, + * then the reclaim flow will notice that there is no station in the map for + * sta_id and it will dump the responses. + */ + +/** + * DOC: station table - internal stations + * + * The FW needs a few internal stations that are not reflected in + * mac80211, such as broadcast station in AP / GO mode, or AUX sta for + * scanning and P2P device (during the GO negotiation). + * For these kind of stations we have %iwl_mvm_int_sta struct which holds the + * data relevant for them from both %iwl_mvm_sta and %ieee80211_sta. + * Usually the data for these stations is static, so no locking is required, + * and no TID data as this is also not needed. + * One thing to note, is that these stations have an ID in the fw, but not + * in mac80211. In order to "reserve" them a sta_id in %fw_id_to_mac_id + * we fill ERR_PTR(EINVAL) in this mapping and all other dereferencing of + * pointers from this mapping need to check that the value is not error + * or NULL. + * + * Currently there is only one auxiliary station for scanning, initialized + * on init. + */ + +/** + * DOC: station table - AP Station in STA mode + * + * %iwl_mvm_vif includes the index of the AP station in the fw's STA table: + * %ap_sta_id. To get the point to the corresponding %ieee80211_sta, + * &fw_id_to_mac_id can be used. Due to the way the fw works, we must not remove + * the AP station from the fw before setting the MAC context as unassociated. + * Hence, %fw_id_to_mac_id[%ap_sta_id] will be NULLed when the AP station is + * removed by mac80211, but the station won't be removed in the fw until the + * VIF is set as unassociated. Then, %ap_sta_id will be invalidated. + */ + +/** + * DOC: station table - Drain vs. Flush + * + * Flush means that all the frames in the SCD queue are dumped regardless the + * station to which they were sent. We do that when we disassociate and before + * we remove the STA of the AP. The flush can be done synchronously against the + * fw. + * Drain means that the fw will drop all the frames sent to a specific station. + * This is useful when a client (if we are IBSS / GO or AP) disassociates. + */ + +/** + * DOC: station table - fw restart + * + * When the fw asserts, or we have any other issue that requires to reset the + * driver, we require mac80211 to reconfigure the driver. Since the private + * data of the stations is embed in mac80211's %ieee80211_sta, that data will + * not be zeroed and needs to be reinitialized manually. + * %IWL_MVM_STATUS_IN_HW_RESTART is set during restart and that will hint us + * that we must not allocate a new sta_id but reuse the previous one. This + * means that the stations being re-added after the reset will have the same + * place in the fw as before the reset. We do need to zero the %fw_id_to_mac_id + * map, since the stations aren't in the fw any more. Internal stations that + * are not added by mac80211 will be re-added in the init flow that is called + * after the restart: mac80211 call's %iwl_mvm_mac_start which calls to + * %iwl_mvm_up. + */ + +/** + * DOC: AP mode - PS + * + * When a station is asleep, the fw will set it as "asleep". All frames on + * shared queues (i.e. non-aggregation queues) to that station will be dropped + * by the fw (%TX_STATUS_FAIL_DEST_PS failure code). + * + * AMPDUs are in a separate queue that is stopped by the fw. We just need to + * let mac80211 know when there are frames in these queues so that it can + * properly handle trigger frames. + * + * When a trigger frame is received, mac80211 tells the driver to send frames + * from the AMPDU queues or sends frames to non-aggregation queues itself, + * depending on which ACs are delivery-enabled and what TID has frames to + * transmit. Note that mac80211 has all the knowledge since all the non-agg + * frames are buffered / filtered, and the driver tells mac80211 about agg + * frames). The driver needs to tell the fw to let frames out even if the + * station is asleep. This is done by %iwl_mvm_sta_modify_sleep_tx_count. + * + * When we receive a frame from that station with PM bit unset, the driver + * needs to let the fw know that this station isn't asleep any more. This is + * done by %iwl_mvm_sta_modify_ps_wake in response to mac80211 signaling the + * station's wakeup. + * + * For a GO, the Service Period might be cut short due to an absence period + * of the GO. In this (and all other cases) the firmware notifies us with the + * EOSP_NOTIFICATION, and we notify mac80211 of that. Further frames that we + * already sent to the device will be rejected again. + * + * See also "AP support for powersaving clients" in mac80211.h. + */ + +/** + * enum iwl_mvm_agg_state + * + * The state machine of the BA agreement establishment / tear down. + * These states relate to a specific RA / TID. + * + * @IWL_AGG_OFF: aggregation is not used + * @IWL_AGG_QUEUED: aggregation start work has been queued + * @IWL_AGG_STARTING: aggregation are starting (between start and oper) + * @IWL_AGG_ON: aggregation session is up + * @IWL_EMPTYING_HW_QUEUE_ADDBA: establishing a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + * @IWL_EMPTYING_HW_QUEUE_DELBA: tearing down a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + */ +enum iwl_mvm_agg_state { + IWL_AGG_OFF = 0, + IWL_AGG_QUEUED, + IWL_AGG_STARTING, + IWL_AGG_ON, + IWL_EMPTYING_HW_QUEUE_ADDBA, + IWL_EMPTYING_HW_QUEUE_DELBA, +}; + +/** + * struct iwl_mvm_tid_data - holds the states for each RA / TID + * @seq_number: the next WiFi sequence number to use + * @next_reclaimed: the WiFi sequence number of the next packet to be acked. + * This is basically (last acked packet++). + * @rate_n_flags: Rate at which Tx was attempted. Holds the data between the + * Tx response (TX_CMD), and the block ack notification (COMPRESSED_BA). + * @lq_color: the color of the LQ command as it appears in tx response. + * @amsdu_in_ampdu_allowed: true if A-MSDU in A-MPDU is allowed. + * @state: state of the BA agreement establishment / tear down. + * @txq_id: Tx queue used by the BA session / DQA + * @ssn: the first packet to be sent in AGG HW queue in Tx AGG start flow, or + * the first packet to be sent in legacy HW queue in Tx AGG stop flow. + * Basically when next_reclaimed reaches ssn, we can tell mac80211 that + * we are ready to finish the Tx AGG stop / start flow. + * @tx_time: medium time consumed by this A-MPDU + * @tpt_meas_start: time of the throughput measurements start, is reset every HZ + * @tx_count_last: number of frames transmitted during the last second + * @tx_count: counts the number of frames transmitted since the last reset of + * tpt_meas_start + */ +struct iwl_mvm_tid_data { + u16 seq_number; + u16 next_reclaimed; + /* The rest is Tx AGG related */ + u32 rate_n_flags; + u8 lq_color; + bool amsdu_in_ampdu_allowed; + enum iwl_mvm_agg_state state; + u16 txq_id; + u16 ssn; + u16 tx_time; + unsigned long tpt_meas_start; + u32 tx_count_last; + u32 tx_count; +}; + +struct iwl_mvm_key_pn { + struct rcu_head rcu_head; + struct { + u8 pn[IWL_MAX_TID_COUNT][IEEE80211_CCMP_PN_LEN]; + } ____cacheline_aligned_in_smp q[]; +}; + +/** + * enum iwl_mvm_rxq_notif_type - Internal message identifier + * + * @IWL_MVM_RXQ_EMPTY: empty sync notification + * @IWL_MVM_RXQ_NOTIF_DEL_BA: notify RSS queues of delBA + * @IWL_MVM_RXQ_NSSN_SYNC: notify all the RSS queues with the new NSSN + */ +enum iwl_mvm_rxq_notif_type { + IWL_MVM_RXQ_EMPTY, + IWL_MVM_RXQ_NOTIF_DEL_BA, + IWL_MVM_RXQ_NSSN_SYNC, +}; + +/** + * struct iwl_mvm_internal_rxq_notif - Internal representation of the data sent + * in &iwl_rxq_sync_cmd. Should be DWORD aligned. + * FW is agnostic to the payload, so there are no endianity requirements. + * + * @type: value from &iwl_mvm_rxq_notif_type + * @sync: ctrl path is waiting for all notifications to be received + * @cookie: internal cookie to identify old notifications + * @data: payload + */ +struct iwl_mvm_internal_rxq_notif { + u16 type; + u16 sync; + u32 cookie; + u8 data[]; +} __packed; + +struct iwl_mvm_delba_data { + u32 baid; +} __packed; + +struct iwl_mvm_nssn_sync_data { + u32 baid; + u32 nssn; +} __packed; + +/** + * struct iwl_mvm_rxq_dup_data - per station per rx queue data + * @last_seq: last sequence per tid for duplicate packet detection + * @last_sub_frame: last subframe packet + */ +struct iwl_mvm_rxq_dup_data { + __le16 last_seq[IWL_MAX_TID_COUNT + 1]; + u8 last_sub_frame[IWL_MAX_TID_COUNT + 1]; +} ____cacheline_aligned_in_smp; + +/** + * struct iwl_mvm_sta - representation of a station in the driver + * @sta_id: the index of the station in the fw (will be replaced by id_n_color) + * @tfd_queue_msk: the tfd queues used by the station + * @mac_id_n_color: the MAC context this station is linked to + * @tid_disable_agg: bitmap: if bit(tid) is set, the fw won't send ampdus for + * tid. + * @max_agg_bufsize: the maximal size of the AGG buffer for this station + * @sta_type: station type + * @sta_state: station state according to enum %ieee80211_sta_state + * @bt_reduced_txpower: is reduced tx power enabled for this station + * @next_status_eosp: the next reclaimed packet is a PS-Poll response and + * we need to signal the EOSP + * @lock: lock to protect the whole struct. Since %tid_data is access from Tx + * and from Tx response flow, it needs a spinlock. + * @tid_data: per tid data + mgmt. Look at %iwl_mvm_tid_data. + * @tid_to_baid: a simple map of TID to baid + * @lq_sta: holds rate scaling data, either for the case when RS is done in + * the driver - %rs_drv or in the FW - %rs_fw. + * @reserved_queue: the queue reserved for this STA for DQA purposes + * Every STA has is given one reserved queue to allow it to operate. If no + * such queue can be guaranteed, the STA addition will fail. + * @tx_protection: reference counter for controlling the Tx protection. + * @tt_tx_protection: is thermal throttling enable Tx protection? + * @disable_tx: is tx to this STA disabled? + * @amsdu_enabled: bitmap of TX AMSDU allowed TIDs. + * In case TLC offload is not active it is either 0xFFFF or 0. + * @max_amsdu_len: max AMSDU length + * @orig_amsdu_len: used to save the original amsdu_len when it is changed via + * debugfs. If it's set to 0, it means that it is it's not set via + * debugfs. + * @agg_tids: bitmap of tids whose status is operational aggregated (IWL_AGG_ON) + * @sleep_tx_count: the number of frames that we told the firmware to let out + * even when that station is asleep. This is useful in case the queue + * gets empty before all the frames were sent, which can happen when + * we are sending frames from an AMPDU queue and there was a hole in + * the BA window. To be used for UAPSD only. + * @ptk_pn: per-queue PTK PN data structures + * @dup_data: per queue duplicate packet detection data + * @deferred_traffic_tid_map: indication bitmap of deferred traffic per-TID + * @tx_ant: the index of the antenna to use for data tx to this station. Only + * used during connection establishment (e.g. for the 4 way handshake + * exchange). + * + * When mac80211 creates a station it reserves some space (hw->sta_data_size) + * in the structure for use by driver. This structure is placed in that + * space. + * + */ +struct iwl_mvm_sta { + u32 sta_id; + u32 tfd_queue_msk; + u32 mac_id_n_color; + u16 tid_disable_agg; + u16 max_agg_bufsize; + enum iwl_sta_type sta_type; + enum ieee80211_sta_state sta_state; + bool bt_reduced_txpower; + bool next_status_eosp; + spinlock_t lock; + struct iwl_mvm_tid_data tid_data[IWL_MAX_TID_COUNT + 1]; + u8 tid_to_baid[IWL_MAX_TID_COUNT]; + union { + struct iwl_lq_sta_rs_fw rs_fw; + struct iwl_lq_sta rs_drv; + } lq_sta; + struct ieee80211_vif *vif; + struct iwl_mvm_key_pn __rcu *ptk_pn[4]; + struct iwl_mvm_rxq_dup_data *dup_data; + + u8 reserved_queue; + + /* Temporary, until the new TLC will control the Tx protection */ + s8 tx_protection; + bool tt_tx_protection; + + bool disable_tx; + u16 amsdu_enabled; + u16 max_amsdu_len; + u16 orig_amsdu_len; + bool sleeping; + u8 agg_tids; + u8 sleep_tx_count; + u8 avg_energy; + u8 tx_ant; +}; + +u16 iwl_mvm_tid_queued(struct iwl_mvm *mvm, struct iwl_mvm_tid_data *tid_data); + +static inline struct iwl_mvm_sta * +iwl_mvm_sta_from_mac80211(struct ieee80211_sta *sta) +{ + return (void *)sta->drv_priv; +} + +/** + * struct iwl_mvm_int_sta - representation of an internal station (auxiliary or + * broadcast) + * @sta_id: the index of the station in the fw (will be replaced by id_n_color) + * @type: station type + * @tfd_queue_msk: the tfd queues used by the station + */ +struct iwl_mvm_int_sta { + u32 sta_id; + enum iwl_sta_type type; + u32 tfd_queue_msk; +}; + +/** + * Send the STA info to the FW. + * + * @mvm: the iwl_mvm* to use + * @sta: the STA + * @update: this is true if the FW is being updated about a STA it already knows + * about. Otherwise (if this is a new STA), this should be false. + * @flags: if update==true, this marks what is being changed via ORs of values + * from enum iwl_sta_modify_flag. Otherwise, this is ignored. + */ +int iwl_mvm_sta_send_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + bool update, unsigned int flags); +int iwl_mvm_add_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + +static inline int iwl_mvm_update_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + return iwl_mvm_sta_send_to_fw(mvm, sta, true, 0); +} + +int iwl_mvm_wait_sta_queues_empty(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvm_sta); +int iwl_mvm_rm_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int iwl_mvm_rm_sta_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 sta_id); +int iwl_mvm_set_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf, + u8 key_offset); +int iwl_mvm_remove_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf); + +void iwl_mvm_update_tkip_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, + u16 *phase1key); + +void iwl_mvm_rx_eosp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/* AMPDU */ +int iwl_mvm_sta_rx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u16 ssn, bool start, u16 buf_size, u16 timeout); +int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn); +int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 buf_size, + bool amsdu); +int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); +int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); + +int iwl_mvm_sta_tx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u8 queue, bool start); + +int iwl_mvm_add_aux_sta(struct iwl_mvm *mvm, u32 lmac_id); +int iwl_mvm_rm_aux_sta(struct iwl_mvm *mvm); + +int iwl_mvm_alloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_send_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_add_p2p_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_send_rm_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_rm_p2p_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_add_mcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_rm_mcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_allocate_int_sta(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *sta, + u32 qmask, enum nl80211_iftype iftype, + enum iwl_sta_type type); +void iwl_mvm_dealloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_dealloc_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta); +int iwl_mvm_add_snif_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_rm_snif_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_dealloc_snif_sta(struct iwl_mvm *mvm); + +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm, + struct ieee80211_sta *sta); +void iwl_mvm_sta_modify_sleep_tx_count(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + enum ieee80211_frame_release_type reason, + u16 cnt, u16 tids, bool more_data, + bool single_sta_queue); +int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool drain); +void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, bool disable); +void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + bool disable); +void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + bool disable); +void iwl_mvm_csa_client_absent(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_add_new_dqa_stream_wk(struct work_struct *wk); +int iwl_mvm_add_pasn_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_int_sta *sta, u8 *addr, u32 cipher, + u8 *key, u32 key_len); +#endif /* __sta_h__ */ diff --git a/sys/contrib/dev/iwlwifi/mvm/tdls.c b/sys/contrib/dev/iwlwifi/mvm/tdls.c new file mode 100644 index 00000000000..66d24980268 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/tdls.c @@ -0,0 +1,681 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2014 Intel Mobile Communications GmbH + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2020 Intel Corporation + */ +#if defined(__FreeBSD__) +#include +#endif +#include +#include "mvm.h" +#include "time-event.h" +#include "iwl-io.h" +#include "iwl-prph.h" + +#define TU_TO_US(x) (x * 1024) +#define TU_TO_MS(x) (TU_TO_US(x) / 1000) + +void iwl_mvm_teardown_tdls_peers(struct iwl_mvm *mvm) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + int i; + + lockdep_assert_held(&mvm->mutex); + + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (!sta || IS_ERR(sta) || !sta->tdls) + continue; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + ieee80211_tdls_oper_request(mvmsta->vif, sta->addr, + NL80211_TDLS_TEARDOWN, + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED, + GFP_KERNEL); + } +} + +int iwl_mvm_tdls_sta_count(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + int count = 0; + int i; + + lockdep_assert_held(&mvm->mutex); + + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (!sta || IS_ERR(sta) || !sta->tdls) + continue; + + if (vif) { + mvmsta = iwl_mvm_sta_from_mac80211(sta); + if (mvmsta->vif != vif) + continue; + } + + count++; + } + + return count; +} + +static void iwl_mvm_tdls_config(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_rx_packet *pkt; + struct iwl_tdls_config_res *resp; + struct iwl_tdls_config_cmd tdls_cfg_cmd = {}; + struct iwl_host_cmd cmd = { + .id = TDLS_CONFIG_CMD, + .flags = CMD_WANT_SKB, + .data = { &tdls_cfg_cmd, }, + .len = { sizeof(struct iwl_tdls_config_cmd), }, + }; + struct ieee80211_sta *sta; + int ret, i, cnt; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + lockdep_assert_held(&mvm->mutex); + + tdls_cfg_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + tdls_cfg_cmd.tx_to_ap_tid = IWL_MVM_TDLS_FW_TID; + tdls_cfg_cmd.tx_to_ap_ssn = cpu_to_le16(0); /* not used for now */ + + /* for now the Tx cmd is empty and unused */ + + /* populate TDLS peer data */ + cnt = 0; + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta) || !sta->tdls) + continue; + + tdls_cfg_cmd.sta_info[cnt].sta_id = i; + tdls_cfg_cmd.sta_info[cnt].tx_to_peer_tid = + IWL_MVM_TDLS_FW_TID; + tdls_cfg_cmd.sta_info[cnt].tx_to_peer_ssn = cpu_to_le16(0); + tdls_cfg_cmd.sta_info[cnt].is_initiator = + cpu_to_le32(sta->tdls_initiator ? 1 : 0); + + cnt++; + } + + tdls_cfg_cmd.tdls_peer_count = cnt; + IWL_DEBUG_TDLS(mvm, "send TDLS config to FW for %d peers\n", cnt); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (WARN_ON_ONCE(ret)) + return; + + pkt = cmd.resp_pkt; + + WARN_ON_ONCE(iwl_rx_packet_payload_len(pkt) != sizeof(*resp)); + + /* we don't really care about the response at this point */ + + iwl_free_resp(&cmd); +} + +void iwl_mvm_recalc_tdls_state(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool sta_added) +{ + int tdls_sta_cnt = iwl_mvm_tdls_sta_count(mvm, vif); + + /* when the first peer joins, send a power update first */ + if (tdls_sta_cnt == 1 && sta_added) + iwl_mvm_power_update_mac(mvm); + + /* Configure the FW with TDLS peer info only if TDLS channel switch + * capability is set. + * TDLS config data is used currently only in TDLS channel switch code. + * Supposed to serve also TDLS buffer station which is not implemneted + * yet in FW*/ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH)) + iwl_mvm_tdls_config(mvm, vif); + + /* when the last peer leaves, send a power update last */ + if (tdls_sta_cnt == 0 && !sta_added) + iwl_mvm_power_update_mac(mvm); +} + +void iwl_mvm_mac_mgd_protect_tdls_discover(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + u32 duration = 2 * vif->bss_conf.dtim_period * vif->bss_conf.beacon_int; + + /* Protect the session to hear the TDLS setup response on the channel */ + mutex_lock(&mvm->mutex); + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) + iwl_mvm_schedule_session_protection(mvm, vif, duration, + duration, true); + else + iwl_mvm_protect_session(mvm, vif, duration, + duration, 100, true); + mutex_unlock(&mvm->mutex); +} + +static const char * +iwl_mvm_tdls_cs_state_str(enum iwl_mvm_tdls_cs_state state) +{ + switch (state) { + case IWL_MVM_TDLS_SW_IDLE: + return "IDLE"; + case IWL_MVM_TDLS_SW_REQ_SENT: + return "REQ SENT"; + case IWL_MVM_TDLS_SW_RESP_RCVD: + return "RESP RECEIVED"; + case IWL_MVM_TDLS_SW_REQ_RCVD: + return "REQ RECEIVED"; + case IWL_MVM_TDLS_SW_ACTIVE: + return "ACTIVE"; + } + + return NULL; +} + +static void iwl_mvm_tdls_update_cs_state(struct iwl_mvm *mvm, + enum iwl_mvm_tdls_cs_state state) +{ + if (mvm->tdls_cs.state == state) + return; + + IWL_DEBUG_TDLS(mvm, "TDLS channel switch state: %s -> %s\n", + iwl_mvm_tdls_cs_state_str(mvm->tdls_cs.state), + iwl_mvm_tdls_cs_state_str(state)); + mvm->tdls_cs.state = state; + + /* we only send requests to our switching peer - update sent time */ + if (state == IWL_MVM_TDLS_SW_REQ_SENT) + mvm->tdls_cs.peer.sent_timestamp = iwl_mvm_get_systime(mvm); + + if (state == IWL_MVM_TDLS_SW_IDLE) + mvm->tdls_cs.cur_sta_id = IWL_MVM_INVALID_STA; +} + +void iwl_mvm_rx_tdls_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_tdls_channel_switch_notif *notif = (void *)pkt->data; + struct ieee80211_sta *sta; + unsigned int delay; + struct iwl_mvm_sta *mvmsta; + struct ieee80211_vif *vif; + u32 sta_id = le32_to_cpu(notif->sta_id); + + lockdep_assert_held(&mvm->mutex); + + /* can fail sometimes */ + if (!le32_to_cpu(notif->status)) { + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_IDLE); + return; + } + + if (WARN_ON(sta_id >= mvm->fw->ucode_capa.num_stations)) + return; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + /* the station may not be here, but if it is, it must be a TDLS peer */ + if (IS_ERR_OR_NULL(sta) || WARN_ON(!sta->tdls)) + return; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + vif = mvmsta->vif; + + /* + * Update state and possibly switch again after this is over (DTIM). + * Also convert TU to msec. + */ + delay = TU_TO_MS(vif->bss_conf.dtim_period * vif->bss_conf.beacon_int); + mod_delayed_work(system_wq, &mvm->tdls_cs.dwork, + msecs_to_jiffies(delay)); + + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_ACTIVE); +} + +static int +iwl_mvm_tdls_check_action(struct iwl_mvm *mvm, + enum iwl_tdls_channel_switch_type type, + const u8 *peer, bool peer_initiator, u32 timestamp) +{ + bool same_peer = false; + int ret = 0; + + /* get the existing peer if it's there */ + if (mvm->tdls_cs.state != IWL_MVM_TDLS_SW_IDLE && + mvm->tdls_cs.cur_sta_id != IWL_MVM_INVALID_STA) { + struct ieee80211_sta *sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvm->tdls_cs.cur_sta_id], + lockdep_is_held(&mvm->mutex)); + if (!IS_ERR_OR_NULL(sta)) + same_peer = ether_addr_equal(peer, sta->addr); + } + + switch (mvm->tdls_cs.state) { + case IWL_MVM_TDLS_SW_IDLE: + /* + * might be spurious packet from the peer after the switch is + * already done + */ + if (type == TDLS_MOVE_CH) + ret = -EINVAL; + break; + case IWL_MVM_TDLS_SW_REQ_SENT: + /* only allow requests from the same peer */ + if (!same_peer) + ret = -EBUSY; + else if (type == TDLS_SEND_CHAN_SW_RESP_AND_MOVE_CH && + !peer_initiator) + /* + * We received a ch-switch request while an outgoing + * one is pending. Allow it if the peer is the link + * initiator. + */ + ret = -EBUSY; + else if (type == TDLS_SEND_CHAN_SW_REQ) + /* wait for idle before sending another request */ + ret = -EBUSY; + else if (timestamp <= mvm->tdls_cs.peer.sent_timestamp) + /* we got a stale response - ignore it */ + ret = -EINVAL; + break; + case IWL_MVM_TDLS_SW_RESP_RCVD: + /* + * we are waiting for the FW to give an "active" notification, + * so ignore requests in the meantime + */ + ret = -EBUSY; + break; + case IWL_MVM_TDLS_SW_REQ_RCVD: + /* as above, allow the link initiator to proceed */ + if (type == TDLS_SEND_CHAN_SW_REQ) { + if (!same_peer) + ret = -EBUSY; + else if (peer_initiator) /* they are the initiator */ + ret = -EBUSY; + } else if (type == TDLS_MOVE_CH) { + ret = -EINVAL; + } + break; + case IWL_MVM_TDLS_SW_ACTIVE: + /* + * the only valid request when active is a request to return + * to the base channel by the current off-channel peer + */ + if (type != TDLS_MOVE_CH || !same_peer) + ret = -EBUSY; + break; + } + + if (ret) + IWL_DEBUG_TDLS(mvm, + "Invalid TDLS action %d state %d peer %pM same_peer %d initiator %d\n", + type, mvm->tdls_cs.state, peer, same_peer, + peer_initiator); + + return ret; +} + +static int +iwl_mvm_tdls_config_channel_switch(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum iwl_tdls_channel_switch_type type, + const u8 *peer, bool peer_initiator, + u8 oper_class, + struct cfg80211_chan_def *chandef, + u32 timestamp, u16 switch_time, + u16 switch_timeout, struct sk_buff *skb, + u32 ch_sw_tm_ie) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + struct ieee80211_tx_info *info; + struct ieee80211_hdr *hdr; + struct iwl_tdls_channel_switch_cmd cmd = {0}; + struct iwl_tdls_channel_switch_cmd_tail *tail = + iwl_mvm_chan_info_cmd_tail(mvm, &cmd.ci); + u16 len = sizeof(cmd) - iwl_mvm_chan_info_padding(mvm); + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_tdls_check_action(mvm, type, peer, peer_initiator, + timestamp); + if (ret) + return ret; + + if (!skb || WARN_ON(skb->len > IWL_TDLS_CH_SW_FRAME_MAX_SIZE)) { + ret = -EINVAL; + goto out; + } + + cmd.switch_type = type; + tail->timing.frame_timestamp = cpu_to_le32(timestamp); + tail->timing.switch_time = cpu_to_le32(switch_time); + tail->timing.switch_timeout = cpu_to_le32(switch_timeout); + + rcu_read_lock(); + sta = ieee80211_find_sta(vif, peer); + if (!sta) { + rcu_read_unlock(); + ret = -ENOENT; + goto out; + } + mvmsta = iwl_mvm_sta_from_mac80211(sta); + cmd.peer_sta_id = cpu_to_le32(mvmsta->sta_id); + + if (!chandef) { + if (mvm->tdls_cs.state == IWL_MVM_TDLS_SW_REQ_SENT && + mvm->tdls_cs.peer.chandef.chan) { + /* actually moving to the channel */ + chandef = &mvm->tdls_cs.peer.chandef; + } else if (mvm->tdls_cs.state == IWL_MVM_TDLS_SW_ACTIVE && + type == TDLS_MOVE_CH) { + /* we need to return to base channel */ + struct ieee80211_chanctx_conf *chanctx = + rcu_dereference(vif->chanctx_conf); + + if (WARN_ON_ONCE(!chanctx)) { + rcu_read_unlock(); + goto out; + } + + chandef = &chanctx->def; + } + } + + if (chandef) + iwl_mvm_set_chan_info_chandef(mvm, &cmd.ci, chandef); + + /* keep quota calculation simple for now - 50% of DTIM for TDLS */ + tail->timing.max_offchan_duration = + cpu_to_le32(TU_TO_US(vif->bss_conf.dtim_period * + vif->bss_conf.beacon_int) / 2); + + /* Switch time is the first element in the switch-timing IE. */ + tail->frame.switch_time_offset = cpu_to_le32(ch_sw_tm_ie + 2); + + info = IEEE80211_SKB_CB(skb); + hdr = (void *)skb->data; + if (info->control.hw_key) { + if (info->control.hw_key->cipher != WLAN_CIPHER_SUITE_CCMP) { + rcu_read_unlock(); + ret = -EINVAL; + goto out; + } + iwl_mvm_set_tx_cmd_ccmp(info, &tail->frame.tx_cmd); + } + + iwl_mvm_set_tx_cmd(mvm, skb, &tail->frame.tx_cmd, info, + mvmsta->sta_id); + + iwl_mvm_set_tx_cmd_rate(mvm, &tail->frame.tx_cmd, info, sta, + hdr->frame_control); + rcu_read_unlock(); + + memcpy(tail->frame.data, skb->data, skb->len); + + ret = iwl_mvm_send_cmd_pdu(mvm, TDLS_CHANNEL_SWITCH_CMD, 0, len, &cmd); + if (ret) { + IWL_ERR(mvm, "Failed to send TDLS_CHANNEL_SWITCH cmd: %d\n", + ret); + goto out; + } + + /* channel switch has started, update state */ + if (type != TDLS_MOVE_CH) { + mvm->tdls_cs.cur_sta_id = mvmsta->sta_id; + iwl_mvm_tdls_update_cs_state(mvm, + type == TDLS_SEND_CHAN_SW_REQ ? + IWL_MVM_TDLS_SW_REQ_SENT : + IWL_MVM_TDLS_SW_REQ_RCVD); + } else { + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_RESP_RCVD); + } + +out: + + /* channel switch failed - we are idle */ + if (ret) + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_IDLE); + + return ret; +} + +void iwl_mvm_tdls_ch_switch_work(struct work_struct *work) +{ + struct iwl_mvm *mvm; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + struct ieee80211_vif *vif; + unsigned int delay; + int ret; + + mvm = container_of(work, struct iwl_mvm, tdls_cs.dwork.work); + mutex_lock(&mvm->mutex); + + /* called after an active channel switch has finished or timed-out */ + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_IDLE); + + /* station might be gone, in that case do nothing */ + if (mvm->tdls_cs.peer.sta_id == IWL_MVM_INVALID_STA) + goto out; + + sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvm->tdls_cs.peer.sta_id], + lockdep_is_held(&mvm->mutex)); + /* the station may not be here, but if it is, it must be a TDLS peer */ + if (!sta || IS_ERR(sta) || WARN_ON(!sta->tdls)) + goto out; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + vif = mvmsta->vif; + ret = iwl_mvm_tdls_config_channel_switch(mvm, vif, + TDLS_SEND_CHAN_SW_REQ, + sta->addr, + mvm->tdls_cs.peer.initiator, + mvm->tdls_cs.peer.op_class, + &mvm->tdls_cs.peer.chandef, + 0, 0, 0, + mvm->tdls_cs.peer.skb, + mvm->tdls_cs.peer.ch_sw_tm_ie); + if (ret) + IWL_ERR(mvm, "Not sending TDLS channel switch: %d\n", ret); + + /* retry after a DTIM if we failed sending now */ + delay = TU_TO_MS(vif->bss_conf.dtim_period * vif->bss_conf.beacon_int); + schedule_delayed_work(&mvm->tdls_cs.dwork, msecs_to_jiffies(delay)); +out: + mutex_unlock(&mvm->mutex); +} + +int +iwl_mvm_tdls_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u8 oper_class, + struct cfg80211_chan_def *chandef, + struct sk_buff *tmpl_skb, u32 ch_sw_tm_ie) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvmsta; + unsigned int delay; + int ret; + + mutex_lock(&mvm->mutex); + + IWL_DEBUG_TDLS(mvm, "TDLS channel switch with %pM ch %d width %d\n", + sta->addr, chandef->chan->center_freq, chandef->width); + + /* we only support a single peer for channel switching */ + if (mvm->tdls_cs.peer.sta_id != IWL_MVM_INVALID_STA) { + IWL_DEBUG_TDLS(mvm, + "Existing peer. Can't start switch with %pM\n", + sta->addr); + ret = -EBUSY; + goto out; + } + + ret = iwl_mvm_tdls_config_channel_switch(mvm, vif, + TDLS_SEND_CHAN_SW_REQ, + sta->addr, sta->tdls_initiator, + oper_class, chandef, 0, 0, 0, + tmpl_skb, ch_sw_tm_ie); + if (ret) + goto out; + + /* + * Mark the peer as "in tdls switch" for this vif. We only allow a + * single such peer per vif. + */ + mvm->tdls_cs.peer.skb = skb_copy(tmpl_skb, GFP_KERNEL); + if (!mvm->tdls_cs.peer.skb) { + ret = -ENOMEM; + goto out; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + mvm->tdls_cs.peer.sta_id = mvmsta->sta_id; + mvm->tdls_cs.peer.chandef = *chandef; + mvm->tdls_cs.peer.initiator = sta->tdls_initiator; + mvm->tdls_cs.peer.op_class = oper_class; + mvm->tdls_cs.peer.ch_sw_tm_ie = ch_sw_tm_ie; + + /* + * Wait for 2 DTIM periods before attempting the next switch. The next + * switch will be made sooner if the current one completes before that. + */ + delay = 2 * TU_TO_MS(vif->bss_conf.dtim_period * + vif->bss_conf.beacon_int); + mod_delayed_work(system_wq, &mvm->tdls_cs.dwork, + msecs_to_jiffies(delay)); + +out: + mutex_unlock(&mvm->mutex); + return ret; +} + +void iwl_mvm_tdls_cancel_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct ieee80211_sta *cur_sta; + bool wait_for_phy = false; + + mutex_lock(&mvm->mutex); + + IWL_DEBUG_TDLS(mvm, "TDLS cancel channel switch with %pM\n", sta->addr); + + /* we only support a single peer for channel switching */ + if (mvm->tdls_cs.peer.sta_id == IWL_MVM_INVALID_STA) { + IWL_DEBUG_TDLS(mvm, "No ch switch peer - %pM\n", sta->addr); + goto out; + } + + cur_sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvm->tdls_cs.peer.sta_id], + lockdep_is_held(&mvm->mutex)); + /* make sure it's the same peer */ + if (cur_sta != sta) + goto out; + + /* + * If we're currently in a switch because of the now canceled peer, + * wait a DTIM here to make sure the phy is back on the base channel. + * We can't otherwise force it. + */ + if (mvm->tdls_cs.cur_sta_id == mvm->tdls_cs.peer.sta_id && + mvm->tdls_cs.state != IWL_MVM_TDLS_SW_IDLE) + wait_for_phy = true; + + mvm->tdls_cs.peer.sta_id = IWL_MVM_INVALID_STA; + dev_kfree_skb(mvm->tdls_cs.peer.skb); + mvm->tdls_cs.peer.skb = NULL; + +out: + mutex_unlock(&mvm->mutex); + + /* make sure the phy is on the base channel */ + if (wait_for_phy) +#if defined(__linux__) + msleep( +#elif defined(__FreeBSD__) + linux_msleep( +#endif + TU_TO_MS(vif->bss_conf.dtim_period * + vif->bss_conf.beacon_int)); + + /* flush the channel switch state */ + flush_delayed_work(&mvm->tdls_cs.dwork); + + IWL_DEBUG_TDLS(mvm, "TDLS ending channel switch with %pM\n", sta->addr); +} + +void +iwl_mvm_tdls_recv_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_tdls_ch_sw_params *params) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + enum iwl_tdls_channel_switch_type type; + unsigned int delay; + const char *action_str = + params->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST ? + "REQ" : "RESP"; + + mutex_lock(&mvm->mutex); + + IWL_DEBUG_TDLS(mvm, + "Received TDLS ch switch action %s from %pM status %d\n", + action_str, params->sta->addr, params->status); + + /* + * we got a non-zero status from a peer we were switching to - move to + * the idle state and retry again later + */ + if (params->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE && + params->status != 0 && + mvm->tdls_cs.state == IWL_MVM_TDLS_SW_REQ_SENT && + mvm->tdls_cs.cur_sta_id != IWL_MVM_INVALID_STA) { + struct ieee80211_sta *cur_sta; + + /* make sure it's the same peer */ + cur_sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvm->tdls_cs.cur_sta_id], + lockdep_is_held(&mvm->mutex)); + if (cur_sta == params->sta) { + iwl_mvm_tdls_update_cs_state(mvm, + IWL_MVM_TDLS_SW_IDLE); + goto retry; + } + } + + type = (params->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST) ? + TDLS_SEND_CHAN_SW_RESP_AND_MOVE_CH : TDLS_MOVE_CH; + + iwl_mvm_tdls_config_channel_switch(mvm, vif, type, params->sta->addr, + params->sta->tdls_initiator, 0, + params->chandef, params->timestamp, + params->switch_time, + params->switch_timeout, + params->tmpl_skb, + params->ch_sw_tm_ie); + +retry: + /* register a timeout in case we don't succeed in switching */ + delay = vif->bss_conf.dtim_period * vif->bss_conf.beacon_int * + 1024 / 1000; + mod_delayed_work(system_wq, &mvm->tdls_cs.dwork, + msecs_to_jiffies(delay)); + mutex_unlock(&mvm->mutex); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/time-event.c b/sys/contrib/dev/iwlwifi/mvm/time-event.c new file mode 100644 index 00000000000..e91f8e889df --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/time-event.c @@ -0,0 +1,1221 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#include +#include + +#include "fw/notif-wait.h" +#include "iwl-trans.h" +#include "fw-api.h" +#include "time-event.h" +#include "mvm.h" +#include "iwl-io.h" +#include "iwl-prph.h" + +/* + * For the high priority TE use a time event type that has similar priority to + * the FW's action scan priority. + */ +#define IWL_MVM_ROC_TE_TYPE_NORMAL TE_P2P_DEVICE_DISCOVERABLE +#define IWL_MVM_ROC_TE_TYPE_MGMT_TX TE_P2P_CLIENT_ASSOC + +void iwl_mvm_te_clear_data(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data) +{ + lockdep_assert_held(&mvm->time_event_lock); + + if (!te_data || !te_data->vif) + return; + + list_del(&te_data->list); + + /* + * the list is only used for AUX ROC events so make sure it is always + * initialized + */ + INIT_LIST_HEAD(&te_data->list); + + te_data->running = false; + te_data->uid = 0; + te_data->id = TE_MAX; + te_data->vif = NULL; +} + +void iwl_mvm_roc_done_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, roc_done_wk); + + /* + * Clear the ROC_RUNNING /ROC_AUX_RUNNING status bit. + * This will cause the TX path to drop offchannel transmissions. + * That would also be done by mac80211, but it is racy, in particular + * in the case that the time event actually completed in the firmware + * (which is handled in iwl_mvm_te_handle_notif). + */ + clear_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); + clear_bit(IWL_MVM_STATUS_ROC_AUX_RUNNING, &mvm->status); + + synchronize_net(); + + /* + * Flush the offchannel queue -- this is called when the time + * event finishes or is canceled, so that frames queued for it + * won't get stuck on the queue and be transmitted in the next + * time event. + */ + + mutex_lock(&mvm->mutex); + if (test_and_clear_bit(IWL_MVM_STATUS_NEED_FLUSH_P2P, &mvm->status)) { + struct iwl_mvm_vif *mvmvif; + + /* + * NB: access to this pointer would be racy, but the flush bit + * can only be set when we had a P2P-Device VIF, and we have a + * flush of this work in iwl_mvm_prepare_mac_removal() so it's + * not really racy. + */ + + if (!WARN_ON(!mvm->p2p_device_vif)) { + mvmvif = iwl_mvm_vif_from_mac80211(mvm->p2p_device_vif); + iwl_mvm_flush_sta(mvm, &mvmvif->bcast_sta, true); + } + } else { + /* do the same in case of hot spot 2.0 */ + iwl_mvm_flush_sta(mvm, &mvm->aux_sta, true); + /* In newer version of this command an aux station is added only + * in cases of dedicated tx queue and need to be removed in end + * of use */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, + ADD_STA, 0) >= 12) + iwl_mvm_rm_aux_sta(mvm); + } + + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_roc_finished(struct iwl_mvm *mvm) +{ + /* + * Of course, our status bit is just as racy as mac80211, so in + * addition, fire off the work struct which will drop all frames + * from the hardware queues that made it through the race. First + * it will of course synchronize the TX path to make sure that + * any *new* TX will be rejected. + */ + schedule_work(&mvm->roc_done_wk); +} + +static void iwl_mvm_csa_noa_start(struct iwl_mvm *mvm) +{ + struct ieee80211_vif *csa_vif; + + rcu_read_lock(); + + csa_vif = rcu_dereference(mvm->csa_vif); + if (!csa_vif || !csa_vif->csa_active) + goto out_unlock; + + IWL_DEBUG_TE(mvm, "CSA NOA started\n"); + + /* + * CSA NoA is started but we still have beacons to + * transmit on the current channel. + * So we just do nothing here and the switch + * will be performed on the last TBTT. + */ + if (!ieee80211_beacon_cntdwn_is_complete(csa_vif)) { + IWL_WARN(mvm, "CSA NOA started too early\n"); + goto out_unlock; + } + + ieee80211_csa_finish(csa_vif); + + rcu_read_unlock(); + + RCU_INIT_POINTER(mvm->csa_vif, NULL); + + return; + +out_unlock: + rcu_read_unlock(); +} + +static bool iwl_mvm_te_check_disconnect(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const char *errmsg) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type != NL80211_IFTYPE_STATION) + return false; + + if (!mvmvif->csa_bcn_pending && vif->bss_conf.assoc && + vif->bss_conf.dtim_period) + return false; + if (errmsg) + IWL_ERR(mvm, "%s\n", errmsg); + + if (mvmvif->csa_bcn_pending) { + struct iwl_mvm_sta *mvmsta; + + rcu_read_lock(); + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, mvmvif->ap_sta_id); + if (!WARN_ON(!mvmsta)) + iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, false); + rcu_read_unlock(); + } + + if (vif->bss_conf.assoc) { + /* + * When not associated, this will be called from + * iwl_mvm_event_mlme_callback_ini() + */ + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_ASSOC_FAILED, + NULL); + } + + iwl_mvm_connection_loss(mvm, vif, errmsg); + return true; +} + +static void +iwl_mvm_te_handle_notify_csa(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data, + struct iwl_time_event_notif *notif) +{ + struct ieee80211_vif *vif = te_data->vif; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (!notif->status) + IWL_DEBUG_TE(mvm, "CSA time event failed to start\n"); + + switch (te_data->vif->type) { + case NL80211_IFTYPE_AP: + if (!notif->status) + mvmvif->csa_failed = true; + iwl_mvm_csa_noa_start(mvm); + break; + case NL80211_IFTYPE_STATION: + if (!notif->status) { + iwl_mvm_connection_loss(mvm, vif, + "CSA TE failed to start"); + break; + } + iwl_mvm_csa_client_absent(mvm, te_data->vif); + cancel_delayed_work(&mvmvif->csa_work); + ieee80211_chswitch_done(te_data->vif, true); + break; + default: + /* should never happen */ + WARN_ON_ONCE(1); + break; + } + + /* we don't need it anymore */ + iwl_mvm_te_clear_data(mvm, te_data); +} + +static void iwl_mvm_te_check_trigger(struct iwl_mvm *mvm, + struct iwl_time_event_notif *notif, + struct iwl_mvm_time_event_data *te_data) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_time_event *te_trig; + int i; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, + ieee80211_vif_to_wdev(te_data->vif), + FW_DBG_TRIGGER_TIME_EVENT); + if (!trig) + return; + + te_trig = (void *)trig->data; + + for (i = 0; i < ARRAY_SIZE(te_trig->time_events); i++) { + u32 trig_te_id = le32_to_cpu(te_trig->time_events[i].id); + u32 trig_action_bitmap = + le32_to_cpu(te_trig->time_events[i].action_bitmap); + u32 trig_status_bitmap = + le32_to_cpu(te_trig->time_events[i].status_bitmap); + + if (trig_te_id != te_data->id || + !(trig_action_bitmap & le32_to_cpu(notif->action)) || + !(trig_status_bitmap & BIT(le32_to_cpu(notif->status)))) + continue; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "Time event %d Action 0x%x received status: %d", + te_data->id, + le32_to_cpu(notif->action), + le32_to_cpu(notif->status)); + break; + } +} + +static void iwl_mvm_p2p_roc_finished(struct iwl_mvm *mvm) +{ + /* + * If the IWL_MVM_STATUS_NEED_FLUSH_P2P is already set, then the + * roc_done_wk is already scheduled or running, so don't schedule it + * again to avoid a race where the roc_done_wk clears this bit after + * it is set here, affecting the next run of the roc_done_wk. + */ + if (!test_and_set_bit(IWL_MVM_STATUS_NEED_FLUSH_P2P, &mvm->status)) + iwl_mvm_roc_finished(mvm); +} + +/* + * Handles a FW notification for an event that is known to the driver. + * + * @mvm: the mvm component + * @te_data: the time event data + * @notif: the notification data corresponding the time event data. + */ +static void iwl_mvm_te_handle_notif(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data, + struct iwl_time_event_notif *notif) +{ + lockdep_assert_held(&mvm->time_event_lock); + + IWL_DEBUG_TE(mvm, "Handle time event notif - UID = 0x%x action %d\n", + le32_to_cpu(notif->unique_id), + le32_to_cpu(notif->action)); + + iwl_mvm_te_check_trigger(mvm, notif, te_data); + + /* + * The FW sends the start/end time event notifications even for events + * that it fails to schedule. This is indicated in the status field of + * the notification. This happens in cases that the scheduler cannot + * find a schedule that can handle the event (for example requesting a + * P2P Device discoveribility, while there are other higher priority + * events in the system). + */ + if (!le32_to_cpu(notif->status)) { + const char *msg; + + if (notif->action & cpu_to_le32(TE_V2_NOTIF_HOST_EVENT_START)) + msg = "Time Event start notification failure"; + else + msg = "Time Event end notification failure"; + + IWL_DEBUG_TE(mvm, "%s\n", msg); + + if (iwl_mvm_te_check_disconnect(mvm, te_data->vif, msg)) { + iwl_mvm_te_clear_data(mvm, te_data); + return; + } + } + + if (le32_to_cpu(notif->action) & TE_V2_NOTIF_HOST_EVENT_END) { + IWL_DEBUG_TE(mvm, + "TE ended - current time %lu, estimated end %lu\n", + jiffies, te_data->end_jiffies); + + switch (te_data->vif->type) { + case NL80211_IFTYPE_P2P_DEVICE: + ieee80211_remain_on_channel_expired(mvm->hw); + iwl_mvm_p2p_roc_finished(mvm); + break; + case NL80211_IFTYPE_STATION: + /* + * If we are switching channel, don't disconnect + * if the time event is already done. Beacons can + * be delayed a bit after the switch. + */ + if (te_data->id == TE_CHANNEL_SWITCH_PERIOD) { + IWL_DEBUG_TE(mvm, + "No beacon heard and the CS time event is over, don't disconnect\n"); + break; + } + + /* + * By now, we should have finished association + * and know the dtim period. + */ + iwl_mvm_te_check_disconnect(mvm, te_data->vif, + !te_data->vif->bss_conf.assoc ? + "Not associated and the time event is over already..." : + "No beacon heard and the time event is over already..."); + break; + default: + break; + } + + iwl_mvm_te_clear_data(mvm, te_data); + } else if (le32_to_cpu(notif->action) & TE_V2_NOTIF_HOST_EVENT_START) { + te_data->running = true; + te_data->end_jiffies = TU_TO_EXP_TIME(te_data->duration); + + if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { + set_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); + ieee80211_ready_on_channel(mvm->hw); + } else if (te_data->id == TE_CHANNEL_SWITCH_PERIOD) { + iwl_mvm_te_handle_notify_csa(mvm, te_data, notif); + } + } else { + IWL_WARN(mvm, "Got TE with unknown action\n"); + } +} + +/* + * Handle A Aux ROC time event + */ +static int iwl_mvm_aux_roc_te_handle_notif(struct iwl_mvm *mvm, + struct iwl_time_event_notif *notif) +{ + struct iwl_mvm_time_event_data *te_data, *tmp; + bool aux_roc_te = false; + + list_for_each_entry_safe(te_data, tmp, &mvm->aux_roc_te_list, list) { + if (le32_to_cpu(notif->unique_id) == te_data->uid) { + aux_roc_te = true; + break; + } + } + if (!aux_roc_te) /* Not a Aux ROC time event */ + return -EINVAL; + + iwl_mvm_te_check_trigger(mvm, notif, te_data); + + IWL_DEBUG_TE(mvm, + "Aux ROC time event notification - UID = 0x%x action %d (error = %d)\n", + le32_to_cpu(notif->unique_id), + le32_to_cpu(notif->action), le32_to_cpu(notif->status)); + + if (!le32_to_cpu(notif->status) || + le32_to_cpu(notif->action) == TE_V2_NOTIF_HOST_EVENT_END) { + /* End TE, notify mac80211 */ + ieee80211_remain_on_channel_expired(mvm->hw); + iwl_mvm_roc_finished(mvm); /* flush aux queue */ + list_del(&te_data->list); /* remove from list */ + te_data->running = false; + te_data->vif = NULL; + te_data->uid = 0; + te_data->id = TE_MAX; + } else if (le32_to_cpu(notif->action) == TE_V2_NOTIF_HOST_EVENT_START) { + set_bit(IWL_MVM_STATUS_ROC_AUX_RUNNING, &mvm->status); + te_data->running = true; + ieee80211_ready_on_channel(mvm->hw); /* Start TE */ + } else { + IWL_DEBUG_TE(mvm, + "ERROR: Unknown Aux ROC Time Event (action = %d)\n", + le32_to_cpu(notif->action)); + return -EINVAL; + } + + return 0; +} + +/* + * The Rx handler for time event notifications + */ +void iwl_mvm_rx_time_event_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_time_event_notif *notif = (void *)pkt->data; + struct iwl_mvm_time_event_data *te_data, *tmp; + + IWL_DEBUG_TE(mvm, "Time event notification - UID = 0x%x action %d\n", + le32_to_cpu(notif->unique_id), + le32_to_cpu(notif->action)); + + spin_lock_bh(&mvm->time_event_lock); + /* This time event is triggered for Aux ROC request */ + if (!iwl_mvm_aux_roc_te_handle_notif(mvm, notif)) + goto unlock; + + list_for_each_entry_safe(te_data, tmp, &mvm->time_event_list, list) { + if (le32_to_cpu(notif->unique_id) == te_data->uid) + iwl_mvm_te_handle_notif(mvm, te_data, notif); + } +unlock: + spin_unlock_bh(&mvm->time_event_lock); +} + +static bool iwl_mvm_te_notif(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_mvm_time_event_data *te_data = data; + struct iwl_time_event_notif *resp; + int resp_len = iwl_rx_packet_payload_len(pkt); + + if (WARN_ON(pkt->hdr.cmd != TIME_EVENT_NOTIFICATION)) + return true; + + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + IWL_ERR(mvm, "Invalid TIME_EVENT_NOTIFICATION response\n"); + return true; + } + + resp = (void *)pkt->data; + + /* te_data->uid is already set in the TIME_EVENT_CMD response */ + if (le32_to_cpu(resp->unique_id) != te_data->uid) + return false; + + IWL_DEBUG_TE(mvm, "TIME_EVENT_NOTIFICATION response - UID = 0x%x\n", + te_data->uid); + if (!resp->status) + IWL_ERR(mvm, + "TIME_EVENT_NOTIFICATION received but not executed\n"); + + return true; +} + +static bool iwl_mvm_time_event_response(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_mvm_time_event_data *te_data = data; + struct iwl_time_event_resp *resp; + int resp_len = iwl_rx_packet_payload_len(pkt); + + if (WARN_ON(pkt->hdr.cmd != TIME_EVENT_CMD)) + return true; + + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + IWL_ERR(mvm, "Invalid TIME_EVENT_CMD response\n"); + return true; + } + + resp = (void *)pkt->data; + + /* we should never get a response to another TIME_EVENT_CMD here */ + if (WARN_ON_ONCE(le32_to_cpu(resp->id) != te_data->id)) + return false; + + te_data->uid = le32_to_cpu(resp->unique_id); + IWL_DEBUG_TE(mvm, "TIME_EVENT_CMD response - UID = 0x%x\n", + te_data->uid); + return true; +} + +static int iwl_mvm_time_event_send_add(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_time_event_data *te_data, + struct iwl_time_event_cmd *te_cmd) +{ + static const u16 time_event_response[] = { TIME_EVENT_CMD }; + struct iwl_notification_wait wait_time_event; + int ret; + + lockdep_assert_held(&mvm->mutex); + + IWL_DEBUG_TE(mvm, "Add new TE, duration %d TU\n", + le32_to_cpu(te_cmd->duration)); + + spin_lock_bh(&mvm->time_event_lock); + if (WARN_ON(te_data->id != TE_MAX)) { + spin_unlock_bh(&mvm->time_event_lock); + return -EIO; + } + te_data->vif = vif; + te_data->duration = le32_to_cpu(te_cmd->duration); + te_data->id = le32_to_cpu(te_cmd->id); + list_add_tail(&te_data->list, &mvm->time_event_list); + spin_unlock_bh(&mvm->time_event_lock); + + /* + * Use a notification wait, which really just processes the + * command response and doesn't wait for anything, in order + * to be able to process the response and get the UID inside + * the RX path. Using CMD_WANT_SKB doesn't work because it + * stores the buffer and then wakes up this thread, by which + * time another notification (that the time event started) + * might already be processed unsuccessfully. + */ + iwl_init_notification_wait(&mvm->notif_wait, &wait_time_event, + time_event_response, + ARRAY_SIZE(time_event_response), + iwl_mvm_time_event_response, te_data); + + ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, 0, + sizeof(*te_cmd), te_cmd); + if (ret) { + IWL_ERR(mvm, "Couldn't send TIME_EVENT_CMD: %d\n", ret); + iwl_remove_notification(&mvm->notif_wait, &wait_time_event); + goto out_clear_te; + } + + /* No need to wait for anything, so just pass 1 (0 isn't valid) */ + ret = iwl_wait_notification(&mvm->notif_wait, &wait_time_event, 1); + /* should never fail */ + WARN_ON_ONCE(ret); + + if (ret) { + out_clear_te: + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + return ret; +} + +void iwl_mvm_protect_session(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration, + u32 max_delay, bool wait_for_notif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + const u16 te_notif_response[] = { TIME_EVENT_NOTIFICATION }; + struct iwl_notification_wait wait_te_notif; + struct iwl_time_event_cmd time_cmd = {}; + + lockdep_assert_held(&mvm->mutex); + + if (te_data->running && + time_after(te_data->end_jiffies, TU_TO_EXP_TIME(min_duration))) { + IWL_DEBUG_TE(mvm, "We have enough time in the current TE: %u\n", + jiffies_to_msecs(te_data->end_jiffies - jiffies)); + return; + } + + if (te_data->running) { + IWL_DEBUG_TE(mvm, "extend 0x%x: only %u ms left\n", + te_data->uid, + jiffies_to_msecs(te_data->end_jiffies - jiffies)); + /* + * we don't have enough time + * cancel the current TE and issue a new one + * Of course it would be better to remove the old one only + * when the new one is added, but we don't care if we are off + * channel for a bit. All we need to do, is not to return + * before we actually begin to be on the channel. + */ + iwl_mvm_stop_session_protection(mvm, vif); + } + + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + time_cmd.id = cpu_to_le32(TE_BSS_STA_AGGRESSIVE_ASSOC); + + time_cmd.apply_time = cpu_to_le32(0); + + time_cmd.max_frags = TE_V2_FRAG_NONE; + time_cmd.max_delay = cpu_to_le32(max_delay); + /* TODO: why do we need to interval = bi if it is not periodic? */ + time_cmd.interval = cpu_to_le32(1); + time_cmd.duration = cpu_to_le32(duration); + time_cmd.repeat = 1; + time_cmd.policy = cpu_to_le16(TE_V2_NOTIF_HOST_EVENT_START | + TE_V2_NOTIF_HOST_EVENT_END | + TE_V2_START_IMMEDIATELY); + + if (!wait_for_notif) { + iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); + return; + } + + /* + * Create notification_wait for the TIME_EVENT_NOTIFICATION to use + * right after we send the time event + */ + iwl_init_notification_wait(&mvm->notif_wait, &wait_te_notif, + te_notif_response, + ARRAY_SIZE(te_notif_response), + iwl_mvm_te_notif, te_data); + + /* If TE was sent OK - wait for the notification that started */ + if (iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd)) { + IWL_ERR(mvm, "Failed to add TE to protect session\n"); + iwl_remove_notification(&mvm->notif_wait, &wait_te_notif); + } else if (iwl_wait_notification(&mvm->notif_wait, &wait_te_notif, + TU_TO_JIFFIES(max_delay))) { + IWL_ERR(mvm, "Failed to protect session until TE\n"); + } +} + +static void iwl_mvm_cancel_session_protection(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + u32 id) +{ + struct iwl_mvm_session_prot_cmd cmd = { + .id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), + .conf_id = cpu_to_le32(id), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(SESSION_PROTECTION_CMD, + MAC_CONF_GROUP, 0), + 0, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(mvm, + "Couldn't send the SESSION_PROTECTION_CMD: %d\n", ret); +} + +static bool __iwl_mvm_remove_time_event(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data, + u32 *uid) +{ + u32 id; + struct iwl_mvm_vif *mvmvif; + enum nl80211_iftype iftype; + + if (!te_data->vif) + return false; + + mvmvif = iwl_mvm_vif_from_mac80211(te_data->vif); + iftype = te_data->vif->type; + + /* + * It is possible that by the time we got to this point the time + * event was already removed. + */ + spin_lock_bh(&mvm->time_event_lock); + + /* Save time event uid before clearing its data */ + *uid = te_data->uid; + id = te_data->id; + + /* + * The clear_data function handles time events that were already removed + */ + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + + /* When session protection is supported, the te_data->id field + * is reused to save session protection's configuration. + */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) { + if (mvmvif && id < SESSION_PROTECT_CONF_MAX_ID) { + /* Session protection is still ongoing. Cancel it */ + iwl_mvm_cancel_session_protection(mvm, mvmvif, id); + if (iftype == NL80211_IFTYPE_P2P_DEVICE) { + iwl_mvm_p2p_roc_finished(mvm); + } + } + return false; + } else { + /* It is possible that by the time we try to remove it, the + * time event has already ended and removed. In such a case + * there is no need to send a removal command. + */ + if (id == TE_MAX) { + IWL_DEBUG_TE(mvm, "TE 0x%x has already ended\n", *uid); + return false; + } + } + + return true; +} + +/* + * Explicit request to remove a aux roc time event. The removal of a time + * event needs to be synchronized with the flow of a time event's end + * notification, which also removes the time event from the op mode + * data structures. + */ +static void iwl_mvm_remove_aux_roc_te(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_time_event_data *te_data) +{ + struct iwl_hs20_roc_req aux_cmd = {}; + u16 len = sizeof(aux_cmd) - iwl_mvm_chan_info_padding(mvm); + + u32 uid; + int ret; + + if (!__iwl_mvm_remove_time_event(mvm, te_data, &uid)) + return; + + aux_cmd.event_unique_id = cpu_to_le32(uid); + aux_cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); + aux_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + IWL_DEBUG_TE(mvm, "Removing BSS AUX ROC TE 0x%x\n", + le32_to_cpu(aux_cmd.event_unique_id)); + ret = iwl_mvm_send_cmd_pdu(mvm, HOT_SPOT_CMD, 0, + len, &aux_cmd); + + if (WARN_ON(ret)) + return; +} + +/* + * Explicit request to remove a time event. The removal of a time event needs to + * be synchronized with the flow of a time event's end notification, which also + * removes the time event from the op mode data structures. + */ +void iwl_mvm_remove_time_event(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_time_event_data *te_data) +{ + struct iwl_time_event_cmd time_cmd = {}; + u32 uid; + int ret; + + if (!__iwl_mvm_remove_time_event(mvm, te_data, &uid)) + return; + + /* When we remove a TE, the UID is to be set in the id field */ + time_cmd.id = cpu_to_le32(uid); + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + + IWL_DEBUG_TE(mvm, "Removing TE 0x%x\n", le32_to_cpu(time_cmd.id)); + ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, 0, + sizeof(time_cmd), &time_cmd); + if (ret) + IWL_ERR(mvm, "Couldn't remove the time event\n"); +} + +void iwl_mvm_stop_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + u32 id; + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + id = te_data->id; + spin_unlock_bh(&mvm->time_event_lock); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) { + if (id != SESSION_PROTECT_CONF_ASSOC) { + IWL_DEBUG_TE(mvm, + "don't remove session protection id=%u\n", + id); + return; + } + } else if (id != TE_BSS_STA_AGGRESSIVE_ASSOC) { + IWL_DEBUG_TE(mvm, + "don't remove TE with id=%u (not session protection)\n", + id); + return; + } + + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); +} + +void iwl_mvm_rx_session_protect_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_session_prot_notif *notif = (void *)pkt->data; + struct ieee80211_vif *vif; + struct iwl_mvm_vif *mvmvif; + + rcu_read_lock(); + vif = iwl_mvm_rcu_dereference_vif_id(mvm, le32_to_cpu(notif->mac_id), + true); + + if (!vif) + goto out_unlock; + + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* The vif is not a P2P_DEVICE, maintain its time_event_data */ + if (vif->type != NL80211_IFTYPE_P2P_DEVICE) { + struct iwl_mvm_time_event_data *te_data = + &mvmvif->time_event_data; + + if (!le32_to_cpu(notif->status)) { + iwl_mvm_te_check_disconnect(mvm, vif, + "Session protection failure"); + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + + if (le32_to_cpu(notif->start)) { + spin_lock_bh(&mvm->time_event_lock); + te_data->running = le32_to_cpu(notif->start); + te_data->end_jiffies = + TU_TO_EXP_TIME(te_data->duration); + spin_unlock_bh(&mvm->time_event_lock); + } else { + /* + * By now, we should have finished association + * and know the dtim period. + */ + iwl_mvm_te_check_disconnect(mvm, vif, + !vif->bss_conf.assoc ? + "Not associated and the session protection is over already..." : + "No beacon heard and the session protection is over already..."); + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + + goto out_unlock; + } + + if (!le32_to_cpu(notif->status) || !le32_to_cpu(notif->start)) { + /* End TE, notify mac80211 */ + mvmvif->time_event_data.id = SESSION_PROTECT_CONF_MAX_ID; + ieee80211_remain_on_channel_expired(mvm->hw); + iwl_mvm_p2p_roc_finished(mvm); + } else if (le32_to_cpu(notif->start)) { + if (WARN_ON(mvmvif->time_event_data.id != + le32_to_cpu(notif->conf_id))) + goto out_unlock; + set_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); + ieee80211_ready_on_channel(mvm->hw); /* Start TE */ + } + + out_unlock: + rcu_read_unlock(); +} + +static int +iwl_mvm_start_p2p_roc_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + int duration, + enum ieee80211_roc_type type) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_session_prot_cmd cmd = { + .id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_ADD), + .duration_tu = cpu_to_le32(MSEC_TO_TU(duration)), + }; + + lockdep_assert_held(&mvm->mutex); + + /* The time_event_data.id field is reused to save session + * protection's configuration. + */ + switch (type) { + case IEEE80211_ROC_TYPE_NORMAL: + mvmvif->time_event_data.id = + SESSION_PROTECT_CONF_P2P_DEVICE_DISCOV; + break; + case IEEE80211_ROC_TYPE_MGMT_TX: + mvmvif->time_event_data.id = + SESSION_PROTECT_CONF_P2P_GO_NEGOTIATION; + break; + default: + WARN_ONCE(1, "Got an invalid ROC type\n"); + return -EINVAL; + } + + cmd.conf_id = cpu_to_le32(mvmvif->time_event_data.id); + return iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(SESSION_PROTECTION_CMD, + MAC_CONF_GROUP, 0), + 0, sizeof(cmd), &cmd); +} + +int iwl_mvm_start_p2p_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + int duration, enum ieee80211_roc_type type) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + struct iwl_time_event_cmd time_cmd = {}; + + lockdep_assert_held(&mvm->mutex); + if (te_data->running) { + IWL_WARN(mvm, "P2P_DEVICE remain on channel already running\n"); + return -EBUSY; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) + return iwl_mvm_start_p2p_roc_session_protection(mvm, vif, + duration, + type); + + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + + switch (type) { + case IEEE80211_ROC_TYPE_NORMAL: + time_cmd.id = cpu_to_le32(IWL_MVM_ROC_TE_TYPE_NORMAL); + break; + case IEEE80211_ROC_TYPE_MGMT_TX: + time_cmd.id = cpu_to_le32(IWL_MVM_ROC_TE_TYPE_MGMT_TX); + break; + default: + WARN_ONCE(1, "Got an invalid ROC type\n"); + return -EINVAL; + } + + time_cmd.apply_time = cpu_to_le32(0); + time_cmd.interval = cpu_to_le32(1); + + /* + * The P2P Device TEs can have lower priority than other events + * that are being scheduled by the driver/fw, and thus it might not be + * scheduled. To improve the chances of it being scheduled, allow them + * to be fragmented, and in addition allow them to be delayed. + */ + time_cmd.max_frags = min(MSEC_TO_TU(duration)/50, TE_V2_FRAG_ENDLESS); + time_cmd.max_delay = cpu_to_le32(MSEC_TO_TU(duration/2)); + time_cmd.duration = cpu_to_le32(MSEC_TO_TU(duration)); + time_cmd.repeat = 1; + time_cmd.policy = cpu_to_le16(TE_V2_NOTIF_HOST_EVENT_START | + TE_V2_NOTIF_HOST_EVENT_END | + TE_V2_START_IMMEDIATELY); + + return iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); +} + +static struct iwl_mvm_time_event_data *iwl_mvm_get_roc_te(struct iwl_mvm *mvm) +{ + struct iwl_mvm_time_event_data *te_data; + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + + /* + * Iterate over the list of time events and find the time event that is + * associated with a P2P_DEVICE interface. + * This assumes that a P2P_DEVICE interface can have only a single time + * event at any given time and this time event coresponds to a ROC + * request + */ + list_for_each_entry(te_data, &mvm->time_event_list, list) { + if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) + goto out; + } + + /* There can only be at most one AUX ROC time event, we just use the + * list to simplify/unify code. Remove it if it exists. + */ + te_data = list_first_entry_or_null(&mvm->aux_roc_te_list, + struct iwl_mvm_time_event_data, + list); +out: + spin_unlock_bh(&mvm->time_event_lock); + return te_data; +} + +void iwl_mvm_cleanup_roc_te(struct iwl_mvm *mvm) +{ + struct iwl_mvm_time_event_data *te_data; + u32 uid; + + te_data = iwl_mvm_get_roc_te(mvm); + if (te_data) + __iwl_mvm_remove_time_event(mvm, te_data, &uid); +} + +void iwl_mvm_stop_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif; + struct iwl_mvm_time_event_data *te_data; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) { + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + iwl_mvm_cancel_session_protection(mvm, mvmvif, + mvmvif->time_event_data.id); + iwl_mvm_p2p_roc_finished(mvm); + } else { + iwl_mvm_remove_aux_roc_te(mvm, mvmvif, + &mvmvif->time_event_data); + iwl_mvm_roc_finished(mvm); + } + + return; + } + + te_data = iwl_mvm_get_roc_te(mvm); + if (!te_data) { + IWL_WARN(mvm, "No remain on channel event\n"); + return; + } + + mvmvif = iwl_mvm_vif_from_mac80211(te_data->vif); + + if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); + iwl_mvm_p2p_roc_finished(mvm); + } else { + iwl_mvm_remove_aux_roc_te(mvm, mvmvif, te_data); + iwl_mvm_roc_finished(mvm); + } +} + +void iwl_mvm_remove_csa_period(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + u32 id; + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + id = te_data->id; + spin_unlock_bh(&mvm->time_event_lock); + + if (id != TE_CHANNEL_SWITCH_PERIOD) + return; + + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); +} + +int iwl_mvm_schedule_csa_period(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 apply_time) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + struct iwl_time_event_cmd time_cmd = {}; + + lockdep_assert_held(&mvm->mutex); + + if (te_data->running) { + u32 id; + + spin_lock_bh(&mvm->time_event_lock); + id = te_data->id; + spin_unlock_bh(&mvm->time_event_lock); + + if (id == TE_CHANNEL_SWITCH_PERIOD) { + IWL_DEBUG_TE(mvm, "CS period is already scheduled\n"); + return -EBUSY; + } + + /* + * Remove the session protection time event to allow the + * channel switch. If we got here, we just heard a beacon so + * the session protection is not needed anymore anyway. + */ + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); + } + + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + time_cmd.id = cpu_to_le32(TE_CHANNEL_SWITCH_PERIOD); + time_cmd.apply_time = cpu_to_le32(apply_time); + time_cmd.max_frags = TE_V2_FRAG_NONE; + time_cmd.duration = cpu_to_le32(duration); + time_cmd.repeat = 1; + time_cmd.interval = cpu_to_le32(1); + time_cmd.policy = cpu_to_le16(TE_V2_NOTIF_HOST_EVENT_START | + TE_V2_ABSENCE); + if (!apply_time) + time_cmd.policy |= cpu_to_le16(TE_V2_START_IMMEDIATELY); + + return iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); +} + +static bool iwl_mvm_session_prot_notif(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_mvm_session_prot_notif *resp; + int resp_len = iwl_rx_packet_payload_len(pkt); + + if (WARN_ON(pkt->hdr.cmd != SESSION_PROTECTION_NOTIF || + pkt->hdr.group_id != MAC_CONF_GROUP)) + return true; + + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + IWL_ERR(mvm, "Invalid SESSION_PROTECTION_NOTIF response\n"); + return true; + } + + resp = (void *)pkt->data; + + if (!resp->status) + IWL_ERR(mvm, + "TIME_EVENT_NOTIFICATION received but not executed\n"); + + return true; +} + +void iwl_mvm_schedule_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration, + bool wait_for_notif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + const u16 notif[] = { iwl_cmd_id(SESSION_PROTECTION_NOTIF, + MAC_CONF_GROUP, 0) }; + struct iwl_notification_wait wait_notif; + struct iwl_mvm_session_prot_cmd cmd = { + .id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_ADD), + .duration_tu = cpu_to_le32(MSEC_TO_TU(duration)), + }; + + /* The time_event_data.id field is reused to save session + * protection's configuration. + */ + mvmvif->time_event_data.id = SESSION_PROTECT_CONF_ASSOC; + cmd.conf_id = cpu_to_le32(mvmvif->time_event_data.id); + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + if (te_data->running && + time_after(te_data->end_jiffies, TU_TO_EXP_TIME(min_duration))) { + IWL_DEBUG_TE(mvm, "We have enough time in the current TE: %u\n", + jiffies_to_msecs(te_data->end_jiffies - jiffies)); + spin_unlock_bh(&mvm->time_event_lock); + + return; + } + + iwl_mvm_te_clear_data(mvm, te_data); + te_data->duration = le32_to_cpu(cmd.duration_tu); + te_data->vif = vif; + spin_unlock_bh(&mvm->time_event_lock); + + IWL_DEBUG_TE(mvm, "Add new session protection, duration %d TU\n", + le32_to_cpu(cmd.duration_tu)); + + if (!wait_for_notif) { + if (iwl_mvm_send_cmd_pdu(mvm, + iwl_cmd_id(SESSION_PROTECTION_CMD, + MAC_CONF_GROUP, 0), + 0, sizeof(cmd), &cmd)) { + IWL_ERR(mvm, + "Couldn't send the SESSION_PROTECTION_CMD\n"); + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + + return; + } + + iwl_init_notification_wait(&mvm->notif_wait, &wait_notif, + notif, ARRAY_SIZE(notif), + iwl_mvm_session_prot_notif, NULL); + + if (iwl_mvm_send_cmd_pdu(mvm, + iwl_cmd_id(SESSION_PROTECTION_CMD, + MAC_CONF_GROUP, 0), + 0, sizeof(cmd), &cmd)) { + IWL_ERR(mvm, + "Couldn't send the SESSION_PROTECTION_CMD\n"); + iwl_remove_notification(&mvm->notif_wait, &wait_notif); + } else if (iwl_wait_notification(&mvm->notif_wait, &wait_notif, + TU_TO_JIFFIES(100))) { + IWL_ERR(mvm, + "Failed to protect session until session protection\n"); + } +} diff --git a/sys/contrib/dev/iwlwifi/mvm/time-event.h b/sys/contrib/dev/iwlwifi/mvm/time-event.h new file mode 100644 index 00000000000..989a5319fb2 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/time-event.h @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2019-2020 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + */ +#ifndef __time_event_h__ +#define __time_event_h__ + +#include "fw-api.h" + +#include "mvm.h" + +/** + * DOC: Time Events - what is it? + * + * Time Events are a fw feature that allows the driver to control the presence + * of the device on the channel. Since the fw supports multiple channels + * concurrently, the fw may choose to jump to another channel at any time. + * In order to make sure that the fw is on a specific channel at a certain time + * and for a certain duration, the driver needs to issue a time event. + * + * The simplest example is for BSS association. The driver issues a time event, + * waits for it to start, and only then tells mac80211 that we can start the + * association. This way, we make sure that the association will be done + * smoothly and won't be interrupted by channel switch decided within the fw. + */ + + /** + * DOC: The flow against the fw + * + * When the driver needs to make sure we are in a certain channel, at a certain + * time and for a certain duration, it sends a Time Event. The flow against the + * fw goes like this: + * 1) Driver sends a TIME_EVENT_CMD to the fw + * 2) Driver gets the response for that command. This response contains the + * Unique ID (UID) of the event. + * 3) The fw sends notification when the event starts. + * + * Of course the API provides various options that allow to cover parameters + * of the flow. + * What is the duration of the event? + * What is the start time of the event? + * Is there an end-time for the event? + * How much can the event be delayed? + * Can the event be split? + * If yes what is the maximal number of chunks? + * etc... + */ + +/** + * DOC: Abstraction to the driver + * + * In order to simplify the use of time events to the rest of the driver, + * we abstract the use of time events. This component provides the functions + * needed by the driver. + */ + +#define IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS 600 +#define IWL_MVM_TE_SESSION_PROTECTION_MIN_TIME_MS 400 + +/** + * iwl_mvm_protect_session - start / extend the session protection. + * @mvm: the mvm component + * @vif: the virtual interface for which the session is issued + * @duration: the duration of the session in TU. + * @min_duration: will start a new session if the current session will end + * in less than min_duration. + * @max_delay: maximum delay before starting the time event (in TU) + * @wait_for_notif: true if it is required that a time event notification be + * waited for (that the time event has been scheduled before returning) + * + * This function can be used to start a session protection which means that the + * fw will stay on the channel for %duration_ms milliseconds. This function + * can block (sleep) until the session starts. This function can also be used + * to extend a currently running session. + * This function is meant to be used for BSS association for example, where we + * want to make sure that the fw stays on the channel during the association. + */ +void iwl_mvm_protect_session(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration, + u32 max_delay, bool wait_for_notif); + +/** + * iwl_mvm_stop_session_protection - cancel the session protection. + * @mvm: the mvm component + * @vif: the virtual interface for which the session is issued + * + * This functions cancels the session protection which is an act of good + * citizenship. If it is not needed any more it should be canceled because + * the other bindings wait for the medium during that time. + * This funtions doesn't sleep. + */ +void iwl_mvm_stop_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); + +/* + * iwl_mvm_rx_time_event_notif - handles %TIME_EVENT_NOTIFICATION. + */ +void iwl_mvm_rx_time_event_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/** + * iwl_mvm_start_p2p_roc - start remain on channel for p2p device functionality + * @mvm: the mvm component + * @vif: the virtual interface for which the roc is requested. It is assumed + * that the vif type is NL80211_IFTYPE_P2P_DEVICE + * @duration: the requested duration in millisecond for the fw to be on the + * channel that is bound to the vif. + * @type: the remain on channel request type + * + * This function can be used to issue a remain on channel session, + * which means that the fw will stay in the channel for the request %duration + * milliseconds. The function is async, meaning that it only issues the ROC + * request but does not wait for it to start. Once the FW is ready to serve the + * ROC request, it will issue a notification to the driver that it is on the + * requested channel. Once the FW completes the ROC request it will issue + * another notification to the driver. + */ +int iwl_mvm_start_p2p_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + int duration, enum ieee80211_roc_type type); + +/** + * iwl_mvm_stop_roc - stop remain on channel functionality + * @mvm: the mvm component + * @vif: the virtual interface for which the roc is stopped + * + * This function can be used to cancel an ongoing ROC session. + * The function is async, it will instruct the FW to stop serving the ROC + * session, but will not wait for the actual stopping of the session. + */ +void iwl_mvm_stop_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif); + +/** + * iwl_mvm_remove_time_event - general function to clean up of time event + * @mvm: the mvm component + * @vif: the vif to which the time event belongs + * @te_data: the time event data that corresponds to that time event + * + * This function can be used to cancel a time event regardless its type. + * It is useful for cleaning up time events running before removing an + * interface. + */ +void iwl_mvm_remove_time_event(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_time_event_data *te_data); + +/** + * iwl_mvm_te_clear_data - remove time event from list + * @mvm: the mvm component + * @te_data: the time event data to remove + * + * This function is mostly internal, it is made available here only + * for firmware restart purposes. + */ +void iwl_mvm_te_clear_data(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data); + +void iwl_mvm_cleanup_roc_te(struct iwl_mvm *mvm); +void iwl_mvm_roc_done_wk(struct work_struct *wk); + +void iwl_mvm_remove_csa_period(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); + +/** + * iwl_mvm_schedule_csa_period - request channel switch absence period + * @mvm: the mvm component + * @vif: the virtual interface for which the channel switch is issued + * @duration: the duration of the NoA in TU. + * @apply_time: NoA start time in GP2. + * + * This function is used to schedule NoA time event and is used to perform + * the channel switch flow. + */ +int iwl_mvm_schedule_csa_period(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 apply_time); + +/** + * iwl_mvm_te_scheduled - check if the fw received the TE cmd + * @te_data: the time event data that corresponds to that time event + * + * This function returns true iff this TE is added to the fw. + */ +static inline bool +iwl_mvm_te_scheduled(struct iwl_mvm_time_event_data *te_data) +{ + if (!te_data) + return false; + + return !!te_data->uid; +} + +/** + * iwl_mvm_schedule_session_protection - schedule a session protection + * @mvm: the mvm component + * @vif: the virtual interface for which the protection issued + * @duration: the duration of the protection + * @wait_for_notif: if true, will block until the start of the protection + */ +void iwl_mvm_schedule_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration, + bool wait_for_notif); + +/** + * iwl_mvm_rx_session_protect_notif - handles %SESSION_PROTECTION_NOTIF + */ +void iwl_mvm_rx_session_protect_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +#endif /* __time_event_h__ */ diff --git a/sys/contrib/dev/iwlwifi/mvm/tt.c b/sys/contrib/dev/iwlwifi/mvm/tt.c new file mode 100644 index 00000000000..a4850233eda --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/tt.c @@ -0,0 +1,905 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2019-2020 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2016 Intel Deutschland GmbH + */ +#ifdef CONFIG_THERMAL +#include +#endif + +#include "mvm.h" + +#define IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT HZ + +void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm) +{ + struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; + u32 duration = tt->params.ct_kill_duration; + + if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) + return; + + IWL_ERR(mvm, "Enter CT Kill\n"); + iwl_mvm_set_hw_ctkill_state(mvm, true); + + if (!iwl_mvm_is_tt_in_fw(mvm)) { + tt->throttle = false; + tt->dynamic_smps = false; + } + + /* Don't schedule an exit work if we're in test mode, since + * the temperature will not change unless we manually set it + * again (or disable testing). + */ + if (!mvm->temperature_test) + schedule_delayed_work(&tt->ct_kill_exit, + round_jiffies_relative(duration * HZ)); +} + +static void iwl_mvm_exit_ctkill(struct iwl_mvm *mvm) +{ + if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) + return; + + IWL_ERR(mvm, "Exit CT Kill\n"); + iwl_mvm_set_hw_ctkill_state(mvm, false); +} + +static void iwl_mvm_tt_temp_changed(struct iwl_mvm *mvm, u32 temp) +{ + /* ignore the notification if we are in test mode */ + if (mvm->temperature_test) + return; + + if (mvm->temperature == temp) + return; + + mvm->temperature = temp; + iwl_mvm_tt_handler(mvm); +} + +static int iwl_mvm_temp_notif_parse(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_dts_measurement_notif_v1 *notif_v1; + int len = iwl_rx_packet_payload_len(pkt); + int temp; + + /* we can use notif_v1 only, because v2 only adds an additional + * parameter, which is not used in this function. + */ + if (WARN_ON_ONCE(len < sizeof(*notif_v1))) { + IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n"); + return -EINVAL; + } + + notif_v1 = (void *)pkt->data; + + temp = le32_to_cpu(notif_v1->temp); + + /* shouldn't be negative, but since it's s32, make sure it isn't */ + if (WARN_ON_ONCE(temp < 0)) + temp = 0; + + IWL_DEBUG_TEMP(mvm, "DTS_MEASUREMENT_NOTIFICATION - %d\n", temp); + + return temp; +} + +static bool iwl_mvm_temp_notif_wait(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + int *temp = data; + int ret; + + ret = iwl_mvm_temp_notif_parse(mvm, pkt); + if (ret < 0) + return true; + + *temp = ret; + + return true; +} + +void iwl_mvm_temp_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_dts_measurement_notif_v2 *notif_v2; + int len = iwl_rx_packet_payload_len(pkt); + int temp; + u32 ths_crossed; + + /* the notification is handled synchronously in ctkill, so skip here */ + if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) + return; + + temp = iwl_mvm_temp_notif_parse(mvm, pkt); + + if (!iwl_mvm_is_tt_in_fw(mvm)) { + if (temp >= 0) + iwl_mvm_tt_temp_changed(mvm, temp); + return; + } + + if (WARN_ON_ONCE(len < sizeof(*notif_v2))) { + IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n"); + return; + } + + notif_v2 = (void *)pkt->data; + ths_crossed = le32_to_cpu(notif_v2->threshold_idx); + + /* 0xFF in ths_crossed means the notification is not related + * to a trip, so we can ignore it here. + */ + if (ths_crossed == 0xFF) + return; + + IWL_DEBUG_TEMP(mvm, "Temp = %d Threshold crossed = %d\n", + temp, ths_crossed); + +#ifdef CONFIG_THERMAL + if (WARN_ON(ths_crossed >= IWL_MAX_DTS_TRIPS)) + return; + + if (mvm->tz_device.tzone) { + struct iwl_mvm_thermal_device *tz_dev = &mvm->tz_device; + + thermal_zone_device_update(tz_dev->tzone, + THERMAL_TRIP_VIOLATED); + } +#endif /* CONFIG_THERMAL */ +} + +void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct ct_kill_notif *notif; + + notif = (struct ct_kill_notif *)pkt->data; + IWL_DEBUG_TEMP(mvm, "CT Kill notification temperature = %d\n", + notif->temperature); + + iwl_mvm_enter_ctkill(mvm); +} + +/* + * send the DTS_MEASUREMENT_TRIGGER command with or without waiting for a + * response. If we get a response then the measurement is stored in 'temp' + */ +static int iwl_mvm_send_temp_cmd(struct iwl_mvm *mvm, bool response, s32 *temp) +{ + struct iwl_host_cmd cmd = {}; + struct iwl_dts_measurement_cmd dts_cmd = { + .flags = cpu_to_le32(DTS_TRIGGER_CMD_FLAGS_TEMP), + }; + struct iwl_ext_dts_measurement_cmd ext_cmd = { + .control_mode = cpu_to_le32(DTS_DIRECT_WITHOUT_MEASURE), + }; + struct iwl_dts_measurement_resp *resp; + void *cmd_ptr; + int ret; + u32 cmd_flags = 0; + u16 len; + + /* Check which command format is used (regular/extended) */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE)) { + len = sizeof(ext_cmd); + cmd_ptr = &ext_cmd; + } else { + len = sizeof(dts_cmd); + cmd_ptr = &dts_cmd; + } + /* The command version where we get a response is zero length */ + if (response) { + cmd_flags = CMD_WANT_SKB; + len = 0; + } + + cmd.id = WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE); + cmd.len[0] = len; + cmd.flags = cmd_flags; + cmd.data[0] = cmd_ptr; + + IWL_DEBUG_TEMP(mvm, + "Sending temperature measurement command - %s response\n", + response ? "with" : "without"); + ret = iwl_mvm_send_cmd(mvm, &cmd); + + if (ret) { + IWL_ERR(mvm, + "Failed to send the temperature measurement command (err=%d)\n", + ret); + return ret; + } + + if (response) { + resp = (void *)cmd.resp_pkt->data; + *temp = le32_to_cpu(resp->temp); + IWL_DEBUG_TEMP(mvm, + "Got temperature measurement response: temp=%d\n", + *temp); + iwl_free_resp(&cmd); + } + + return ret; +} + +int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp) +{ + struct iwl_notification_wait wait_temp_notif; + static u16 temp_notif[] = { WIDE_ID(PHY_OPS_GROUP, + DTS_MEASUREMENT_NOTIF_WIDE) }; + int ret; + u8 cmd_ver; + + /* + * If command version is 1 we send the command and immediately get + * a response. For older versions we send the command and wait for a + * notification (no command TLV for previous versions). + */ + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, PHY_OPS_GROUP, + CMD_DTS_MEASUREMENT_TRIGGER_WIDE, + IWL_FW_CMD_VER_UNKNOWN); + if (cmd_ver == 1) + return iwl_mvm_send_temp_cmd(mvm, true, temp); + + lockdep_assert_held(&mvm->mutex); + + iwl_init_notification_wait(&mvm->notif_wait, &wait_temp_notif, + temp_notif, ARRAY_SIZE(temp_notif), + iwl_mvm_temp_notif_wait, temp); + + ret = iwl_mvm_send_temp_cmd(mvm, false, temp); + if (ret) { + iwl_remove_notification(&mvm->notif_wait, &wait_temp_notif); + return ret; + } + + ret = iwl_wait_notification(&mvm->notif_wait, &wait_temp_notif, + IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT); + if (ret) + IWL_WARN(mvm, "Getting the temperature timed out\n"); + + return ret; +} + +static void check_exit_ctkill(struct work_struct *work) +{ + struct iwl_mvm_tt_mgmt *tt; + struct iwl_mvm *mvm; + u32 duration; + s32 temp; + int ret; + + tt = container_of(work, struct iwl_mvm_tt_mgmt, ct_kill_exit.work); + mvm = container_of(tt, struct iwl_mvm, thermal_throttle); + + if (iwl_mvm_is_tt_in_fw(mvm)) { + iwl_mvm_exit_ctkill(mvm); + + return; + } + + duration = tt->params.ct_kill_duration; + + flush_work(&mvm->roc_done_wk); + + mutex_lock(&mvm->mutex); + + if (__iwl_mvm_mac_start(mvm)) + goto reschedule; + + ret = iwl_mvm_get_temp(mvm, &temp); + + __iwl_mvm_mac_stop(mvm); + + if (ret) + goto reschedule; + + IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", temp); + + if (temp <= tt->params.ct_kill_exit) { + mutex_unlock(&mvm->mutex); + iwl_mvm_exit_ctkill(mvm); + return; + } + +reschedule: + mutex_unlock(&mvm->mutex); + schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit, + round_jiffies(duration * HZ)); +} + +static void iwl_mvm_tt_smps_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = _data; + enum ieee80211_smps_mode smps_mode; + + lockdep_assert_held(&mvm->mutex); + + if (mvm->thermal_throttle.dynamic_smps) + smps_mode = IEEE80211_SMPS_DYNAMIC; + else + smps_mode = IEEE80211_SMPS_AUTOMATIC; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, smps_mode); +} + +static void iwl_mvm_tt_tx_protection(struct iwl_mvm *mvm, bool enable) +{ + struct iwl_mvm_sta *mvmsta; + int i, err; + + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + mvmsta = iwl_mvm_sta_from_staid_protected(mvm, i); + if (!mvmsta) + continue; + + if (enable == mvmsta->tt_tx_protection) + continue; + err = iwl_mvm_tx_protection(mvm, mvmsta, enable); + if (err) { + IWL_ERR(mvm, "Failed to %s Tx protection\n", + enable ? "enable" : "disable"); + } else { + IWL_DEBUG_TEMP(mvm, "%s Tx protection\n", + enable ? "Enable" : "Disable"); + mvmsta->tt_tx_protection = enable; + } + } +} + +void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff) +{ + struct iwl_host_cmd cmd = { + .id = REPLY_THERMAL_MNG_BACKOFF, + .len = { sizeof(u32), }, + .data = { &backoff, }, + }; + + backoff = max(backoff, mvm->thermal_throttle.min_backoff); + + if (iwl_mvm_send_cmd(mvm, &cmd) == 0) { + IWL_DEBUG_TEMP(mvm, "Set Thermal Tx backoff to: %u\n", + backoff); + mvm->thermal_throttle.tx_backoff = backoff; + } else { + IWL_ERR(mvm, "Failed to change Thermal Tx backoff\n"); + } +} + +void iwl_mvm_tt_handler(struct iwl_mvm *mvm) +{ + struct iwl_tt_params *params = &mvm->thermal_throttle.params; + struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; + s32 temperature = mvm->temperature; + bool throttle_enable = false; + int i; + u32 tx_backoff; + + IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", mvm->temperature); + + if (params->support_ct_kill && temperature >= params->ct_kill_entry) { + iwl_mvm_enter_ctkill(mvm); + return; + } + + if (params->support_ct_kill && + temperature <= params->ct_kill_exit) { + iwl_mvm_exit_ctkill(mvm); + return; + } + + if (params->support_dynamic_smps) { + if (!tt->dynamic_smps && + temperature >= params->dynamic_smps_entry) { + IWL_DEBUG_TEMP(mvm, "Enable dynamic SMPS\n"); + tt->dynamic_smps = true; + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_tt_smps_iterator, mvm); + throttle_enable = true; + } else if (tt->dynamic_smps && + temperature <= params->dynamic_smps_exit) { + IWL_DEBUG_TEMP(mvm, "Disable dynamic SMPS\n"); + tt->dynamic_smps = false; + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_tt_smps_iterator, mvm); + } + } + + if (params->support_tx_protection) { + if (temperature >= params->tx_protection_entry) { + iwl_mvm_tt_tx_protection(mvm, true); + throttle_enable = true; + } else if (temperature <= params->tx_protection_exit) { + iwl_mvm_tt_tx_protection(mvm, false); + } + } + + if (params->support_tx_backoff) { + tx_backoff = tt->min_backoff; + for (i = 0; i < TT_TX_BACKOFF_SIZE; i++) { + if (temperature < params->tx_backoff[i].temperature) + break; + tx_backoff = max(tt->min_backoff, + params->tx_backoff[i].backoff); + } + if (tx_backoff != tt->min_backoff) + throttle_enable = true; + if (tt->tx_backoff != tx_backoff) + iwl_mvm_tt_tx_backoff(mvm, tx_backoff); + } + + if (!tt->throttle && throttle_enable) { + IWL_WARN(mvm, + "Due to high temperature thermal throttling initiated\n"); + tt->throttle = true; + } else if (tt->throttle && !tt->dynamic_smps && + tt->tx_backoff == tt->min_backoff && + temperature <= params->tx_protection_exit) { + IWL_WARN(mvm, + "Temperature is back to normal thermal throttling stopped\n"); + tt->throttle = false; + } +} + +static const struct iwl_tt_params iwl_mvm_default_tt_params = { + .ct_kill_entry = 118, + .ct_kill_exit = 96, + .ct_kill_duration = 5, + .dynamic_smps_entry = 114, + .dynamic_smps_exit = 110, + .tx_protection_entry = 114, + .tx_protection_exit = 108, + .tx_backoff = { + {.temperature = 112, .backoff = 200}, + {.temperature = 113, .backoff = 600}, + {.temperature = 114, .backoff = 1200}, + {.temperature = 115, .backoff = 2000}, + {.temperature = 116, .backoff = 4000}, + {.temperature = 117, .backoff = 10000}, + }, + .support_ct_kill = true, + .support_dynamic_smps = true, + .support_tx_protection = true, + .support_tx_backoff = true, +}; + +/* budget in mWatt */ +static const u32 iwl_mvm_cdev_budgets[] = { + 2400, /* cooling state 0 */ + 2000, /* cooling state 1 */ + 1800, /* cooling state 2 */ + 1600, /* cooling state 3 */ + 1400, /* cooling state 4 */ + 1200, /* cooling state 5 */ + 1000, /* cooling state 6 */ + 900, /* cooling state 7 */ + 800, /* cooling state 8 */ + 700, /* cooling state 9 */ + 650, /* cooling state 10 */ + 600, /* cooling state 11 */ + 550, /* cooling state 12 */ + 500, /* cooling state 13 */ + 450, /* cooling state 14 */ + 400, /* cooling state 15 */ + 350, /* cooling state 16 */ + 300, /* cooling state 17 */ + 250, /* cooling state 18 */ + 200, /* cooling state 19 */ + 150, /* cooling state 20 */ +}; + +int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 state) +{ + struct iwl_mvm_ctdp_cmd cmd = { + .operation = cpu_to_le32(op), + .budget = cpu_to_le32(iwl_mvm_cdev_budgets[state]), + .window_size = 0, + }; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + status = 0; + ret = iwl_mvm_send_cmd_pdu_status(mvm, WIDE_ID(PHY_OPS_GROUP, + CTDP_CONFIG_CMD), + sizeof(cmd), &cmd, &status); + + if (ret) { + IWL_ERR(mvm, "cTDP command failed (err=%d)\n", ret); + return ret; + } + + switch (op) { + case CTDP_CMD_OPERATION_START: +#ifdef CONFIG_THERMAL + mvm->cooling_dev.cur_state = state; +#endif /* CONFIG_THERMAL */ + break; + case CTDP_CMD_OPERATION_REPORT: + IWL_DEBUG_TEMP(mvm, "cTDP avg energy in mWatt = %d\n", status); + /* when the function is called with CTDP_CMD_OPERATION_REPORT + * option the function should return the average budget value + * that is received from the FW. + * The budget can't be less or equal to 0, so it's possible + * to distinguish between error values and budgets. + */ + return status; + case CTDP_CMD_OPERATION_STOP: + IWL_DEBUG_TEMP(mvm, "cTDP stopped successfully\n"); + break; + } + + return 0; +} + +#ifdef CONFIG_THERMAL +static int compare_temps(const void *a, const void *b) +{ + return ((s16)le16_to_cpu(*(__le16 *)a) - + (s16)le16_to_cpu(*(__le16 *)b)); +} +#endif + +int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm) +{ + struct temp_report_ths_cmd cmd = {0}; + int ret; +#ifdef CONFIG_THERMAL + int i, j, idx = 0; + + lockdep_assert_held(&mvm->mutex); + + if (!mvm->tz_device.tzone) + goto send; + + /* The driver holds array of temperature trips that are unsorted + * and uncompressed, the FW should get it compressed and sorted + */ + + /* compress temp_trips to cmd array, remove uninitialized values*/ + for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) { + if (mvm->tz_device.temp_trips[i] != S16_MIN) { + cmd.thresholds[idx++] = + cpu_to_le16(mvm->tz_device.temp_trips[i]); + } + } + cmd.num_temps = cpu_to_le32(idx); + + if (!idx) + goto send; + + /*sort cmd array*/ + sort(cmd.thresholds, idx, sizeof(s16), compare_temps, NULL); + + /* we should save the indexes of trips because we sort + * and compress the orginal array + */ + for (i = 0; i < idx; i++) { + for (j = 0; j < IWL_MAX_DTS_TRIPS; j++) { + if (le16_to_cpu(cmd.thresholds[i]) == + mvm->tz_device.temp_trips[j]) + mvm->tz_device.fw_trips_index[i] = j; + } + } + +send: +#endif + ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP, + TEMP_REPORTING_THRESHOLDS_CMD), + 0, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(mvm, "TEMP_REPORT_THS_CMD command failed (err=%d)\n", + ret); + + return ret; +} + +#ifdef CONFIG_THERMAL +static int iwl_mvm_tzone_get_temp(struct thermal_zone_device *device, + int *temperature) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata; + int ret; + int temp; + + mutex_lock(&mvm->mutex); + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) { + ret = -ENODATA; + goto out; + } + + ret = iwl_mvm_get_temp(mvm, &temp); + if (ret) + goto out; + + *temperature = temp * 1000; + +out: + mutex_unlock(&mvm->mutex); + return ret; +} + +static int iwl_mvm_tzone_get_trip_temp(struct thermal_zone_device *device, + int trip, int *temp) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata; + + if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) + return -EINVAL; + + *temp = mvm->tz_device.temp_trips[trip] * 1000; + + return 0; +} + +static int iwl_mvm_tzone_get_trip_type(struct thermal_zone_device *device, + int trip, enum thermal_trip_type *type) +{ + if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) + return -EINVAL; + + *type = THERMAL_TRIP_PASSIVE; + + return 0; +} + +static int iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device *device, + int trip, int temp) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata; + struct iwl_mvm_thermal_device *tzone; + int i, ret; + s16 temperature; + + mutex_lock(&mvm->mutex); + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) { + ret = -EIO; + goto out; + } + + if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) { + ret = -EINVAL; + goto out; + } + + if ((temp / 1000) > S16_MAX) { + ret = -EINVAL; + goto out; + } + + temperature = (s16)(temp / 1000); + tzone = &mvm->tz_device; + + if (!tzone) { + ret = -EIO; + goto out; + } + + /* no updates*/ + if (tzone->temp_trips[trip] == temperature) { + ret = 0; + goto out; + } + + /* already existing temperature */ + for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) { + if (tzone->temp_trips[i] == temperature) { + ret = -EINVAL; + goto out; + } + } + + tzone->temp_trips[trip] = temperature; + + ret = iwl_mvm_send_temp_report_ths_cmd(mvm); +out: + mutex_unlock(&mvm->mutex); + return ret; +} + +static struct thermal_zone_device_ops tzone_ops = { + .get_temp = iwl_mvm_tzone_get_temp, + .get_trip_temp = iwl_mvm_tzone_get_trip_temp, + .get_trip_type = iwl_mvm_tzone_get_trip_type, + .set_trip_temp = iwl_mvm_tzone_set_trip_temp, +}; + +/* make all trips writable */ +#define IWL_WRITABLE_TRIPS_MSK (BIT(IWL_MAX_DTS_TRIPS) - 1) + +static void iwl_mvm_thermal_zone_register(struct iwl_mvm *mvm) +{ + int i, ret; + char name[16]; + static atomic_t counter = ATOMIC_INIT(0); + + if (!iwl_mvm_is_tt_in_fw(mvm)) { + mvm->tz_device.tzone = NULL; + + return; + } + + BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); + + sprintf(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF); + mvm->tz_device.tzone = thermal_zone_device_register(name, + IWL_MAX_DTS_TRIPS, + IWL_WRITABLE_TRIPS_MSK, + mvm, &tzone_ops, + NULL, 0, 0); + if (IS_ERR(mvm->tz_device.tzone)) { + IWL_DEBUG_TEMP(mvm, + "Failed to register to thermal zone (err = %ld)\n", + PTR_ERR(mvm->tz_device.tzone)); + mvm->tz_device.tzone = NULL; + return; + } + + ret = thermal_zone_device_enable(mvm->tz_device.tzone); + if (ret) { + IWL_DEBUG_TEMP(mvm, "Failed to enable thermal zone\n"); + thermal_zone_device_unregister(mvm->tz_device.tzone); + return; + } + + /* 0 is a valid temperature, + * so initialize the array with S16_MIN which invalid temperature + */ + for (i = 0 ; i < IWL_MAX_DTS_TRIPS; i++) + mvm->tz_device.temp_trips[i] = S16_MIN; +} + +static int iwl_mvm_tcool_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = ARRAY_SIZE(iwl_mvm_cdev_budgets) - 1; + + return 0; +} + +static int iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata); + + *state = mvm->cooling_dev.cur_state; + + return 0; +} + +static int iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long new_state) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata); + int ret; + + mutex_lock(&mvm->mutex); + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) { + ret = -EIO; + goto unlock; + } + + if (new_state >= ARRAY_SIZE(iwl_mvm_cdev_budgets)) { + ret = -EINVAL; + goto unlock; + } + + ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START, + new_state); + +unlock: + mutex_unlock(&mvm->mutex); + return ret; +} + +static const struct thermal_cooling_device_ops tcooling_ops = { + .get_max_state = iwl_mvm_tcool_get_max_state, + .get_cur_state = iwl_mvm_tcool_get_cur_state, + .set_cur_state = iwl_mvm_tcool_set_cur_state, +}; + +static void iwl_mvm_cooling_device_register(struct iwl_mvm *mvm) +{ + char name[] = "iwlwifi"; + + if (!iwl_mvm_is_ctdp_supported(mvm)) + return; + + BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); + + mvm->cooling_dev.cdev = + thermal_cooling_device_register(name, + mvm, + &tcooling_ops); + + if (IS_ERR(mvm->cooling_dev.cdev)) { + IWL_DEBUG_TEMP(mvm, + "Failed to register to cooling device (err = %ld)\n", + PTR_ERR(mvm->cooling_dev.cdev)); + mvm->cooling_dev.cdev = NULL; + return; + } +} + +static void iwl_mvm_thermal_zone_unregister(struct iwl_mvm *mvm) +{ + if (!iwl_mvm_is_tt_in_fw(mvm) || !mvm->tz_device.tzone) + return; + + IWL_DEBUG_TEMP(mvm, "Thermal zone device unregister\n"); + if (mvm->tz_device.tzone) { + thermal_zone_device_unregister(mvm->tz_device.tzone); + mvm->tz_device.tzone = NULL; + } +} + +static void iwl_mvm_cooling_device_unregister(struct iwl_mvm *mvm) +{ + if (!iwl_mvm_is_ctdp_supported(mvm) || !mvm->cooling_dev.cdev) + return; + + IWL_DEBUG_TEMP(mvm, "Cooling device unregister\n"); + if (mvm->cooling_dev.cdev) { + thermal_cooling_device_unregister(mvm->cooling_dev.cdev); + mvm->cooling_dev.cdev = NULL; + } +} +#endif /* CONFIG_THERMAL */ + +void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff) +{ + struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; + + IWL_DEBUG_TEMP(mvm, "Initialize Thermal Throttling\n"); + + if (mvm->cfg->thermal_params) + tt->params = *mvm->cfg->thermal_params; + else + tt->params = iwl_mvm_default_tt_params; + + tt->throttle = false; + tt->dynamic_smps = false; + tt->min_backoff = min_backoff; + INIT_DELAYED_WORK(&tt->ct_kill_exit, check_exit_ctkill); + +#ifdef CONFIG_THERMAL + iwl_mvm_cooling_device_register(mvm); + iwl_mvm_thermal_zone_register(mvm); +#endif + mvm->init_status |= IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE; +} + +void iwl_mvm_thermal_exit(struct iwl_mvm *mvm) +{ + if (!(mvm->init_status & IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE)) + return; + + cancel_delayed_work_sync(&mvm->thermal_throttle.ct_kill_exit); + IWL_DEBUG_TEMP(mvm, "Exit Thermal Throttling\n"); + +#ifdef CONFIG_THERMAL + iwl_mvm_cooling_device_unregister(mvm); + iwl_mvm_thermal_zone_unregister(mvm); +#endif + mvm->init_status &= ~IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE; +} diff --git a/sys/contrib/dev/iwlwifi/mvm/tx.c b/sys/contrib/dev/iwlwifi/mvm/tx.c new file mode 100644 index 00000000000..1a383a00f55 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/tx.c @@ -0,0 +1,2180 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include +#include +#include +#include + +#include "iwl-trans.h" +#include "iwl-eeprom-parse.h" +#include "mvm.h" +#include "sta.h" + +static void +iwl_mvm_bar_check_trigger(struct iwl_mvm *mvm, const u8 *addr, + u16 tid, u16 ssn) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_ba *ba_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, NULL, FW_DBG_TRIGGER_BA); + if (!trig) + return; + + ba_trig = (void *)trig->data; + + if (!(le16_to_cpu(ba_trig->tx_bar) & BIT(tid))) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "BAR sent to %pM, tid %d, ssn %d", + addr, tid, ssn); +} + +#define OPT_HDR(type, skb, off) \ + (type *)(skb_network_header(skb) + (off)) + +static u16 iwl_mvm_tx_csum(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_hdr *hdr, + struct ieee80211_tx_info *info, + u16 offload_assist) +{ +#if IS_ENABLED(CONFIG_INET) + u16 mh_len = ieee80211_hdrlen(hdr->frame_control); + u8 protocol = 0; + + /* Do not compute checksum if already computed */ + if (skb->ip_summed != CHECKSUM_PARTIAL) + goto out; + + /* We do not expect to be requested to csum stuff we do not support */ + if (WARN_ONCE(!(mvm->hw->netdev_features & IWL_TX_CSUM_NETIF_FLAGS) || + (skb->protocol != htons(ETH_P_IP) && + skb->protocol != htons(ETH_P_IPV6)), + "No support for requested checksum\n")) { + skb_checksum_help(skb); + goto out; + } + + if (skb->protocol == htons(ETH_P_IP)) { + protocol = ip_hdr(skb)->protocol; + } else { +#if IS_ENABLED(CONFIG_IPV6) + struct ipv6hdr *ipv6h = + (struct ipv6hdr *)skb_network_header(skb); + unsigned int off = sizeof(*ipv6h); + + protocol = ipv6h->nexthdr; + while (protocol != NEXTHDR_NONE && ipv6_ext_hdr(protocol)) { + struct ipv6_opt_hdr *hp; + + /* only supported extension headers */ + if (protocol != NEXTHDR_ROUTING && + protocol != NEXTHDR_HOP && + protocol != NEXTHDR_DEST) { + skb_checksum_help(skb); + goto out; + } + + hp = OPT_HDR(struct ipv6_opt_hdr, skb, off); + protocol = hp->nexthdr; + off += ipv6_optlen(hp); + } + /* if we get here - protocol now should be TCP/UDP */ +#endif + } + + if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) { + WARN_ON_ONCE(1); + skb_checksum_help(skb); + goto out; + } + + /* enable L4 csum */ + offload_assist |= BIT(TX_CMD_OFFLD_L4_EN); + + /* + * Set offset to IP header (snap). + * We don't support tunneling so no need to take care of inner header. + * Size is in words. + */ + offload_assist |= (4 << TX_CMD_OFFLD_IP_HDR); + + /* Do IPv4 csum for AMSDU only (no IP csum for Ipv6) */ + if (skb->protocol == htons(ETH_P_IP) && + (offload_assist & BIT(TX_CMD_OFFLD_AMSDU))) { + ip_hdr(skb)->check = 0; + offload_assist |= BIT(TX_CMD_OFFLD_L3_EN); + } + + /* reset UDP/TCP header csum */ + if (protocol == IPPROTO_TCP) + tcp_hdr(skb)->check = 0; + else + udp_hdr(skb)->check = 0; + + /* + * mac header len should include IV, size is in words unless + * the IV is added by the firmware like in WEP. + * In new Tx API, the IV is always added by the firmware. + */ + if (!iwl_mvm_has_new_tx_api(mvm) && info->control.hw_key && + info->control.hw_key->cipher != WLAN_CIPHER_SUITE_WEP40 && + info->control.hw_key->cipher != WLAN_CIPHER_SUITE_WEP104) + mh_len += info->control.hw_key->iv_len; + mh_len /= 2; + offload_assist |= mh_len << TX_CMD_OFFLD_MH_SIZE; + +out: +#endif + return offload_assist; +} + +/* + * Sets most of the Tx cmd's fields + */ +void iwl_mvm_set_tx_cmd(struct iwl_mvm *mvm, struct sk_buff *skb, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, u8 sta_id) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + __le16 fc = hdr->frame_control; + u32 tx_flags = le32_to_cpu(tx_cmd->tx_flags); + u32 len = skb->len + FCS_LEN; + u16 offload_assist = 0; + u8 ac; + + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) || + (ieee80211_is_probe_resp(fc) && + !is_multicast_ether_addr(hdr->addr1))) + tx_flags |= TX_CMD_FLG_ACK; + else + tx_flags &= ~TX_CMD_FLG_ACK; + + if (ieee80211_is_probe_resp(fc)) + tx_flags |= TX_CMD_FLG_TSF; + + if (ieee80211_has_morefrags(fc)) + tx_flags |= TX_CMD_FLG_MORE_FRAG; + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tx_cmd->tid_tspec = qc[0] & 0xf; + tx_flags &= ~TX_CMD_FLG_SEQ_CTL; + if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) + offload_assist |= BIT(TX_CMD_OFFLD_AMSDU); + } else if (ieee80211_is_back_req(fc)) { + struct ieee80211_bar *bar = (void *)skb->data; + u16 control = le16_to_cpu(bar->control); + u16 ssn = le16_to_cpu(bar->start_seq_num); + + tx_flags |= TX_CMD_FLG_ACK | TX_CMD_FLG_BAR; + tx_cmd->tid_tspec = (control & + IEEE80211_BAR_CTRL_TID_INFO_MASK) >> + IEEE80211_BAR_CTRL_TID_INFO_SHIFT; + WARN_ON_ONCE(tx_cmd->tid_tspec >= IWL_MAX_TID_COUNT); + iwl_mvm_bar_check_trigger(mvm, bar->ra, tx_cmd->tid_tspec, + ssn); + } else { + if (ieee80211_is_data(fc)) + tx_cmd->tid_tspec = IWL_TID_NON_QOS; + else + tx_cmd->tid_tspec = IWL_MAX_TID_COUNT; + + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) + tx_flags |= TX_CMD_FLG_SEQ_CTL; + else + tx_flags &= ~TX_CMD_FLG_SEQ_CTL; + } + + /* Default to 0 (BE) when tid_spec is set to IWL_MAX_TID_COUNT */ + if (tx_cmd->tid_tspec < IWL_MAX_TID_COUNT) + ac = tid_to_mac80211_ac[tx_cmd->tid_tspec]; + else + ac = tid_to_mac80211_ac[0]; + + tx_flags |= iwl_mvm_bt_coex_tx_prio(mvm, hdr, info, ac) << + TX_CMD_FLG_BT_PRIO_POS; + + if (ieee80211_is_mgmt(fc)) { + if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_ASSOC); + else if (ieee80211_is_action(fc)) + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_NONE); + else + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_MGMT); + + /* The spec allows Action frames in A-MPDU, we don't support + * it + */ + WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_AMPDU); + } else if (info->control.flags & IEEE80211_TX_CTRL_PORT_CTRL_PROTO) { + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_MGMT); + } else { + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_NONE); + } + + if (ieee80211_is_data(fc) && len > mvm->rts_threshold && + !is_multicast_ether_addr(hdr->addr1)) + tx_flags |= TX_CMD_FLG_PROT_REQUIRE; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT) && + ieee80211_action_contains_tpc(skb)) + tx_flags |= TX_CMD_FLG_WRITE_TX_POWER; + + tx_cmd->tx_flags = cpu_to_le32(tx_flags); + /* Total # bytes to be transmitted - PCIe code will adjust for A-MSDU */ + tx_cmd->len = cpu_to_le16((u16)skb->len); + tx_cmd->life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); + tx_cmd->sta_id = sta_id; + + /* padding is inserted later in transport */ + if (ieee80211_hdrlen(fc) % 4 && + !(offload_assist & BIT(TX_CMD_OFFLD_AMSDU))) + offload_assist |= BIT(TX_CMD_OFFLD_PAD); + + tx_cmd->offload_assist |= + cpu_to_le16(iwl_mvm_tx_csum(mvm, skb, hdr, info, + offload_assist)); +} + +static u32 iwl_mvm_get_tx_ant(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc) +{ + if (info->band == NL80211_BAND_2GHZ && + !iwl_mvm_bt_coex_is_shared_ant_avail(mvm)) + return mvm->cfg->non_shared_ant << RATE_MCS_ANT_POS; + + if (sta && ieee80211_is_data(fc)) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + return BIT(mvmsta->tx_ant) << RATE_MCS_ANT_POS; + } + + return BIT(mvm->mgmt_last_antenna_idx) << RATE_MCS_ANT_POS; +} + +static u32 iwl_mvm_get_tx_rate(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc) +{ + int rate_idx = -1; + u8 rate_plcp; + u32 rate_flags = 0; + bool is_cck; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + /* info->control is only relevant for non HW rate control */ + if (!ieee80211_hw_check(mvm->hw, HAS_RATE_CONTROL)) { + /* HT rate doesn't make sense for a non data frame */ + WARN_ONCE(info->control.rates[0].flags & IEEE80211_TX_RC_MCS && + !ieee80211_is_data(fc), + "Got a HT rate (flags:0x%x/mcs:%d/fc:0x%x/state:%d) for a non data frame\n", + info->control.rates[0].flags, + info->control.rates[0].idx, + le16_to_cpu(fc), mvmsta->sta_state); + + rate_idx = info->control.rates[0].idx; + } + + /* if the rate isn't a well known legacy rate, take the lowest one */ + if (rate_idx < 0 || rate_idx >= IWL_RATE_COUNT_LEGACY) + rate_idx = rate_lowest_index( + &mvm->nvm_data->bands[info->band], sta); + + /* + * For non 2 GHZ band, remap mac80211 rate + * indices into driver indices + */ + if (info->band != NL80211_BAND_2GHZ) + rate_idx += IWL_FIRST_OFDM_RATE; + + /* For 2.4 GHZ band, check that there is no need to remap */ + BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + + /* Get PLCP rate for tx_cmd->rate_n_flags */ + rate_plcp = iwl_mvm_mac80211_idx_to_hwrate(mvm->fw, rate_idx); + is_cck = (rate_idx >= IWL_FIRST_CCK_RATE) && (rate_idx <= IWL_LAST_CCK_RATE); + + /* Set CCK or OFDM flag */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, LONG_GROUP, TX_CMD, 0) > 8) { + if (!is_cck) + rate_flags |= RATE_MCS_LEGACY_OFDM_MSK; + else + rate_flags |= RATE_MCS_CCK_MSK; + } else if (is_cck) { + rate_flags |= RATE_MCS_CCK_MSK_V1; + } + + return (u32)rate_plcp | rate_flags; +} + +static u32 iwl_mvm_get_tx_rate_n_flags(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc) +{ + return iwl_mvm_get_tx_rate(mvm, info, sta, fc) | + iwl_mvm_get_tx_ant(mvm, info, sta, fc); +} + +/* + * Sets the fields in the Tx cmd that are rate related + */ +void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm *mvm, struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc) +{ + /* Set retry limit on RTS packets */ + tx_cmd->rts_retry_limit = IWL_RTS_DFAULT_RETRY_LIMIT; + + /* Set retry limit on DATA packets and Probe Responses*/ + if (ieee80211_is_probe_resp(fc)) { + tx_cmd->data_retry_limit = IWL_MGMT_DFAULT_RETRY_LIMIT; + tx_cmd->rts_retry_limit = + min(tx_cmd->data_retry_limit, tx_cmd->rts_retry_limit); + } else if (ieee80211_is_back_req(fc)) { + tx_cmd->data_retry_limit = IWL_BAR_DFAULT_RETRY_LIMIT; + } else { + tx_cmd->data_retry_limit = IWL_DEFAULT_TX_RETRY; + } + + /* + * for data packets, rate info comes from the table inside the fw. This + * table is controlled by LINK_QUALITY commands + */ + + if (ieee80211_is_data(fc) && sta) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (mvmsta->sta_state >= IEEE80211_STA_AUTHORIZED) { + tx_cmd->initial_rate_index = 0; + tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_STA_RATE); + return; + } + } else if (ieee80211_is_back_req(fc)) { + tx_cmd->tx_flags |= + cpu_to_le32(TX_CMD_FLG_ACK | TX_CMD_FLG_BAR); + } + + /* Set the rate in the TX cmd */ + tx_cmd->rate_n_flags = + cpu_to_le32(iwl_mvm_get_tx_rate_n_flags(mvm, info, sta, fc)); +} + +static inline void iwl_mvm_set_tx_cmd_pn(struct ieee80211_tx_info *info, + u8 *crypto_hdr) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + u64 pn; + + pn = atomic64_inc_return(&keyconf->tx_pn); + crypto_hdr[0] = pn; + crypto_hdr[2] = 0; + crypto_hdr[3] = 0x20 | (keyconf->keyidx << 6); + crypto_hdr[1] = pn >> 8; + crypto_hdr[4] = pn >> 16; + crypto_hdr[5] = pn >> 24; + crypto_hdr[6] = pn >> 32; + crypto_hdr[7] = pn >> 40; +} + +/* + * Sets the fields in the Tx cmd that are crypto related + */ +static void iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct iwl_tx_cmd *tx_cmd, + struct sk_buff *skb_frag, + int hdrlen) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + u8 *crypto_hdr = skb_frag->data + hdrlen; + enum iwl_tx_cmd_sec_ctrl type = TX_CMD_SEC_CCM; + u64 pn; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + iwl_mvm_set_tx_cmd_ccmp(info, tx_cmd); + iwl_mvm_set_tx_cmd_pn(info, crypto_hdr); + break; + + case WLAN_CIPHER_SUITE_TKIP: + tx_cmd->sec_ctl = TX_CMD_SEC_TKIP; + pn = atomic64_inc_return(&keyconf->tx_pn); + ieee80211_tkip_add_iv(crypto_hdr, keyconf, pn); + ieee80211_get_tkip_p2k(keyconf, skb_frag, tx_cmd->key); + break; + + case WLAN_CIPHER_SUITE_WEP104: + tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; + fallthrough; + case WLAN_CIPHER_SUITE_WEP40: + tx_cmd->sec_ctl |= TX_CMD_SEC_WEP | + ((keyconf->keyidx << TX_CMD_SEC_WEP_KEY_IDX_POS) & + TX_CMD_SEC_WEP_KEY_IDX_MSK); + + memcpy(&tx_cmd->key[3], keyconf->key, keyconf->keylen); + break; + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + type = TX_CMD_SEC_GCMP; + fallthrough; + case WLAN_CIPHER_SUITE_CCMP_256: + /* TODO: Taking the key from the table might introduce a race + * when PTK rekeying is done, having an old packets with a PN + * based on the old key but the message encrypted with a new + * one. + * Need to handle this. + */ + tx_cmd->sec_ctl |= type | TX_CMD_SEC_KEY_FROM_TABLE; + tx_cmd->key[0] = keyconf->hw_key_idx; + iwl_mvm_set_tx_cmd_pn(info, crypto_hdr); + break; + default: + tx_cmd->sec_ctl |= TX_CMD_SEC_EXT; + } +} + +/* + * Allocates and sets the Tx cmd the driver data pointers in the skb + */ +static struct iwl_device_tx_cmd * +iwl_mvm_set_tx_params(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, int hdrlen, + struct ieee80211_sta *sta, u8 sta_id) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct iwl_device_tx_cmd *dev_cmd; + struct iwl_tx_cmd *tx_cmd; + + dev_cmd = iwl_trans_alloc_tx_cmd(mvm->trans); + + if (unlikely(!dev_cmd)) + return NULL; + + dev_cmd->hdr.cmd = TX_CMD; + + if (iwl_mvm_has_new_tx_api(mvm)) { + u16 offload_assist = 0; + u32 rate_n_flags = 0; + u16 flags = 0; + struct iwl_mvm_sta *mvmsta = sta ? + iwl_mvm_sta_from_mac80211(sta) : NULL; + + if (ieee80211_is_data_qos(hdr->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + + if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) + offload_assist |= BIT(TX_CMD_OFFLD_AMSDU); + } + + offload_assist = iwl_mvm_tx_csum(mvm, skb, hdr, info, + offload_assist); + + /* padding is inserted later in transport */ + if (ieee80211_hdrlen(hdr->frame_control) % 4 && + !(offload_assist & BIT(TX_CMD_OFFLD_AMSDU))) + offload_assist |= BIT(TX_CMD_OFFLD_PAD); + + if (!info->control.hw_key) + flags |= IWL_TX_FLAGS_ENCRYPT_DIS; + + /* + * For data packets rate info comes from the fw. Only + * set rate/antenna during connection establishment or in case + * no station is given. + */ + if (!sta || !ieee80211_is_data(hdr->frame_control) || + mvmsta->sta_state < IEEE80211_STA_AUTHORIZED) { + flags |= IWL_TX_FLAGS_CMD_RATE; + rate_n_flags = + iwl_mvm_get_tx_rate_n_flags(mvm, info, sta, + hdr->frame_control); + } + + if (mvm->trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210) { + struct iwl_tx_cmd_gen3 *cmd = (void *)dev_cmd->payload; + + cmd->offload_assist |= cpu_to_le32(offload_assist); + + /* Total # bytes to be transmitted */ + cmd->len = cpu_to_le16((u16)skb->len); + + /* Copy MAC header from skb into command buffer */ + memcpy(cmd->hdr, hdr, hdrlen); + + cmd->flags = cpu_to_le16(flags); + cmd->rate_n_flags = cpu_to_le32(rate_n_flags); + } else { + struct iwl_tx_cmd_gen2 *cmd = (void *)dev_cmd->payload; + + cmd->offload_assist |= cpu_to_le16(offload_assist); + + /* Total # bytes to be transmitted */ + cmd->len = cpu_to_le16((u16)skb->len); + + /* Copy MAC header from skb into command buffer */ + memcpy(cmd->hdr, hdr, hdrlen); + + cmd->flags = cpu_to_le32(flags); + cmd->rate_n_flags = cpu_to_le32(rate_n_flags); + } + goto out; + } + + tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + + if (info->control.hw_key) + iwl_mvm_set_tx_cmd_crypto(mvm, info, tx_cmd, skb, hdrlen); + + iwl_mvm_set_tx_cmd(mvm, skb, tx_cmd, info, sta_id); + + iwl_mvm_set_tx_cmd_rate(mvm, tx_cmd, info, sta, hdr->frame_control); + + /* Copy MAC header from skb into command buffer */ + memcpy(tx_cmd->hdr, hdr, hdrlen); + +out: + return dev_cmd; +} + +static void iwl_mvm_skb_prepare_status(struct sk_buff *skb, + struct iwl_device_tx_cmd *cmd) +{ + struct ieee80211_tx_info *skb_info = IEEE80211_SKB_CB(skb); + + memset(&skb_info->status, 0, sizeof(skb_info->status)); + memset(skb_info->driver_data, 0, sizeof(skb_info->driver_data)); + + skb_info->driver_data[1] = cmd; +} + +static int iwl_mvm_get_ctrl_vif_queue(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct ieee80211_hdr *hdr) +{ + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(info->control.vif); + __le16 fc = hdr->frame_control; + + switch (info->control.vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + /* + * Non-bufferable frames use the broadcast station, thus they + * use the probe queue. + * Also take care of the case where we send a deauth to a + * station that we don't have, or similarly an association + * response (with non-success status) for a station we can't + * accept. + * Also, disassociate frames might happen, particular with + * reason 7 ("Class 3 frame received from nonassociated STA"). + */ + if (ieee80211_is_mgmt(fc) && + (!ieee80211_is_bufferable_mmpdu(fc) || + ieee80211_is_deauth(fc) || ieee80211_is_disassoc(fc))) + return mvm->probe_queue; + + if (!ieee80211_has_order(fc) && !ieee80211_is_probe_req(fc) && + is_multicast_ether_addr(hdr->addr1)) + return mvmvif->cab_queue; + + WARN_ONCE(info->control.vif->type != NL80211_IFTYPE_ADHOC, + "fc=0x%02x", le16_to_cpu(fc)); + return mvm->probe_queue; + case NL80211_IFTYPE_P2P_DEVICE: + if (ieee80211_is_mgmt(fc)) + return mvm->p2p_dev_queue; + + WARN_ON_ONCE(1); + return mvm->p2p_dev_queue; + default: + WARN_ONCE(1, "Not a ctrl vif, no available queue\n"); + return -1; + } +} + +static void iwl_mvm_probe_resp_set_noa(struct iwl_mvm *mvm, + struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(info->control.vif); + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + int base_len = (u8 *)mgmt->u.probe_resp.variable - (u8 *)mgmt; + struct iwl_probe_resp_data *resp_data; + const u8 *ie; + u8 *pos; + u8 match[] = { + (WLAN_OUI_WFA >> 16) & 0xff, + (WLAN_OUI_WFA >> 8) & 0xff, + WLAN_OUI_WFA & 0xff, + WLAN_OUI_TYPE_WFA_P2P, + }; + + rcu_read_lock(); + + resp_data = rcu_dereference(mvmvif->probe_resp_data); + if (!resp_data) + goto out; + + if (!resp_data->notif.noa_active) + goto out; + + ie = cfg80211_find_ie_match(WLAN_EID_VENDOR_SPECIFIC, + mgmt->u.probe_resp.variable, + skb->len - base_len, + match, 4, 2); + if (!ie) { + IWL_DEBUG_TX(mvm, "probe resp doesn't have P2P IE\n"); + goto out; + } + + if (skb_tailroom(skb) < resp_data->noa_len) { + if (pskb_expand_head(skb, 0, resp_data->noa_len, GFP_ATOMIC)) { + IWL_ERR(mvm, + "Failed to reallocate probe resp\n"); + goto out; + } + } + + pos = skb_put(skb, resp_data->noa_len); + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + /* Set length of IE body (not including ID and length itself) */ + *pos++ = resp_data->noa_len - 2; + *pos++ = (WLAN_OUI_WFA >> 16) & 0xff; + *pos++ = (WLAN_OUI_WFA >> 8) & 0xff; + *pos++ = WLAN_OUI_WFA & 0xff; + *pos++ = WLAN_OUI_TYPE_WFA_P2P; + + memcpy(pos, &resp_data->notif.noa_attr, + resp_data->noa_len - sizeof(struct ieee80211_vendor_ie)); + +out: + rcu_read_unlock(); +} + +int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info info; + struct iwl_device_tx_cmd *dev_cmd; + u8 sta_id; + int hdrlen = ieee80211_hdrlen(hdr->frame_control); + __le16 fc = hdr->frame_control; + bool offchannel = IEEE80211_SKB_CB(skb)->flags & + IEEE80211_TX_CTL_TX_OFFCHAN; + int queue = -1; + + if (IWL_MVM_NON_TRANSMITTING_AP && ieee80211_is_probe_resp(fc)) + return -1; + + memcpy(&info, skb->cb, sizeof(info)); + + if (WARN_ON_ONCE(skb->len > IEEE80211_MAX_DATA_LEN + hdrlen)) + return -1; + + if (WARN_ON_ONCE(info.flags & IEEE80211_TX_CTL_AMPDU)) + return -1; + + if (info.control.vif) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(info.control.vif); + + if (info.control.vif->type == NL80211_IFTYPE_P2P_DEVICE || + info.control.vif->type == NL80211_IFTYPE_AP || + info.control.vif->type == NL80211_IFTYPE_ADHOC) { + if (!ieee80211_is_data(hdr->frame_control)) + sta_id = mvmvif->bcast_sta.sta_id; + else + sta_id = mvmvif->mcast_sta.sta_id; + + queue = iwl_mvm_get_ctrl_vif_queue(mvm, &info, hdr); + } else if (info.control.vif->type == NL80211_IFTYPE_MONITOR) { + queue = mvm->snif_queue; + sta_id = mvm->snif_sta.sta_id; + } else if (info.control.vif->type == NL80211_IFTYPE_STATION && + offchannel) { + /* + * IWL_MVM_OFFCHANNEL_QUEUE is used for ROC packets + * that can be used in 2 different types of vifs, P2P & + * STATION. + * P2P uses the offchannel queue. + * STATION (HS2.0) uses the auxiliary context of the FW, + * and hence needs to be sent on the aux queue. + */ + sta_id = mvm->aux_sta.sta_id; + queue = mvm->aux_queue; + } + } + + if (queue < 0) { + IWL_ERR(mvm, "No queue was found. Dropping TX\n"); + return -1; + } + + if (unlikely(ieee80211_is_probe_resp(fc))) + iwl_mvm_probe_resp_set_noa(mvm, skb); + + IWL_DEBUG_TX(mvm, "station Id %d, queue=%d\n", sta_id, queue); + + dev_cmd = iwl_mvm_set_tx_params(mvm, skb, &info, hdrlen, NULL, sta_id); + if (!dev_cmd) + return -1; + + /* From now on, we cannot access info->control */ + iwl_mvm_skb_prepare_status(skb, dev_cmd); + + if (iwl_trans_tx(mvm->trans, skb, dev_cmd, queue)) { + iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); + return -1; + } + + return 0; +} + +unsigned int iwl_mvm_max_amsdu_size(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, unsigned int tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + enum nl80211_band band = mvmsta->vif->bss_conf.chandef.chan->band; + u8 ac = tid_to_mac80211_ac[tid]; + unsigned int txf; + int lmac = iwl_mvm_get_lmac_id(mvm->fw, band); + + /* For HE redirect to trigger based fifos */ + if (sta->he_cap.has_he && !WARN_ON(!iwl_mvm_has_new_tx_api(mvm))) + ac += 4; + + txf = iwl_mvm_mac_ac_to_tx_fifo(mvm, ac); + + /* + * Don't send an AMSDU that will be longer than the TXF. + * Add a security margin of 256 for the TX command + headers. + * We also want to have the start of the next packet inside the + * fifo to be able to send bursts. + */ + return min_t(unsigned int, mvmsta->max_amsdu_len, + mvm->fwrt.smem_cfg.lmac[lmac].txfifo_size[txf] - 256); +} + +#ifdef CONFIG_INET + +static int +iwl_mvm_tx_tso_segment(struct sk_buff *skb, unsigned int num_subframes, + netdev_features_t netdev_flags, + struct sk_buff_head *mpdus_skb) +{ + struct sk_buff *tmp, *next; + struct ieee80211_hdr *hdr = (void *)skb->data; + char cb[sizeof(skb->cb)]; + u16 i = 0; + unsigned int tcp_payload_len; + unsigned int mss = skb_shinfo(skb)->gso_size; + bool ipv4 = (skb->protocol == htons(ETH_P_IP)); + bool qos = ieee80211_is_data_qos(hdr->frame_control); + u16 ip_base_id = ipv4 ? ntohs(ip_hdr(skb)->id) : 0; + + skb_shinfo(skb)->gso_size = num_subframes * mss; + memcpy(cb, skb->cb, sizeof(cb)); + + next = skb_gso_segment(skb, netdev_flags); + skb_shinfo(skb)->gso_size = mss; + skb_shinfo(skb)->gso_type = ipv4 ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6; + if (WARN_ON_ONCE(IS_ERR(next))) + return -EINVAL; + else if (next) + consume_skb(skb); + + skb_list_walk_safe(next, tmp, next) { + memcpy(tmp->cb, cb, sizeof(tmp->cb)); + /* + * Compute the length of all the data added for the A-MSDU. + * This will be used to compute the length to write in the TX + * command. We have: SNAP + IP + TCP for n -1 subframes and + * ETH header for n subframes. + */ + tcp_payload_len = skb_tail_pointer(tmp) - + skb_transport_header(tmp) - + tcp_hdrlen(tmp) + tmp->data_len; + + if (ipv4) + ip_hdr(tmp)->id = htons(ip_base_id + i * num_subframes); + + if (tcp_payload_len > mss) { + skb_shinfo(tmp)->gso_size = mss; + skb_shinfo(tmp)->gso_type = ipv4 ? SKB_GSO_TCPV4 : + SKB_GSO_TCPV6; + } else { + if (qos) { + u8 *qc; + + if (ipv4) + ip_send_check(ip_hdr(tmp)); + + qc = ieee80211_get_qos_ctl((void *)tmp->data); + *qc &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT; + } + skb_shinfo(tmp)->gso_size = 0; + } + + skb_mark_not_on_list(tmp); + __skb_queue_tail(mpdus_skb, tmp); + i++; + } + + return 0; +} + +static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff_head *mpdus_skb) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_hdr *hdr = (void *)skb->data; + unsigned int mss = skb_shinfo(skb)->gso_size; + unsigned int num_subframes, tcp_payload_len, subf_len, max_amsdu_len; + u16 snap_ip_tcp, pad; + netdev_features_t netdev_flags = NETIF_F_CSUM_MASK | NETIF_F_SG; + u8 tid; + + snap_ip_tcp = 8 + skb_transport_header(skb) - skb_network_header(skb) + + tcp_hdrlen(skb); + + if (!mvmsta->max_amsdu_len || + !ieee80211_is_data_qos(hdr->frame_control) || + !mvmsta->amsdu_enabled) + return iwl_mvm_tx_tso_segment(skb, 1, netdev_flags, mpdus_skb); + + /* + * Do not build AMSDU for IPv6 with extension headers. + * ask stack to segment and checkum the generated MPDUs for us. + */ + if (skb->protocol == htons(ETH_P_IPV6) && + ((struct ipv6hdr *)skb_network_header(skb))->nexthdr != + IPPROTO_TCP) { + netdev_flags &= ~NETIF_F_CSUM_MASK; + return iwl_mvm_tx_tso_segment(skb, 1, netdev_flags, mpdus_skb); + } + + tid = ieee80211_get_tid(hdr); + if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT)) + return -EINVAL; + + /* + * No need to lock amsdu_in_ampdu_allowed since it can't be modified + * during an BA session. + */ + if ((info->flags & IEEE80211_TX_CTL_AMPDU && + !mvmsta->tid_data[tid].amsdu_in_ampdu_allowed) || + !(mvmsta->amsdu_enabled & BIT(tid))) + return iwl_mvm_tx_tso_segment(skb, 1, netdev_flags, mpdus_skb); + + /* + * Take the min of ieee80211 station and mvm station + */ + max_amsdu_len = + min_t(unsigned int, sta->max_amsdu_len, + iwl_mvm_max_amsdu_size(mvm, sta, tid)); + + /* + * Limit A-MSDU in A-MPDU to 4095 bytes when VHT is not + * supported. This is a spec requirement (IEEE 802.11-2015 + * section 8.7.3 NOTE 3). + */ + if (info->flags & IEEE80211_TX_CTL_AMPDU && + !sta->vht_cap.vht_supported) + max_amsdu_len = min_t(unsigned int, max_amsdu_len, 4095); + + /* Sub frame header + SNAP + IP header + TCP header + MSS */ + subf_len = sizeof(struct ethhdr) + snap_ip_tcp + mss; + pad = (4 - subf_len) & 0x3; + + /* + * If we have N subframes in the A-MSDU, then the A-MSDU's size is + * N * subf_len + (N - 1) * pad. + */ + num_subframes = (max_amsdu_len + pad) / (subf_len + pad); + + if (sta->max_amsdu_subframes && + num_subframes > sta->max_amsdu_subframes) + num_subframes = sta->max_amsdu_subframes; + + tcp_payload_len = skb_tail_pointer(skb) - skb_transport_header(skb) - + tcp_hdrlen(skb) + skb->data_len; + + /* + * Make sure we have enough TBs for the A-MSDU: + * 2 for each subframe + * 1 more for each fragment + * 1 more for the potential data in the header + */ + if ((num_subframes * 2 + skb_shinfo(skb)->nr_frags + 1) > + mvm->trans->max_skb_frags) + num_subframes = 1; + + if (num_subframes > 1) + *ieee80211_get_qos_ctl(hdr) |= IEEE80211_QOS_CTL_A_MSDU_PRESENT; + + /* This skb fits in one single A-MSDU */ + if (num_subframes * mss >= tcp_payload_len) { + __skb_queue_tail(mpdus_skb, skb); + return 0; + } + + /* + * Trick the segmentation function to make it + * create SKBs that can fit into one A-MSDU. + */ + return iwl_mvm_tx_tso_segment(skb, num_subframes, netdev_flags, + mpdus_skb); +} +#else /* CONFIG_INET */ +static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff_head *mpdus_skb) +{ + /* Impossible to get TSO with CONFIG_INET */ + WARN_ON(1); + + return -1; +} +#endif + +/* Check if there are any timed-out TIDs on a given shared TXQ */ +static bool iwl_mvm_txq_should_update(struct iwl_mvm *mvm, int txq_id) +{ + unsigned long queue_tid_bitmap = mvm->queue_info[txq_id].tid_bitmap; + unsigned long now = jiffies; + int tid; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return false; + + for_each_set_bit(tid, &queue_tid_bitmap, IWL_MAX_TID_COUNT + 1) { + if (time_before(mvm->queue_info[txq_id].last_frame_time[tid] + + IWL_MVM_DQA_QUEUE_TIMEOUT, now)) + return true; + } + + return false; +} + +static void iwl_mvm_tx_airtime(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, + int airtime) +{ + int mac = mvmsta->mac_id_n_color & FW_CTXT_ID_MSK; + struct iwl_mvm_tcm_mac *mdata; + + if (mac >= NUM_MAC_INDEX_DRIVER) + return; + + mdata = &mvm->tcm.data[mac]; + + if (mvm->tcm.paused) + return; + + if (time_after(jiffies, mvm->tcm.ts + MVM_TCM_PERIOD)) + schedule_delayed_work(&mvm->tcm.work, 0); + + mdata->tx.airtime += airtime; +} + +static int iwl_mvm_tx_pkt_queued(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, int tid) +{ + u32 ac = tid_to_mac80211_ac[tid]; + int mac = mvmsta->mac_id_n_color & FW_CTXT_ID_MSK; + struct iwl_mvm_tcm_mac *mdata; + + if (mac >= NUM_MAC_INDEX_DRIVER) + return -EINVAL; + + mdata = &mvm->tcm.data[mac]; + + mdata->tx.pkts[ac]++; + + return 0; +} + +/* + * Sets the fields in the Tx cmd that are crypto related. + * + * This function must be called with BHs disabled. + */ +static int iwl_mvm_tx_mpdu(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct iwl_mvm_sta *mvmsta; + struct iwl_device_tx_cmd *dev_cmd; + __le16 fc; + u16 seq_number = 0; + u8 tid = IWL_MAX_TID_COUNT; + u16 txq_id; + bool is_ampdu = false; + int hdrlen; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + fc = hdr->frame_control; + hdrlen = ieee80211_hdrlen(fc); + + if (IWL_MVM_NON_TRANSMITTING_AP && ieee80211_is_probe_resp(fc)) + return -1; + + if (WARN_ON_ONCE(!mvmsta)) + return -1; + + if (WARN_ON_ONCE(mvmsta->sta_id == IWL_MVM_INVALID_STA)) + return -1; + + if (unlikely(ieee80211_is_any_nullfunc(fc)) && sta->he_cap.has_he) + return -1; + + if (unlikely(ieee80211_is_probe_resp(fc))) + iwl_mvm_probe_resp_set_noa(mvm, skb); + + dev_cmd = iwl_mvm_set_tx_params(mvm, skb, info, hdrlen, + sta, mvmsta->sta_id); + if (!dev_cmd) + goto drop; + + /* + * we handle that entirely ourselves -- for uAPSD the firmware + * will always send a notification, and for PS-Poll responses + * we'll notify mac80211 when getting frame status + */ + info->flags &= ~IEEE80211_TX_STATUS_EOSP; + + spin_lock(&mvmsta->lock); + + /* nullfunc frames should go to the MGMT queue regardless of QOS, + * the condition of !ieee80211_is_qos_nullfunc(fc) keeps the default + * assignment of MGMT TID + */ + if (ieee80211_is_data_qos(fc) && !ieee80211_is_qos_nullfunc(fc)) { + tid = ieee80211_get_tid(hdr); + if (WARN_ONCE(tid >= IWL_MAX_TID_COUNT, "Invalid TID %d", tid)) + goto drop_unlock_sta; + + is_ampdu = info->flags & IEEE80211_TX_CTL_AMPDU; + if (WARN_ONCE(is_ampdu && + mvmsta->tid_data[tid].state != IWL_AGG_ON, + "Invalid internal agg state %d for TID %d", + mvmsta->tid_data[tid].state, tid)) + goto drop_unlock_sta; + + seq_number = mvmsta->tid_data[tid].seq_number; + seq_number &= IEEE80211_SCTL_SEQ; + + if (!iwl_mvm_has_new_tx_api(mvm)) { + struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload; + + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(seq_number); + /* update the tx_cmd hdr as it was already copied */ + tx_cmd->hdr->seq_ctrl = hdr->seq_ctrl; + } + } else if (ieee80211_is_data(fc) && !ieee80211_is_data_qos(fc)) { + tid = IWL_TID_NON_QOS; + } + + txq_id = mvmsta->tid_data[tid].txq_id; + + WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM); + + if (WARN_ONCE(txq_id == IWL_MVM_INVALID_QUEUE, "Invalid TXQ id")) { + iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); + spin_unlock(&mvmsta->lock); + return -1; + } + + if (!iwl_mvm_has_new_tx_api(mvm)) { + /* Keep track of the time of the last frame for this RA/TID */ + mvm->queue_info[txq_id].last_frame_time[tid] = jiffies; + + /* + * If we have timed-out TIDs - schedule the worker that will + * reconfig the queues and update them + * + * Note that the no lock is taken here in order to not serialize + * the TX flow. This isn't dangerous because scheduling + * mvm->add_stream_wk can't ruin the state, and if we DON'T + * schedule it due to some race condition then next TX we get + * here we will. + */ + if (unlikely(mvm->queue_info[txq_id].status == + IWL_MVM_QUEUE_SHARED && + iwl_mvm_txq_should_update(mvm, txq_id))) + schedule_work(&mvm->add_stream_wk); + } + + IWL_DEBUG_TX(mvm, "TX to [%d|%d] Q:%d - seq: 0x%x len %d\n", + mvmsta->sta_id, tid, txq_id, + IEEE80211_SEQ_TO_SN(seq_number), skb->len); + + /* From now on, we cannot access info->control */ + iwl_mvm_skb_prepare_status(skb, dev_cmd); + + if (iwl_trans_tx(mvm->trans, skb, dev_cmd, txq_id)) + goto drop_unlock_sta; + + if (tid < IWL_MAX_TID_COUNT && !ieee80211_has_morefrags(fc)) + mvmsta->tid_data[tid].seq_number = seq_number + 0x10; + + spin_unlock(&mvmsta->lock); + + if (iwl_mvm_tx_pkt_queued(mvm, mvmsta, + tid == IWL_MAX_TID_COUNT ? 0 : tid)) + goto drop; + + return 0; + +drop_unlock_sta: + iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); + spin_unlock(&mvmsta->lock); +drop: + IWL_DEBUG_TX(mvm, "TX to [%d|%d] dropped\n", mvmsta->sta_id, tid); + return -1; +} + +int iwl_mvm_tx_skb_sta(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_tx_info info; + struct sk_buff_head mpdus_skbs; + unsigned int payload_len; + int ret; + + if (WARN_ON_ONCE(!mvmsta)) + return -1; + + if (WARN_ON_ONCE(mvmsta->sta_id == IWL_MVM_INVALID_STA)) + return -1; + + memcpy(&info, skb->cb, sizeof(info)); + + if (!skb_is_gso(skb)) + return iwl_mvm_tx_mpdu(mvm, skb, &info, sta); + + payload_len = skb_tail_pointer(skb) - skb_transport_header(skb) - + tcp_hdrlen(skb) + skb->data_len; + + if (payload_len <= skb_shinfo(skb)->gso_size) + return iwl_mvm_tx_mpdu(mvm, skb, &info, sta); + + __skb_queue_head_init(&mpdus_skbs); + + ret = iwl_mvm_tx_tso(mvm, skb, &info, sta, &mpdus_skbs); + if (ret) + return ret; + + if (WARN_ON(skb_queue_empty(&mpdus_skbs))) + return ret; + + while (!skb_queue_empty(&mpdus_skbs)) { + skb = __skb_dequeue(&mpdus_skbs); + + ret = iwl_mvm_tx_mpdu(mvm, skb, &info, sta); + if (ret) { + __skb_queue_purge(&mpdus_skbs); + return ret; + } + } + + return 0; +} + +static void iwl_mvm_check_ratid_empty(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, u8 tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + struct ieee80211_vif *vif = mvmsta->vif; + u16 normalized_ssn; + + lockdep_assert_held(&mvmsta->lock); + + if ((tid_data->state == IWL_AGG_ON || + tid_data->state == IWL_EMPTYING_HW_QUEUE_DELBA) && + iwl_mvm_tid_queued(mvm, tid_data) == 0) { + /* + * Now that this aggregation or DQA queue is empty tell + * mac80211 so it knows we no longer have frames buffered for + * the station on this TID (for the TIM bitmap calculation.) + */ + ieee80211_sta_set_buffered(sta, tid, false); + } + + /* + * In 22000 HW, the next_reclaimed index is only 8 bit, so we'll need + * to align the wrap around of ssn so we compare relevant values. + */ + normalized_ssn = tid_data->ssn; + if (mvm->trans->trans_cfg->gen2) + normalized_ssn &= 0xff; + + if (normalized_ssn != tid_data->next_reclaimed) + return; + + switch (tid_data->state) { + case IWL_EMPTYING_HW_QUEUE_ADDBA: + IWL_DEBUG_TX_QUEUES(mvm, + "Can continue addBA flow ssn = next_recl = %d\n", + tid_data->next_reclaimed); + tid_data->state = IWL_AGG_STARTING; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + + case IWL_EMPTYING_HW_QUEUE_DELBA: + IWL_DEBUG_TX_QUEUES(mvm, + "Can continue DELBA flow ssn = next_recl = %d\n", + tid_data->next_reclaimed); + tid_data->state = IWL_AGG_OFF; + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + + default: + break; + } +} + +#ifdef CONFIG_IWLWIFI_DEBUG +const char *iwl_mvm_get_tx_fail_reason(u32 status) +{ +#define TX_STATUS_FAIL(x) case TX_STATUS_FAIL_ ## x: return #x +#define TX_STATUS_POSTPONE(x) case TX_STATUS_POSTPONE_ ## x: return #x + + switch (status & TX_STATUS_MSK) { + case TX_STATUS_SUCCESS: + return "SUCCESS"; + TX_STATUS_POSTPONE(DELAY); + TX_STATUS_POSTPONE(FEW_BYTES); + TX_STATUS_POSTPONE(BT_PRIO); + TX_STATUS_POSTPONE(QUIET_PERIOD); + TX_STATUS_POSTPONE(CALC_TTAK); + TX_STATUS_FAIL(INTERNAL_CROSSED_RETRY); + TX_STATUS_FAIL(SHORT_LIMIT); + TX_STATUS_FAIL(LONG_LIMIT); + TX_STATUS_FAIL(UNDERRUN); + TX_STATUS_FAIL(DRAIN_FLOW); + TX_STATUS_FAIL(RFKILL_FLUSH); + TX_STATUS_FAIL(LIFE_EXPIRE); + TX_STATUS_FAIL(DEST_PS); + TX_STATUS_FAIL(HOST_ABORTED); + TX_STATUS_FAIL(BT_RETRY); + TX_STATUS_FAIL(STA_INVALID); + TX_STATUS_FAIL(FRAG_DROPPED); + TX_STATUS_FAIL(TID_DISABLE); + TX_STATUS_FAIL(FIFO_FLUSHED); + TX_STATUS_FAIL(SMALL_CF_POLL); + TX_STATUS_FAIL(FW_DROP); + TX_STATUS_FAIL(STA_COLOR_MISMATCH); + } + + return "UNKNOWN"; + +#undef TX_STATUS_FAIL +#undef TX_STATUS_POSTPONE +} +#endif /* CONFIG_IWLWIFI_DEBUG */ + +static int iwl_mvm_get_hwrate_chan_width(u32 chan_width) +{ + switch (chan_width) { + case RATE_MCS_CHAN_WIDTH_20: + return 0; + case RATE_MCS_CHAN_WIDTH_40: + return IEEE80211_TX_RC_40_MHZ_WIDTH; + case RATE_MCS_CHAN_WIDTH_80: + return IEEE80211_TX_RC_80_MHZ_WIDTH; + case RATE_MCS_CHAN_WIDTH_160: + return IEEE80211_TX_RC_160_MHZ_WIDTH; + default: + return 0; + } +} + +void iwl_mvm_hwrate_to_tx_rate(u32 rate_n_flags, + enum nl80211_band band, + struct ieee80211_tx_rate *r) +{ + u32 format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + u32 rate = format == RATE_MCS_HT_MSK ? + RATE_HT_MCS_INDEX(rate_n_flags) : + rate_n_flags & RATE_MCS_CODE_MSK; + + r->flags |= + iwl_mvm_get_hwrate_chan_width(rate_n_flags & + RATE_MCS_CHAN_WIDTH_MSK); + + if (rate_n_flags & RATE_MCS_SGI_MSK) + r->flags |= IEEE80211_TX_RC_SHORT_GI; + if (format == RATE_MCS_HT_MSK) { + r->flags |= IEEE80211_TX_RC_MCS; + r->idx = rate; + } else if (format == RATE_MCS_VHT_MSK) { + ieee80211_rate_set_vht(r, rate, + ((rate_n_flags & RATE_MCS_NSS_MSK) >> + RATE_MCS_NSS_POS) + 1); + r->flags |= IEEE80211_TX_RC_VHT_MCS; + } else if (format == RATE_MCS_HE_MSK) { + /* mac80211 cannot do this without ieee80211_tx_status_ext() + * but it only matters for radiotap */ + r->idx = 0; + } else { + r->idx = iwl_mvm_legacy_hw_idx_to_mac80211_idx(rate_n_flags, + band); + } +} + +void iwl_mvm_hwrate_to_tx_rate_v1(u32 rate_n_flags, + enum nl80211_band band, + struct ieee80211_tx_rate *r) +{ + if (rate_n_flags & RATE_HT_MCS_GF_MSK) + r->flags |= IEEE80211_TX_RC_GREEN_FIELD; + + r->flags |= + iwl_mvm_get_hwrate_chan_width(rate_n_flags & + RATE_MCS_CHAN_WIDTH_MSK_V1); + + if (rate_n_flags & RATE_MCS_SGI_MSK_V1) + r->flags |= IEEE80211_TX_RC_SHORT_GI; + if (rate_n_flags & RATE_MCS_HT_MSK_V1) { + r->flags |= IEEE80211_TX_RC_MCS; + r->idx = rate_n_flags & RATE_HT_MCS_INDEX_MSK_V1; + } else if (rate_n_flags & RATE_MCS_VHT_MSK_V1) { + ieee80211_rate_set_vht( + r, rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK, + ((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS) + 1); + r->flags |= IEEE80211_TX_RC_VHT_MCS; + } else { + r->idx = iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags, + band); + } +} + +/* + * translate ucode response to mac80211 tx status control values + */ +static void iwl_mvm_hwrate_to_tx_status(const struct iwl_fw *fw, + u32 rate_n_flags, + struct ieee80211_tx_info *info) +{ + struct ieee80211_tx_rate *r = &info->status.rates[0]; + + if (iwl_fw_lookup_notif_ver(fw, LONG_GROUP, + TX_CMD, 0) > 6) + rate_n_flags = iwl_new_rate_from_v1(rate_n_flags); + + info->status.antenna = + ((rate_n_flags & RATE_MCS_ANT_AB_MSK) >> RATE_MCS_ANT_POS); + iwl_mvm_hwrate_to_tx_rate(rate_n_flags, + info->band, r); +} + +static void iwl_mvm_tx_status_check_trigger(struct iwl_mvm *mvm, + u32 status, __le16 frame_control) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_tx_status *status_trig; + int i; + + if ((status & TX_STATUS_MSK) != TX_STATUS_SUCCESS) { + enum iwl_fw_ini_time_point tp = + IWL_FW_INI_TIME_POINT_TX_FAILED; + + if (ieee80211_is_action(frame_control)) + tp = IWL_FW_INI_TIME_POINT_TX_WFD_ACTION_FRAME_FAILED; + + iwl_dbg_tlv_time_point(&mvm->fwrt, + tp, NULL); + return; + } + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, NULL, + FW_DBG_TRIGGER_TX_STATUS); + if (!trig) + return; + + status_trig = (void *)trig->data; + + for (i = 0; i < ARRAY_SIZE(status_trig->statuses); i++) { + /* don't collect on status 0 */ + if (!status_trig->statuses[i].status) + break; + + if (status_trig->statuses[i].status != (status & TX_STATUS_MSK)) + continue; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "Tx status %d was received", + status & TX_STATUS_MSK); + break; + } +} + +/* + * iwl_mvm_get_scd_ssn - returns the SSN of the SCD + * @tx_resp: the Tx response from the fw (agg or non-agg) + * + * When the fw sends an AMPDU, it fetches the MPDUs one after the other. Since + * it can't know that everything will go well until the end of the AMPDU, it + * can't know in advance the number of MPDUs that will be sent in the current + * batch. This is why it writes the agg Tx response while it fetches the MPDUs. + * Hence, it can't know in advance what the SSN of the SCD will be at the end + * of the batch. This is why the SSN of the SCD is written at the end of the + * whole struct at a variable offset. This function knows how to cope with the + * variable offset and returns the SSN of the SCD. + */ +static inline u32 iwl_mvm_get_scd_ssn(struct iwl_mvm *mvm, + struct iwl_mvm_tx_resp *tx_resp) +{ + return le32_to_cpup((__le32 *)iwl_mvm_get_agg_status(mvm, tx_resp) + + tx_resp->frame_count) & 0xfff; +} + +static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct ieee80211_sta *sta; + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int txq_id = SEQ_TO_QUEUE(sequence); + /* struct iwl_mvm_tx_resp_v3 is almost the same */ + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + int sta_id = IWL_MVM_TX_RES_GET_RA(tx_resp->ra_tid); + int tid = IWL_MVM_TX_RES_GET_TID(tx_resp->ra_tid); + struct agg_tx_status *agg_status = + iwl_mvm_get_agg_status(mvm, tx_resp); + u32 status = le16_to_cpu(agg_status->status); + u16 ssn = iwl_mvm_get_scd_ssn(mvm, tx_resp); + struct sk_buff_head skbs; + u8 skb_freed = 0; + u8 lq_color; + u16 next_reclaimed, seq_ctl; + bool is_ndp = false; + + __skb_queue_head_init(&skbs); + + if (iwl_mvm_has_new_tx_api(mvm)) + txq_id = le16_to_cpu(tx_resp->tx_queue); + + seq_ctl = le16_to_cpu(tx_resp->seq_ctl); + + /* we can free until ssn % q.n_bd not inclusive */ + iwl_trans_reclaim(mvm->trans, txq_id, ssn, &skbs); + + while (!skb_queue_empty(&skbs)) { + struct sk_buff *skb = __skb_dequeue(&skbs); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (void *)skb->data; + bool flushed = false; + + skb_freed++; + + iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + + memset(&info->status, 0, sizeof(info->status)); + + /* inform mac80211 about what happened with the frame */ + switch (status & TX_STATUS_MSK) { + case TX_STATUS_SUCCESS: + case TX_STATUS_DIRECT_DONE: + info->flags |= IEEE80211_TX_STAT_ACK; + break; + case TX_STATUS_FAIL_FIFO_FLUSHED: + case TX_STATUS_FAIL_DRAIN_FLOW: + flushed = true; + break; + case TX_STATUS_FAIL_DEST_PS: + /* the FW should have stopped the queue and not + * return this status + */ + IWL_ERR_LIMIT(mvm, + "FW reported TX filtered, status=0x%x, FC=0x%x\n", + status, le16_to_cpu(hdr->frame_control)); + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + break; + default: + break; + } + + if ((status & TX_STATUS_MSK) != TX_STATUS_SUCCESS && + ieee80211_is_mgmt(hdr->frame_control)) + iwl_mvm_toggle_tx_ant(mvm, &mvm->mgmt_last_antenna_idx); + + /* + * If we are freeing multiple frames, mark all the frames + * but the first one as acked, since they were acknowledged + * before + * */ + if (skb_freed > 1) + info->flags |= IEEE80211_TX_STAT_ACK; + + iwl_mvm_tx_status_check_trigger(mvm, status, hdr->frame_control); + + info->status.rates[0].count = tx_resp->failure_frame + 1; + + iwl_mvm_hwrate_to_tx_status(mvm->fw, + le32_to_cpu(tx_resp->initial_rate), + info); + + /* Don't assign the converted initial_rate, because driver + * TLC uses this and doesn't support the new FW rate + */ + info->status.status_driver_data[1] = + (void *)(uintptr_t)le32_to_cpu(tx_resp->initial_rate); + + /* Single frame failure in an AMPDU queue => send BAR */ + if (info->flags & IEEE80211_TX_CTL_AMPDU && + !(info->flags & IEEE80211_TX_STAT_ACK) && + !(info->flags & IEEE80211_TX_STAT_TX_FILTERED) && !flushed) + info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + + /* W/A FW bug: seq_ctl is wrong upon failure / BAR frame */ + if (ieee80211_is_back_req(hdr->frame_control)) + seq_ctl = 0; + else if (status != TX_STATUS_SUCCESS) + seq_ctl = le16_to_cpu(hdr->seq_ctrl); + + if (unlikely(!seq_ctl)) { + struct ieee80211_hdr *hdr = (void *)skb->data; + + /* + * If it is an NDP, we can't update next_reclaim since + * its sequence control is 0. Note that for that same + * reason, NDPs are never sent to A-MPDU'able queues + * so that we can never have more than one freed frame + * for a single Tx resonse (see WARN_ON below). + */ + if (ieee80211_is_qos_nullfunc(hdr->frame_control)) + is_ndp = true; + } + + /* + * TODO: this is not accurate if we are freeing more than one + * packet. + */ + info->status.tx_time = + le16_to_cpu(tx_resp->wireless_media_time); + BUILD_BUG_ON(ARRAY_SIZE(info->status.status_driver_data) < 1); + lq_color = TX_RES_RATE_TABLE_COL_GET(tx_resp->tlc_info); + info->status.status_driver_data[0] = + RS_DRV_DATA_PACK(lq_color, tx_resp->reduced_tpc); + + ieee80211_tx_status(mvm->hw, skb); + } + + /* This is an aggregation queue or might become one, so we use + * the ssn since: ssn = wifi seq_num % 256. + * The seq_ctl is the sequence control of the packet to which + * this Tx response relates. But if there is a hole in the + * bitmap of the BA we received, this Tx response may allow to + * reclaim the hole and all the subsequent packets that were + * already acked. In that case, seq_ctl != ssn, and the next + * packet to be reclaimed will be ssn and not seq_ctl. In that + * case, several packets will be reclaimed even if + * frame_count = 1. + * + * The ssn is the index (% 256) of the latest packet that has + * treated (acked / dropped) + 1. + */ + next_reclaimed = ssn; + + IWL_DEBUG_TX_REPLY(mvm, + "TXQ %d status %s (0x%08x)\n", + txq_id, iwl_mvm_get_tx_fail_reason(status), status); + + IWL_DEBUG_TX_REPLY(mvm, + "\t\t\t\tinitial_rate 0x%x retries %d, idx=%d ssn=%d next_reclaimed=0x%x seq_ctl=0x%x\n", + le32_to_cpu(tx_resp->initial_rate), + tx_resp->failure_frame, SEQ_TO_INDEX(sequence), + ssn, next_reclaimed, seq_ctl); + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + /* + * sta can't be NULL otherwise it'd mean that the sta has been freed in + * the firmware while we still have packets for it in the Tx queues. + */ + if (WARN_ON_ONCE(!sta)) + goto out; + + if (!IS_ERR(sta)) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + iwl_mvm_tx_airtime(mvm, mvmsta, + le16_to_cpu(tx_resp->wireless_media_time)); + + if ((status & TX_STATUS_MSK) != TX_STATUS_SUCCESS && + mvmsta->sta_state < IEEE80211_STA_AUTHORIZED) + iwl_mvm_toggle_tx_ant(mvm, &mvmsta->tx_ant); + + if (sta->wme && tid != IWL_MGMT_TID) { + struct iwl_mvm_tid_data *tid_data = + &mvmsta->tid_data[tid]; + bool send_eosp_ndp = false; + + spin_lock_bh(&mvmsta->lock); + + if (!is_ndp) { + tid_data->next_reclaimed = next_reclaimed; + IWL_DEBUG_TX_REPLY(mvm, + "Next reclaimed packet:%d\n", + next_reclaimed); + } else { + IWL_DEBUG_TX_REPLY(mvm, + "NDP - don't update next_reclaimed\n"); + } + + iwl_mvm_check_ratid_empty(mvm, sta, tid); + + if (mvmsta->sleep_tx_count) { + mvmsta->sleep_tx_count--; + if (mvmsta->sleep_tx_count && + !iwl_mvm_tid_queued(mvm, tid_data)) { + /* + * The number of frames in the queue + * dropped to 0 even if we sent less + * frames than we thought we had on the + * Tx queue. + * This means we had holes in the BA + * window that we just filled, ask + * mac80211 to send EOSP since the + * firmware won't know how to do that. + * Send NDP and the firmware will send + * EOSP notification that will trigger + * a call to ieee80211_sta_eosp(). + */ + send_eosp_ndp = true; + } + } + + spin_unlock_bh(&mvmsta->lock); + if (send_eosp_ndp) { + iwl_mvm_sta_modify_sleep_tx_count(mvm, sta, + IEEE80211_FRAME_RELEASE_UAPSD, + 1, tid, false, false); + mvmsta->sleep_tx_count = 0; + ieee80211_send_eosp_nullfunc(sta, tid); + } + } + + if (mvmsta->next_status_eosp) { + mvmsta->next_status_eosp = false; + ieee80211_sta_eosp(sta); + } + } +out: + rcu_read_unlock(); +} + +#ifdef CONFIG_IWLWIFI_DEBUG +#define AGG_TX_STATE_(x) case AGG_TX_STATE_ ## x: return #x +static const char *iwl_get_agg_tx_status(u16 status) +{ + switch (status & AGG_TX_STATE_STATUS_MSK) { + AGG_TX_STATE_(TRANSMITTED); + AGG_TX_STATE_(UNDERRUN); + AGG_TX_STATE_(BT_PRIO); + AGG_TX_STATE_(FEW_BYTES); + AGG_TX_STATE_(ABORT); + AGG_TX_STATE_(TX_ON_AIR_DROP); + AGG_TX_STATE_(LAST_SENT_TRY_CNT); + AGG_TX_STATE_(LAST_SENT_BT_KILL); + AGG_TX_STATE_(SCD_QUERY); + AGG_TX_STATE_(TEST_BAD_CRC32); + AGG_TX_STATE_(RESPONSE); + AGG_TX_STATE_(DUMP_TX); + AGG_TX_STATE_(DELAY_TX); + } + + return "UNKNOWN"; +} + +static void iwl_mvm_rx_tx_cmd_agg_dbg(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + struct agg_tx_status *frame_status = + iwl_mvm_get_agg_status(mvm, tx_resp); + int i; + bool tirgger_timepoint = false; + + for (i = 0; i < tx_resp->frame_count; i++) { + u16 fstatus = le16_to_cpu(frame_status[i].status); + /* In case one frame wasn't transmitted trigger time point */ + tirgger_timepoint |= ((fstatus & AGG_TX_STATE_STATUS_MSK) != + AGG_TX_STATE_TRANSMITTED); + IWL_DEBUG_TX_REPLY(mvm, + "status %s (0x%04x), try-count (%d) seq (0x%x)\n", + iwl_get_agg_tx_status(fstatus), + fstatus & AGG_TX_STATE_STATUS_MSK, + (fstatus & AGG_TX_STATE_TRY_CNT_MSK) >> + AGG_TX_STATE_TRY_CNT_POS, + le16_to_cpu(frame_status[i].sequence)); + } + + if (tirgger_timepoint) + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_TX_FAILED, NULL); + +} +#else +static void iwl_mvm_rx_tx_cmd_agg_dbg(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{} +#endif /* CONFIG_IWLWIFI_DEBUG */ + +static void iwl_mvm_rx_tx_cmd_agg(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + int sta_id = IWL_MVM_TX_RES_GET_RA(tx_resp->ra_tid); + int tid = IWL_MVM_TX_RES_GET_TID(tx_resp->ra_tid); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + struct iwl_mvm_sta *mvmsta; + int queue = SEQ_TO_QUEUE(sequence); + struct ieee80211_sta *sta; + + if (WARN_ON_ONCE(queue < IWL_MVM_DQA_MIN_DATA_QUEUE && + (queue != IWL_MVM_DQA_BSS_CLIENT_QUEUE))) + return; + + iwl_mvm_rx_tx_cmd_agg_dbg(mvm, pkt); + + rcu_read_lock(); + + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, sta_id); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (WARN_ON_ONCE(!sta || !sta->wme)) { + rcu_read_unlock(); + return; + } + + if (!WARN_ON_ONCE(!mvmsta)) { + mvmsta->tid_data[tid].rate_n_flags = + le32_to_cpu(tx_resp->initial_rate); + mvmsta->tid_data[tid].tx_time = + le16_to_cpu(tx_resp->wireless_media_time); + mvmsta->tid_data[tid].lq_color = + TX_RES_RATE_TABLE_COL_GET(tx_resp->tlc_info); + iwl_mvm_tx_airtime(mvm, mvmsta, + le16_to_cpu(tx_resp->wireless_media_time)); + } + + rcu_read_unlock(); +} + +void iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + + if (tx_resp->frame_count == 1) + iwl_mvm_rx_tx_cmd_single(mvm, pkt); + else + iwl_mvm_rx_tx_cmd_agg(mvm, pkt); +} + +static void iwl_mvm_tx_reclaim(struct iwl_mvm *mvm, int sta_id, int tid, + int txq, int index, + struct ieee80211_tx_info *tx_info, u32 rate, + bool is_flush) +{ + struct sk_buff_head reclaimed_skbs; + struct iwl_mvm_tid_data *tid_data = NULL; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta = NULL; + struct sk_buff *skb; + int freed; + + if (WARN_ONCE(sta_id >= mvm->fw->ucode_capa.num_stations || + tid > IWL_MAX_TID_COUNT, + "sta_id %d tid %d", sta_id, tid)) + return; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + /* Reclaiming frames for a station that has been deleted ? */ + if (WARN_ON_ONCE(!sta)) { + rcu_read_unlock(); + return; + } + + __skb_queue_head_init(&reclaimed_skbs); + + /* + * Release all TFDs before the SSN, i.e. all TFDs in front of + * block-ack window (we assume that they've been successfully + * transmitted ... if not, it's too late anyway). + */ + iwl_trans_reclaim(mvm->trans, txq, index, &reclaimed_skbs); + + skb_queue_walk(&reclaimed_skbs, skb) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + + memset(&info->status, 0, sizeof(info->status)); + /* Packet was transmitted successfully, failures come as single + * frames because before failing a frame the firmware transmits + * it without aggregation at least once. + */ + if (!is_flush) + info->flags |= IEEE80211_TX_STAT_ACK; + } + + /* + * It's possible to get a BA response after invalidating the rcu (rcu is + * invalidated in order to prevent new Tx from being sent, but there may + * be some frames already in-flight). + * In this case we just want to reclaim, and could skip all the + * sta-dependent stuff since it's in the middle of being removed + * anyways. + */ + if (IS_ERR(sta)) + goto out; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + tid_data = &mvmsta->tid_data[tid]; + + if (tid_data->txq_id != txq) { + IWL_ERR(mvm, + "invalid reclaim request: Q %d, tid %d\n", + tid_data->txq_id, tid); + rcu_read_unlock(); + return; + } + + spin_lock_bh(&mvmsta->lock); + + tid_data->next_reclaimed = index; + + iwl_mvm_check_ratid_empty(mvm, sta, tid); + + freed = 0; + + /* pack lq color from tid_data along the reduced txp */ + tx_info->status.status_driver_data[0] = + RS_DRV_DATA_PACK(tid_data->lq_color, + tx_info->status.status_driver_data[0]); + tx_info->status.status_driver_data[1] = (void *)(uintptr_t)rate; + + skb_queue_walk(&reclaimed_skbs, skb) { + struct ieee80211_hdr *hdr = (void *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (!is_flush) { + if (ieee80211_is_data_qos(hdr->frame_control)) + freed++; + else + WARN_ON_ONCE(tid != IWL_MAX_TID_COUNT); + } + + /* this is the first skb we deliver in this batch */ + /* put the rate scaling data there */ + if (freed == 1) { + info->flags |= IEEE80211_TX_STAT_AMPDU; + memcpy(&info->status, &tx_info->status, + sizeof(tx_info->status)); + iwl_mvm_hwrate_to_tx_status(mvm->fw, rate, info); + } + } + + spin_unlock_bh(&mvmsta->lock); + + /* We got a BA notif with 0 acked or scd_ssn didn't progress which is + * possible (i.e. first MPDU in the aggregation wasn't acked) + * Still it's important to update RS about sent vs. acked. + */ + if (!is_flush && skb_queue_empty(&reclaimed_skbs)) { + struct ieee80211_chanctx_conf *chanctx_conf = NULL; + + if (mvmsta->vif) + chanctx_conf = + rcu_dereference(mvmsta->vif->chanctx_conf); + + if (WARN_ON_ONCE(!chanctx_conf)) + goto out; + + tx_info->band = chanctx_conf->def.chan->band; + iwl_mvm_hwrate_to_tx_status(mvm->fw, rate, tx_info); + + if (!iwl_mvm_has_tlc_offload(mvm)) { + IWL_DEBUG_TX_REPLY(mvm, + "No reclaim. Update rs directly\n"); + iwl_mvm_rs_tx_status(mvm, sta, tid, tx_info, false); + } + } + +out: + rcu_read_unlock(); + + while (!skb_queue_empty(&reclaimed_skbs)) { + skb = __skb_dequeue(&reclaimed_skbs); + ieee80211_tx_status(mvm->hw, skb); + } +} + +void iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + int sta_id, tid, txq, index; + struct ieee80211_tx_info ba_info = {}; + struct iwl_mvm_ba_notif *ba_notif; + struct iwl_mvm_tid_data *tid_data; + struct iwl_mvm_sta *mvmsta; + + ba_info.flags = IEEE80211_TX_STAT_AMPDU; + + if (iwl_mvm_has_new_tx_api(mvm)) { + struct iwl_mvm_compressed_ba_notif *ba_res = + (void *)pkt->data; + u8 lq_color = TX_RES_RATE_TABLE_COL_GET(ba_res->tlc_rate_info); + u16 tfd_cnt; + int i; + + if (unlikely(sizeof(*ba_res) > pkt_len)) + return; + + sta_id = ba_res->sta_id; + ba_info.status.ampdu_ack_len = (u8)le16_to_cpu(ba_res->done); + ba_info.status.ampdu_len = (u8)le16_to_cpu(ba_res->txed); + ba_info.status.tx_time = + (u16)le32_to_cpu(ba_res->wireless_time); + ba_info.status.status_driver_data[0] = + (void *)(uintptr_t)ba_res->reduced_txp; + + tfd_cnt = le16_to_cpu(ba_res->tfd_cnt); + if (!tfd_cnt || struct_size(ba_res, tfd, tfd_cnt) > pkt_len) + return; + + rcu_read_lock(); + + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, sta_id); + /* + * It's possible to get a BA response after invalidating the rcu + * (rcu is invalidated in order to prevent new Tx from being + * sent, but there may be some frames already in-flight). + * In this case we just want to reclaim, and could skip all the + * sta-dependent stuff since it's in the middle of being removed + * anyways. + */ + + /* Free per TID */ + for (i = 0; i < tfd_cnt; i++) { + struct iwl_mvm_compressed_ba_tfd *ba_tfd = + &ba_res->tfd[i]; + + tid = ba_tfd->tid; + if (tid == IWL_MGMT_TID) + tid = IWL_MAX_TID_COUNT; + + if (mvmsta) + mvmsta->tid_data[i].lq_color = lq_color; + + iwl_mvm_tx_reclaim(mvm, sta_id, tid, + (int)(le16_to_cpu(ba_tfd->q_num)), + le16_to_cpu(ba_tfd->tfd_index), + &ba_info, + le32_to_cpu(ba_res->tx_rate), false); + } + + if (mvmsta) + iwl_mvm_tx_airtime(mvm, mvmsta, + le32_to_cpu(ba_res->wireless_time)); + rcu_read_unlock(); + + IWL_DEBUG_TX_REPLY(mvm, + "BA_NOTIFICATION Received from sta_id = %d, flags %x, sent:%d, acked:%d\n", + sta_id, le32_to_cpu(ba_res->flags), + le16_to_cpu(ba_res->txed), + le16_to_cpu(ba_res->done)); + return; + } + + ba_notif = (void *)pkt->data; + sta_id = ba_notif->sta_id; + tid = ba_notif->tid; + /* "flow" corresponds to Tx queue */ + txq = le16_to_cpu(ba_notif->scd_flow); + /* "ssn" is start of block-ack Tx window, corresponds to index + * (in Tx queue's circular buffer) of first TFD/frame in window */ + index = le16_to_cpu(ba_notif->scd_ssn); + + rcu_read_lock(); + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, sta_id); + if (WARN_ON_ONCE(!mvmsta)) { + rcu_read_unlock(); + return; + } + + tid_data = &mvmsta->tid_data[tid]; + + ba_info.status.ampdu_ack_len = ba_notif->txed_2_done; + ba_info.status.ampdu_len = ba_notif->txed; + ba_info.status.tx_time = tid_data->tx_time; + ba_info.status.status_driver_data[0] = + (void *)(uintptr_t)ba_notif->reduced_txp; + + rcu_read_unlock(); + + iwl_mvm_tx_reclaim(mvm, sta_id, tid, txq, index, &ba_info, + tid_data->rate_n_flags, false); + + IWL_DEBUG_TX_REPLY(mvm, + "BA_NOTIFICATION Received from %pM, sta_id = %d\n", + ba_notif->sta_addr, ba_notif->sta_id); + + IWL_DEBUG_TX_REPLY(mvm, + "TID = %d, SeqCtl = %d, bitmap = 0x%llx, scd_flow = %d, scd_ssn = %d sent:%d, acked:%d\n", + ba_notif->tid, le16_to_cpu(ba_notif->seq_ctl), + le64_to_cpu(ba_notif->bitmap), txq, index, + ba_notif->txed, ba_notif->txed_2_done); + + IWL_DEBUG_TX_REPLY(mvm, "reduced txp from ba notif %d\n", + ba_notif->reduced_txp); +} + +/* + * Note that there are transports that buffer frames before they reach + * the firmware. This means that after flush_tx_path is called, the + * queue might not be empty. The race-free way to handle this is to: + * 1) set the station as draining + * 2) flush the Tx path + * 3) wait for the transport queues to be empty + */ +int iwl_mvm_flush_tx_path(struct iwl_mvm *mvm, u32 tfd_msk) +{ + int ret; + struct iwl_tx_path_flush_cmd_v1 flush_cmd = { + .queues_ctl = cpu_to_le32(tfd_msk), + .flush_ctl = cpu_to_le16(DUMP_TX_FIFO_FLUSH), + }; + + WARN_ON(iwl_mvm_has_new_tx_api(mvm)); + ret = iwl_mvm_send_cmd_pdu(mvm, TXPATH_FLUSH, 0, + sizeof(flush_cmd), &flush_cmd); + if (ret) + IWL_ERR(mvm, "Failed to send flush command (%d)\n", ret); + return ret; +} + +int iwl_mvm_flush_sta_tids(struct iwl_mvm *mvm, u32 sta_id, u16 tids) +{ + int ret; + struct iwl_tx_path_flush_cmd_rsp *rsp; + struct iwl_tx_path_flush_cmd flush_cmd = { + .sta_id = cpu_to_le32(sta_id), + .tid_mask = cpu_to_le16(tids), + }; + + struct iwl_host_cmd cmd = { + .id = TXPATH_FLUSH, + .len = { sizeof(flush_cmd), }, + .data = { &flush_cmd, }, + }; + + WARN_ON(!iwl_mvm_has_new_tx_api(mvm)); + + if (iwl_fw_lookup_notif_ver(mvm->fw, LONG_GROUP, TXPATH_FLUSH, 0) > 0) + cmd.flags |= CMD_WANT_SKB; + + IWL_DEBUG_TX_QUEUES(mvm, "flush for sta id %d tid mask 0x%x\n", + sta_id, tids); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + + if (ret) { + IWL_ERR(mvm, "Failed to send flush command (%d)\n", ret); + return ret; + } + + if (cmd.flags & CMD_WANT_SKB) { + int i; + int num_flushed_queues; + + if (WARN_ON_ONCE(iwl_rx_packet_payload_len(cmd.resp_pkt) != sizeof(*rsp))) { + ret = -EIO; + goto free_rsp; + } + + rsp = (void *)cmd.resp_pkt->data; + + if (WARN_ONCE(le16_to_cpu(rsp->sta_id) != sta_id, + "sta_id %d != rsp_sta_id %d", + sta_id, le16_to_cpu(rsp->sta_id))) { + ret = -EIO; + goto free_rsp; + } + + num_flushed_queues = le16_to_cpu(rsp->num_flushed_queues); + if (WARN_ONCE(num_flushed_queues > IWL_TX_FLUSH_QUEUE_RSP, + "num_flushed_queues %d", num_flushed_queues)) { + ret = -EIO; + goto free_rsp; + } + + for (i = 0; i < num_flushed_queues; i++) { + struct ieee80211_tx_info tx_info = {}; + struct iwl_flush_queue_info *queue_info = &rsp->queues[i]; + int tid = le16_to_cpu(queue_info->tid); + int read_before = le16_to_cpu(queue_info->read_before_flush); + int read_after = le16_to_cpu(queue_info->read_after_flush); + int queue_num = le16_to_cpu(queue_info->queue_num); + + if (tid == IWL_MGMT_TID) + tid = IWL_MAX_TID_COUNT; + + IWL_DEBUG_TX_QUEUES(mvm, + "tid %d queue_id %d read-before %d read-after %d\n", + tid, queue_num, read_before, read_after); + + iwl_mvm_tx_reclaim(mvm, sta_id, tid, queue_num, read_after, + &tx_info, 0, true); + } +free_rsp: + iwl_free_resp(&cmd); + } + return ret; +} + +int iwl_mvm_flush_sta(struct iwl_mvm *mvm, void *sta, bool internal) +{ + struct iwl_mvm_int_sta *int_sta = sta; + struct iwl_mvm_sta *mvm_sta = sta; + + BUILD_BUG_ON(offsetof(struct iwl_mvm_int_sta, sta_id) != + offsetof(struct iwl_mvm_sta, sta_id)); + + if (iwl_mvm_has_new_tx_api(mvm)) + return iwl_mvm_flush_sta_tids(mvm, mvm_sta->sta_id, 0xffff); + + if (internal) + return iwl_mvm_flush_tx_path(mvm, int_sta->tfd_queue_msk); + + return iwl_mvm_flush_tx_path(mvm, mvm_sta->tfd_queue_msk); +} diff --git a/sys/contrib/dev/iwlwifi/mvm/utils.c b/sys/contrib/dev/iwlwifi/mvm/utils.c new file mode 100644 index 00000000000..91cd1a93127 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mvm/utils.c @@ -0,0 +1,1137 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#if defined(__FreeBSD__) +#include +#endif +#include + +#include "iwl-debug.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" +#include "mvm.h" +#include "fw/api/rs.h" +#include "fw/img.h" + +/* + * Will return 0 even if the cmd failed when RFKILL is asserted unless + * CMD_WANT_SKB is set in cmd->flags. + */ +int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd) +{ + int ret; + +#if defined(CONFIG_IWLWIFI_DEBUGFS) && defined(CONFIG_PM_SLEEP) + if (WARN_ON(mvm->d3_test_active)) + return -EIO; +#endif + + /* + * Synchronous commands from this op-mode must hold + * the mutex, this ensures we don't try to send two + * (or more) synchronous commands at a time. + */ + if (!(cmd->flags & CMD_ASYNC)) + lockdep_assert_held(&mvm->mutex); + + ret = iwl_trans_send_cmd(mvm->trans, cmd); + + /* + * If the caller wants the SKB, then don't hide any problems, the + * caller might access the response buffer which will be NULL if + * the command failed. + */ + if (cmd->flags & CMD_WANT_SKB) + return ret; + + /* + * Silently ignore failures if RFKILL is asserted or + * we are in suspend\resume process + */ + if (!ret || ret == -ERFKILL || ret == -EHOSTDOWN) + return 0; + return ret; +} + +int iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, + u32 flags, u16 len, const void *data) +{ + struct iwl_host_cmd cmd = { + .id = id, + .len = { len, }, + .data = { data, }, + .flags = flags, + }; + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +/* + * We assume that the caller set the status to the success value + */ +int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, + u32 *status) +{ + struct iwl_rx_packet *pkt; + struct iwl_cmd_response *resp; + int ret, resp_len; + + lockdep_assert_held(&mvm->mutex); + +#if defined(CONFIG_IWLWIFI_DEBUGFS) && defined(CONFIG_PM_SLEEP) + if (WARN_ON(mvm->d3_test_active)) + return -EIO; +#endif + + /* + * Only synchronous commands can wait for status, + * we use WANT_SKB so the caller can't. + */ + if (WARN_ONCE(cmd->flags & (CMD_ASYNC | CMD_WANT_SKB), + "cmd flags %x", cmd->flags)) + return -EINVAL; + + cmd->flags |= CMD_WANT_SKB; + + ret = iwl_trans_send_cmd(mvm->trans, cmd); + if (ret == -ERFKILL) { + /* + * The command failed because of RFKILL, don't update + * the status, leave it as success and return 0. + */ + return 0; + } else if (ret) { + return ret; + } + + pkt = cmd->resp_pkt; + + resp_len = iwl_rx_packet_payload_len(pkt); + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + ret = -EIO; + goto out_free_resp; + } + + resp = (void *)pkt->data; + *status = le32_to_cpu(resp->status); + out_free_resp: + iwl_free_resp(cmd); + return ret; +} + +/* + * We assume that the caller set the status to the sucess value + */ +int iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, u16 len, + const void *data, u32 *status) +{ + struct iwl_host_cmd cmd = { + .id = id, + .len = { len, }, + .data = { data, }, + }; + + return iwl_mvm_send_cmd_status(mvm, &cmd, status); +} + +int iwl_mvm_legacy_hw_idx_to_mac80211_idx(u32 rate_n_flags, + enum nl80211_band band) +{ + int format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + int rate = rate_n_flags & RATE_LEGACY_RATE_MSK; + bool is_LB = band == NL80211_BAND_2GHZ; + + if (format == RATE_MCS_LEGACY_OFDM_MSK) + return is_LB ? rate + IWL_FIRST_OFDM_RATE : + rate; + + /* CCK is not allowed in HB */ + return is_LB ? rate : -1; +} + +int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags, + enum nl80211_band band) +{ + int rate = rate_n_flags & RATE_LEGACY_RATE_MSK_V1; + int idx; + int band_offset = 0; + + /* Legacy rate format, search for match in table */ + if (band != NL80211_BAND_2GHZ) + band_offset = IWL_FIRST_OFDM_RATE; + for (idx = band_offset; idx < IWL_RATE_COUNT_LEGACY; idx++) + if (iwl_fw_rate_idx_to_plcp(idx) == rate) + return idx - band_offset; + + return -1; +} + +u8 iwl_mvm_mac80211_idx_to_hwrate(const struct iwl_fw *fw, int rate_idx) +{ + if (iwl_fw_lookup_cmd_ver(fw, LONG_GROUP, + TX_CMD, 0) > 8) + /* In the new rate legacy rates are indexed: + * 0 - 3 for CCK and 0 - 7 for OFDM. + */ + return (rate_idx >= IWL_FIRST_OFDM_RATE ? + rate_idx - IWL_FIRST_OFDM_RATE : + rate_idx); + + return iwl_fw_rate_idx_to_plcp(rate_idx); +} + +u8 iwl_mvm_mac80211_ac_to_ucode_ac(enum ieee80211_ac_numbers ac) +{ + static const u8 mac80211_ac_to_ucode_ac[] = { + AC_VO, + AC_VI, + AC_BE, + AC_BK + }; + + return mac80211_ac_to_ucode_ac[ac]; +} + +void iwl_mvm_rx_fw_error(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_error_resp *err_resp = (void *)pkt->data; + + IWL_ERR(mvm, "FW Error notification: type 0x%08X cmd_id 0x%02X\n", + le32_to_cpu(err_resp->error_type), err_resp->cmd_id); + IWL_ERR(mvm, "FW Error notification: seq 0x%04X service 0x%08X\n", + le16_to_cpu(err_resp->bad_cmd_seq_num), + le32_to_cpu(err_resp->error_service)); + IWL_ERR(mvm, "FW Error notification: timestamp 0x%016llX\n", + le64_to_cpu(err_resp->timestamp)); +} + +/* + * Returns the first antenna as ANT_[ABC], as defined in iwl-config.h. + * The parameter should also be a combination of ANT_[ABC]. + */ +u8 first_antenna(u8 mask) +{ + BUILD_BUG_ON(ANT_A != BIT(0)); /* using ffs is wrong if not */ + if (WARN_ON_ONCE(!mask)) /* ffs will return 0 if mask is zeroed */ + return BIT(0); + return BIT(ffs(mask) - 1); +} + +#define MAX_ANT_NUM 2 +/* + * Toggles between TX antennas to send the probe request on. + * Receives the bitmask of valid TX antennas and the *index* used + * for the last TX, and returns the next valid *index* to use. + * In order to set it in the tx_cmd, must do BIT(idx). + */ +u8 iwl_mvm_next_antenna(struct iwl_mvm *mvm, u8 valid, u8 last_idx) +{ + u8 ind = last_idx; + int i; + + for (i = 0; i < MAX_ANT_NUM; i++) { + ind = (ind + 1) % MAX_ANT_NUM; + if (valid & BIT(ind)) + return ind; + } + + WARN_ONCE(1, "Failed to toggle between antennas 0x%x", valid); + return last_idx; +} + +int iwl_mvm_reconfig_scd(struct iwl_mvm *mvm, int queue, int fifo, int sta_id, + int tid, int frame_limit, u16 ssn) +{ + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_ENABLE_QUEUE, + .window = frame_limit, + .sta_id = sta_id, + .ssn = cpu_to_le16(ssn), + .tx_fifo = fifo, + .aggregate = (queue >= IWL_MVM_DQA_MIN_DATA_QUEUE || + queue == IWL_MVM_DQA_BSS_CLIENT_QUEUE), + .tid = tid, + }; + int ret; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + if (WARN(mvm->queue_info[queue].tid_bitmap == 0, + "Trying to reconfig unallocated queue %d\n", queue)) + return -ENXIO; + + IWL_DEBUG_TX_QUEUES(mvm, "Reconfig SCD for TXQ #%d\n", queue); + + ret = iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, 0, sizeof(cmd), &cmd); + WARN_ONCE(ret, "Failed to re-configure queue %d on FIFO %d, ret=%d\n", + queue, fifo, ret); + + return ret; +} + +/** + * iwl_mvm_send_lq_cmd() - Send link quality command + * @mvm: Driver data. + * @lq: Link quality command to send. + * + * The link quality command is sent as the last step of station creation. + * This is the special case in which init is set and we call a callback in + * this case to clear the state indicating that station creation is in + * progress. + */ +int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq) +{ + struct iwl_host_cmd cmd = { + .id = LQ_CMD, + .len = { sizeof(struct iwl_lq_cmd), }, + .flags = CMD_ASYNC, + .data = { lq, }, + }; + + if (WARN_ON(lq->sta_id == IWL_MVM_INVALID_STA || + iwl_mvm_has_tlc_offload(mvm))) + return -EINVAL; + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +/** + * iwl_mvm_update_smps - Get a request to change the SMPS mode + * @mvm: Driver data. + * @vif: Pointer to the ieee80211_vif structure + * @req_type: The part of the driver who call for a change. + * @smps_request: The request to change the SMPS mode. + * + * Get a requst to change the SMPS mode, + * and change it according to all other requests in the driver. + */ +void iwl_mvm_update_smps(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum iwl_mvm_smps_type_request req_type, + enum ieee80211_smps_mode smps_request) +{ + struct iwl_mvm_vif *mvmvif; + enum ieee80211_smps_mode smps_mode = IEEE80211_SMPS_AUTOMATIC; + int i; + + lockdep_assert_held(&mvm->mutex); + + /* SMPS is irrelevant for NICs that don't have at least 2 RX antenna */ + if (num_of_ant(iwl_mvm_get_valid_rx_ant(mvm)) == 1) + return; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + mvmvif = iwl_mvm_vif_from_mac80211(vif); + mvmvif->smps_requests[req_type] = smps_request; + for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++) { + if (mvmvif->smps_requests[i] == IEEE80211_SMPS_STATIC) { + smps_mode = IEEE80211_SMPS_STATIC; + break; + } + if (mvmvif->smps_requests[i] == IEEE80211_SMPS_DYNAMIC) + smps_mode = IEEE80211_SMPS_DYNAMIC; + } + + ieee80211_request_smps(vif, smps_mode); +} + +int iwl_mvm_request_statistics(struct iwl_mvm *mvm, bool clear) +{ + struct iwl_statistics_cmd scmd = { + .flags = clear ? cpu_to_le32(IWL_STATISTICS_FLG_CLEAR) : 0, + }; + struct iwl_host_cmd cmd = { + .id = STATISTICS_CMD, + .len[0] = sizeof(scmd), + .data[0] = &scmd, + .flags = CMD_WANT_SKB, + }; + int ret; + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) + return ret; + + iwl_mvm_handle_rx_statistics(mvm, cmd.resp_pkt); + iwl_free_resp(&cmd); + + if (clear) + iwl_mvm_accu_radio_stats(mvm); + + return 0; +} + +void iwl_mvm_accu_radio_stats(struct iwl_mvm *mvm) +{ + mvm->accu_radio_stats.rx_time += mvm->radio_stats.rx_time; + mvm->accu_radio_stats.tx_time += mvm->radio_stats.tx_time; + mvm->accu_radio_stats.on_time_rf += mvm->radio_stats.on_time_rf; + mvm->accu_radio_stats.on_time_scan += mvm->radio_stats.on_time_scan; +} + +struct iwl_mvm_diversity_iter_data { + struct iwl_mvm_phy_ctxt *ctxt; + bool result; +}; + +static void iwl_mvm_diversity_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_diversity_iter_data *data = _data; + int i; + + if (mvmvif->phy_ctxt != data->ctxt) + return; + + for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++) { + if (mvmvif->smps_requests[i] == IEEE80211_SMPS_STATIC || + mvmvif->smps_requests[i] == IEEE80211_SMPS_DYNAMIC) { + data->result = false; + break; + } + } +} + +bool iwl_mvm_rx_diversity_allowed(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt) +{ + struct iwl_mvm_diversity_iter_data data = { + .ctxt = ctxt, + .result = true, + }; + + lockdep_assert_held(&mvm->mutex); + + if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) + return false; + + if (num_of_ant(iwl_mvm_get_valid_rx_ant(mvm)) == 1) + return false; + + if (mvm->cfg->rx_with_siso_diversity) + return false; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_diversity_iter, &data); + + return data.result; +} + +void iwl_mvm_send_low_latency_cmd(struct iwl_mvm *mvm, + bool low_latency, u16 mac_id) +{ + struct iwl_mac_low_latency_cmd cmd = { + .mac_id = cpu_to_le32(mac_id) + }; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DYNAMIC_QUOTA)) + return; + + if (low_latency) { + /* currently we don't care about the direction */ + cmd.low_latency_rx = 1; + cmd.low_latency_tx = 1; + } + + if (iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(LOW_LATENCY_CMD, + MAC_CONF_GROUP, 0), + 0, sizeof(cmd), &cmd)) + IWL_ERR(mvm, "Failed to send low latency command\n"); +} + +int iwl_mvm_update_low_latency(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool low_latency, + enum iwl_mvm_low_latency_cause cause) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int res; + bool prev; + + lockdep_assert_held(&mvm->mutex); + + prev = iwl_mvm_vif_low_latency(mvmvif); + iwl_mvm_vif_set_low_latency(mvmvif, low_latency, cause); + + low_latency = iwl_mvm_vif_low_latency(mvmvif); + + if (low_latency == prev) + return 0; + + iwl_mvm_send_low_latency_cmd(mvm, low_latency, mvmvif->id); + + res = iwl_mvm_update_quotas(mvm, false, NULL); + if (res) + return res; + + iwl_mvm_bt_coex_vif_change(mvm); + + return iwl_mvm_power_update_mac(mvm); +} + +struct iwl_mvm_low_latency_iter { + bool result; + bool result_per_band[NUM_NL80211_BANDS]; +}; + +static void iwl_mvm_ll_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + struct iwl_mvm_low_latency_iter *result = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + enum nl80211_band band; + + if (iwl_mvm_vif_low_latency(mvmvif)) { + result->result = true; + + if (!mvmvif->phy_ctxt) + return; + + band = mvmvif->phy_ctxt->channel->band; + result->result_per_band[band] = true; + } +} + +bool iwl_mvm_low_latency(struct iwl_mvm *mvm) +{ + struct iwl_mvm_low_latency_iter data = {}; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_ll_iter, &data); + + return data.result; +} + +bool iwl_mvm_low_latency_band(struct iwl_mvm *mvm, enum nl80211_band band) +{ + struct iwl_mvm_low_latency_iter data = {}; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_ll_iter, &data); + + return data.result_per_band[band]; +} + +struct iwl_bss_iter_data { + struct ieee80211_vif *vif; + bool error; +}; + +static void iwl_mvm_bss_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_bss_iter_data *data = _data; + + if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) + return; + + if (data->vif) { + data->error = true; + return; + } + + data->vif = vif; +} + +struct ieee80211_vif *iwl_mvm_get_bss_vif(struct iwl_mvm *mvm) +{ + struct iwl_bss_iter_data bss_iter_data = {}; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bss_iface_iterator, &bss_iter_data); + + if (bss_iter_data.error) { + IWL_ERR(mvm, "More than one managed interface active!\n"); + return ERR_PTR(-EINVAL); + } + + return bss_iter_data.vif; +} + +struct iwl_bss_find_iter_data { + struct ieee80211_vif *vif; + u32 macid; +}; + +static void iwl_mvm_bss_find_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_bss_find_iter_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (mvmvif->id == data->macid) + data->vif = vif; +} + +struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid) +{ + struct iwl_bss_find_iter_data data = { + .macid = macid, + }; + + lockdep_assert_held(&mvm->mutex); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bss_find_iface_iterator, &data); + + return data.vif; +} + +struct iwl_sta_iter_data { + bool assoc; +}; + +static void iwl_mvm_sta_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_sta_iter_data *data = _data; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + if (vif->bss_conf.assoc) + data->assoc = true; +} + +bool iwl_mvm_is_vif_assoc(struct iwl_mvm *mvm) +{ + struct iwl_sta_iter_data data = { + .assoc = false, + }; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_sta_iface_iterator, + &data); + return data.assoc; +} + +unsigned int iwl_mvm_get_wd_timeout(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool tdls, bool cmd_q) +{ + struct iwl_fw_dbg_trigger_tlv *trigger; + struct iwl_fw_dbg_trigger_txq_timer *txq_timer; + unsigned int default_timeout = cmd_q ? + IWL_DEF_WD_TIMEOUT : + mvm->trans->trans_cfg->base_params->wd_timeout; + + if (!iwl_fw_dbg_trigger_enabled(mvm->fw, FW_DBG_TRIGGER_TXQ_TIMERS)) { + /* + * We can't know when the station is asleep or awake, so we + * must disable the queue hang detection. + */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_STA_PM_NOTIF) && + vif && vif->type == NL80211_IFTYPE_AP) + return IWL_WATCHDOG_DISABLED; + return default_timeout; + } + + trigger = iwl_fw_dbg_get_trigger(mvm->fw, FW_DBG_TRIGGER_TXQ_TIMERS); + txq_timer = (void *)trigger->data; + + if (tdls) + return le32_to_cpu(txq_timer->tdls); + + if (cmd_q) + return le32_to_cpu(txq_timer->command_queue); + + if (WARN_ON(!vif)) + return default_timeout; + + switch (ieee80211_vif_type_p2p(vif)) { + case NL80211_IFTYPE_ADHOC: + return le32_to_cpu(txq_timer->ibss); + case NL80211_IFTYPE_STATION: + return le32_to_cpu(txq_timer->bss); + case NL80211_IFTYPE_AP: + return le32_to_cpu(txq_timer->softap); + case NL80211_IFTYPE_P2P_CLIENT: + return le32_to_cpu(txq_timer->p2p_client); + case NL80211_IFTYPE_P2P_GO: + return le32_to_cpu(txq_timer->p2p_go); + case NL80211_IFTYPE_P2P_DEVICE: + return le32_to_cpu(txq_timer->p2p_device); + case NL80211_IFTYPE_MONITOR: + return default_timeout; + default: + WARN_ON(1); + return mvm->trans->trans_cfg->base_params->wd_timeout; + } +} + +void iwl_mvm_connection_loss(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + const char *errmsg) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_mlme *trig_mlme; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_MLME); + if (!trig) + goto out; + + trig_mlme = (void *)trig->data; + + if (trig_mlme->stop_connection_loss && + --trig_mlme->stop_connection_loss) + goto out; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, "%s", errmsg); + +out: + ieee80211_connection_loss(vif); +} + +void iwl_mvm_event_frame_timeout_callback(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_sta *sta, + u16 tid) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_ba *ba_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_BA); + if (!trig) + return; + + ba_trig = (void *)trig->data; + + if (!(le16_to_cpu(ba_trig->frame_timeout) & BIT(tid))) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "Frame from %pM timed out, tid %d", + sta->addr, tid); +} + +u8 iwl_mvm_tcm_load_percentage(u32 airtime, u32 elapsed) +{ + if (!elapsed) + return 0; + + return (100 * airtime / elapsed) / USEC_PER_MSEC; +} + +static enum iwl_mvm_traffic_load +iwl_mvm_tcm_load(struct iwl_mvm *mvm, u32 airtime, unsigned long elapsed) +{ + u8 load = iwl_mvm_tcm_load_percentage(airtime, elapsed); + + if (load > IWL_MVM_TCM_LOAD_HIGH_THRESH) + return IWL_MVM_TRAFFIC_HIGH; + if (load > IWL_MVM_TCM_LOAD_MEDIUM_THRESH) + return IWL_MVM_TRAFFIC_MEDIUM; + + return IWL_MVM_TRAFFIC_LOW; +} + +static void iwl_mvm_tcm_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + bool low_latency, prev = mvmvif->low_latency & LOW_LATENCY_TRAFFIC; + + if (mvmvif->id >= NUM_MAC_INDEX_DRIVER) + return; + + low_latency = mvm->tcm.result.low_latency[mvmvif->id]; + + if (!mvm->tcm.result.change[mvmvif->id] && + prev == low_latency) { + iwl_mvm_update_quotas(mvm, false, NULL); + return; + } + + if (prev != low_latency) { + /* this sends traffic load and updates quota as well */ + iwl_mvm_update_low_latency(mvm, vif, low_latency, + LOW_LATENCY_TRAFFIC); + } else { + iwl_mvm_update_quotas(mvm, false, NULL); + } +} + +static void iwl_mvm_tcm_results(struct iwl_mvm *mvm) +{ + mutex_lock(&mvm->mutex); + + ieee80211_iterate_active_interfaces( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_tcm_iter, mvm); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) + iwl_mvm_config_scan(mvm); + + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_tcm_uapsd_nonagg_detected_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm; + struct iwl_mvm_vif *mvmvif; + struct ieee80211_vif *vif; + + mvmvif = container_of(wk, struct iwl_mvm_vif, + uapsd_nonagg_detected_wk.work); + vif = container_of((void *)mvmvif, struct ieee80211_vif, drv_priv); + mvm = mvmvif->mvm; + + if (mvm->tcm.data[mvmvif->id].opened_rx_ba_sessions) + return; + + /* remember that this AP is broken */ + memcpy(mvm->uapsd_noagg_bssids[mvm->uapsd_noagg_bssid_write_idx].addr, + vif->bss_conf.bssid, ETH_ALEN); + mvm->uapsd_noagg_bssid_write_idx++; + if (mvm->uapsd_noagg_bssid_write_idx >= IWL_MVM_UAPSD_NOAGG_LIST_LEN) + mvm->uapsd_noagg_bssid_write_idx = 0; + + iwl_mvm_connection_loss(mvm, vif, + "AP isn't using AMPDU with uAPSD enabled"); +} + +static void iwl_mvm_uapsd_agg_disconnect(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + if (!vif->bss_conf.assoc) + return; + + if (!mvmvif->queue_params[IEEE80211_AC_VO].uapsd && + !mvmvif->queue_params[IEEE80211_AC_VI].uapsd && + !mvmvif->queue_params[IEEE80211_AC_BE].uapsd && + !mvmvif->queue_params[IEEE80211_AC_BK].uapsd) + return; + + if (mvm->tcm.data[mvmvif->id].uapsd_nonagg_detect.detected) + return; + + mvm->tcm.data[mvmvif->id].uapsd_nonagg_detect.detected = true; + IWL_INFO(mvm, + "detected AP should do aggregation but isn't, likely due to U-APSD\n"); + schedule_delayed_work(&mvmvif->uapsd_nonagg_detected_wk, 15 * HZ); +} + +static void iwl_mvm_check_uapsd_agg_expected_tpt(struct iwl_mvm *mvm, + unsigned int elapsed, + int mac) +{ + u64 bytes = mvm->tcm.data[mac].uapsd_nonagg_detect.rx_bytes; + u64 tpt; + unsigned long rate; + struct ieee80211_vif *vif; + + rate = ewma_rate_read(&mvm->tcm.data[mac].uapsd_nonagg_detect.rate); + + if (!rate || mvm->tcm.data[mac].opened_rx_ba_sessions || + mvm->tcm.data[mac].uapsd_nonagg_detect.detected) + return; + + if (iwl_mvm_has_new_rx_api(mvm)) { + tpt = 8 * bytes; /* kbps */ + do_div(tpt, elapsed); + rate *= 1000; /* kbps */ + if (tpt < 22 * rate / 100) + return; + } else { + /* + * the rate here is actually the threshold, in 100Kbps units, + * so do the needed conversion from bytes to 100Kbps: + * 100kb = bits / (100 * 1000), + * 100kbps = 100kb / (msecs / 1000) == + * (bits / (100 * 1000)) / (msecs / 1000) == + * bits / (100 * msecs) + */ + tpt = (8 * bytes); + do_div(tpt, elapsed * 100); + if (tpt < rate) + return; + } + + rcu_read_lock(); + vif = rcu_dereference(mvm->vif_id_to_mac[mac]); + if (vif) + iwl_mvm_uapsd_agg_disconnect(mvm, vif); + rcu_read_unlock(); +} + +static void iwl_mvm_tcm_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u32 *band = _data; + + if (!mvmvif->phy_ctxt) + return; + + band[mvmvif->id] = mvmvif->phy_ctxt->channel->band; +} + +static unsigned long iwl_mvm_calc_tcm_stats(struct iwl_mvm *mvm, + unsigned long ts, + bool handle_uapsd) +{ + unsigned int elapsed = jiffies_to_msecs(ts - mvm->tcm.ts); + unsigned int uapsd_elapsed = + jiffies_to_msecs(ts - mvm->tcm.uapsd_nonagg_ts); + u32 total_airtime = 0; + u32 band_airtime[NUM_NL80211_BANDS] = {0}; + u32 band[NUM_MAC_INDEX_DRIVER] = {0}; + int ac, mac, i; + bool low_latency = false; + enum iwl_mvm_traffic_load load, band_load; + bool handle_ll = time_after(ts, mvm->tcm.ll_ts + MVM_LL_PERIOD); + + if (handle_ll) + mvm->tcm.ll_ts = ts; + if (handle_uapsd) + mvm->tcm.uapsd_nonagg_ts = ts; + + mvm->tcm.result.elapsed = elapsed; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_tcm_iterator, + &band); + + for (mac = 0; mac < NUM_MAC_INDEX_DRIVER; mac++) { + struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[mac]; + u32 vo_vi_pkts = 0; + u32 airtime = mdata->rx.airtime + mdata->tx.airtime; + + total_airtime += airtime; + band_airtime[band[mac]] += airtime; + + load = iwl_mvm_tcm_load(mvm, airtime, elapsed); + mvm->tcm.result.change[mac] = load != mvm->tcm.result.load[mac]; + mvm->tcm.result.load[mac] = load; + mvm->tcm.result.airtime[mac] = airtime; + + for (ac = IEEE80211_AC_VO; ac <= IEEE80211_AC_VI; ac++) + vo_vi_pkts += mdata->rx.pkts[ac] + + mdata->tx.pkts[ac]; + + /* enable immediately with enough packets but defer disabling */ + if (vo_vi_pkts > IWL_MVM_TCM_LOWLAT_ENABLE_THRESH) + mvm->tcm.result.low_latency[mac] = true; + else if (handle_ll) + mvm->tcm.result.low_latency[mac] = false; + + if (handle_ll) { + /* clear old data */ + memset(&mdata->rx.pkts, 0, sizeof(mdata->rx.pkts)); + memset(&mdata->tx.pkts, 0, sizeof(mdata->tx.pkts)); + } + low_latency |= mvm->tcm.result.low_latency[mac]; + + if (!mvm->tcm.result.low_latency[mac] && handle_uapsd) + iwl_mvm_check_uapsd_agg_expected_tpt(mvm, uapsd_elapsed, + mac); + /* clear old data */ + if (handle_uapsd) + mdata->uapsd_nonagg_detect.rx_bytes = 0; + memset(&mdata->rx.airtime, 0, sizeof(mdata->rx.airtime)); + memset(&mdata->tx.airtime, 0, sizeof(mdata->tx.airtime)); + } + + load = iwl_mvm_tcm_load(mvm, total_airtime, elapsed); + mvm->tcm.result.global_load = load; + + for (i = 0; i < NUM_NL80211_BANDS; i++) { + band_load = iwl_mvm_tcm_load(mvm, band_airtime[i], elapsed); + mvm->tcm.result.band_load[i] = band_load; + } + + /* + * If the current load isn't low we need to force re-evaluation + * in the TCM period, so that we can return to low load if there + * was no traffic at all (and thus iwl_mvm_recalc_tcm didn't get + * triggered by traffic). + */ + if (load != IWL_MVM_TRAFFIC_LOW) + return MVM_TCM_PERIOD; + /* + * If low-latency is active we need to force re-evaluation after + * (the longer) MVM_LL_PERIOD, so that we can disable low-latency + * when there's no traffic at all. + */ + if (low_latency) + return MVM_LL_PERIOD; + /* + * Otherwise, we don't need to run the work struct because we're + * in the default "idle" state - traffic indication is low (which + * also covers the "no traffic" case) and low-latency is disabled + * so there's no state that may need to be disabled when there's + * no traffic at all. + * + * Note that this has no impact on the regular scheduling of the + * updates triggered by traffic - those happen whenever one of the + * two timeouts expire (if there's traffic at all.) + */ + return 0; +} + +void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm) +{ + unsigned long ts = jiffies; + bool handle_uapsd = + time_after(ts, mvm->tcm.uapsd_nonagg_ts + + msecs_to_jiffies(IWL_MVM_UAPSD_NONAGG_PERIOD)); + + spin_lock(&mvm->tcm.lock); + if (mvm->tcm.paused || !time_after(ts, mvm->tcm.ts + MVM_TCM_PERIOD)) { + spin_unlock(&mvm->tcm.lock); + return; + } + spin_unlock(&mvm->tcm.lock); + + if (handle_uapsd && iwl_mvm_has_new_rx_api(mvm)) { + mutex_lock(&mvm->mutex); + if (iwl_mvm_request_statistics(mvm, true)) + handle_uapsd = false; + mutex_unlock(&mvm->mutex); + } + + spin_lock(&mvm->tcm.lock); + /* re-check if somebody else won the recheck race */ + if (!mvm->tcm.paused && time_after(ts, mvm->tcm.ts + MVM_TCM_PERIOD)) { + /* calculate statistics */ + unsigned long work_delay = iwl_mvm_calc_tcm_stats(mvm, ts, + handle_uapsd); + + /* the memset needs to be visible before the timestamp */ + smp_mb(); + mvm->tcm.ts = ts; + if (work_delay) + schedule_delayed_work(&mvm->tcm.work, work_delay); + } + spin_unlock(&mvm->tcm.lock); + + iwl_mvm_tcm_results(mvm); +} + +void iwl_mvm_tcm_work(struct work_struct *work) +{ + struct delayed_work *delayed_work = to_delayed_work(work); + struct iwl_mvm *mvm = container_of(delayed_work, struct iwl_mvm, + tcm.work); + + iwl_mvm_recalc_tcm(mvm); +} + +void iwl_mvm_pause_tcm(struct iwl_mvm *mvm, bool with_cancel) +{ + spin_lock_bh(&mvm->tcm.lock); + mvm->tcm.paused = true; + spin_unlock_bh(&mvm->tcm.lock); + if (with_cancel) + cancel_delayed_work_sync(&mvm->tcm.work); +} + +void iwl_mvm_resume_tcm(struct iwl_mvm *mvm) +{ + int mac; + bool low_latency = false; + + spin_lock_bh(&mvm->tcm.lock); + mvm->tcm.ts = jiffies; + mvm->tcm.ll_ts = jiffies; + for (mac = 0; mac < NUM_MAC_INDEX_DRIVER; mac++) { + struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[mac]; + + memset(&mdata->rx.pkts, 0, sizeof(mdata->rx.pkts)); + memset(&mdata->tx.pkts, 0, sizeof(mdata->tx.pkts)); + memset(&mdata->rx.airtime, 0, sizeof(mdata->rx.airtime)); + memset(&mdata->tx.airtime, 0, sizeof(mdata->tx.airtime)); + + if (mvm->tcm.result.low_latency[mac]) + low_latency = true; + } + /* The TCM data needs to be reset before "paused" flag changes */ + smp_mb(); + mvm->tcm.paused = false; + + /* + * if the current load is not low or low latency is active, force + * re-evaluation to cover the case of no traffic. + */ + if (mvm->tcm.result.global_load > IWL_MVM_TRAFFIC_LOW) + schedule_delayed_work(&mvm->tcm.work, MVM_TCM_PERIOD); + else if (low_latency) + schedule_delayed_work(&mvm->tcm.work, MVM_LL_PERIOD); + + spin_unlock_bh(&mvm->tcm.lock); +} + +void iwl_mvm_tcm_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + INIT_DELAYED_WORK(&mvmvif->uapsd_nonagg_detected_wk, + iwl_mvm_tcm_uapsd_nonagg_detected_wk); +} + +void iwl_mvm_tcm_rm_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + cancel_delayed_work_sync(&mvmvif->uapsd_nonagg_detected_wk); +} + +u32 iwl_mvm_get_systime(struct iwl_mvm *mvm) +{ + u32 reg_addr = DEVICE_SYSTEM_TIME_REG; + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000 && + mvm->trans->cfg->gp2_reg_addr) + reg_addr = mvm->trans->cfg->gp2_reg_addr; + + return iwl_read_prph(mvm->trans, reg_addr); +} + +void iwl_mvm_get_sync_time(struct iwl_mvm *mvm, int clock_type, + u32 *gp2, u64 *boottime, ktime_t *realtime) +{ + bool ps_disabled; + + lockdep_assert_held(&mvm->mutex); + + /* Disable power save when reading GP2 */ + ps_disabled = mvm->ps_disabled; + if (!ps_disabled) { + mvm->ps_disabled = true; + iwl_mvm_power_update_device(mvm); + } + + *gp2 = iwl_mvm_get_systime(mvm); + + if (clock_type == CLOCK_BOOTTIME && boottime) + *boottime = ktime_get_boottime_ns(); + else if (clock_type == CLOCK_REALTIME && realtime) + *realtime = ktime_get_real(); + + if (!ps_disabled) { + mvm->ps_disabled = ps_disabled; + iwl_mvm_power_update_device(mvm); + } +} diff --git a/sys/contrib/dev/iwlwifi/pcie/ctxt-info-gen3.c b/sys/contrib/dev/iwlwifi/pcie/ctxt-info-gen3.c new file mode 100644 index 00000000000..85a6da70ca7 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/pcie/ctxt-info-gen3.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2018-2021 Intel Corporation + */ +#include "iwl-trans.h" +#include "iwl-fh.h" +#include "iwl-context-info-gen3.h" +#include "internal.h" +#include "iwl-prph.h" + +static void +iwl_pcie_ctxt_info_dbg_enable(struct iwl_trans *trans, + struct iwl_prph_scratch_hwm_cfg *dbg_cfg, + u32 *control_flags) +{ + enum iwl_fw_ini_allocation_id alloc_id = IWL_FW_INI_ALLOCATION_ID_DBGC1; + struct iwl_fw_ini_allocation_tlv *fw_mon_cfg; + u32 dbg_flags = 0; + + if (!iwl_trans_dbg_ini_valid(trans)) { + struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + + iwl_pcie_alloc_fw_monitor(trans, 0); + + if (fw_mon->size) { + dbg_flags |= IWL_PRPH_SCRATCH_EDBG_DEST_DRAM; + + IWL_DEBUG_FW(trans, + "WRT: Applying DRAM buffer destination\n"); + + dbg_cfg->hwm_base_addr = cpu_to_le64(fw_mon->physical); + dbg_cfg->hwm_size = cpu_to_le32(fw_mon->size); + } + + goto out; + } + + fw_mon_cfg = &trans->dbg.fw_mon_cfg[alloc_id]; + + switch (le32_to_cpu(fw_mon_cfg->buf_location)) { + case IWL_FW_INI_LOCATION_SRAM_PATH: + dbg_flags |= IWL_PRPH_SCRATCH_EDBG_DEST_INTERNAL; + IWL_DEBUG_FW(trans, + "WRT: Applying SMEM buffer destination\n"); + break; + + case IWL_FW_INI_LOCATION_NPK_PATH: + dbg_flags |= IWL_PRPH_SCRATCH_EDBG_DEST_TB22DTF; + IWL_DEBUG_FW(trans, + "WRT: Applying NPK buffer destination\n"); + break; + + case IWL_FW_INI_LOCATION_DRAM_PATH: + if (trans->dbg.fw_mon_ini[alloc_id].num_frags) { + struct iwl_dram_data *frag = + &trans->dbg.fw_mon_ini[alloc_id].frags[0]; + dbg_flags |= IWL_PRPH_SCRATCH_EDBG_DEST_DRAM; + dbg_cfg->hwm_base_addr = cpu_to_le64(frag->physical); + dbg_cfg->hwm_size = cpu_to_le32(frag->size); + dbg_cfg->debug_token_config = cpu_to_le32(trans->dbg.ucode_preset); + IWL_DEBUG_FW(trans, + "WRT: Applying DRAM destination (debug_token_config=%u)\n", + dbg_cfg->debug_token_config); + IWL_DEBUG_FW(trans, + "WRT: Applying DRAM destination (alloc_id=%u, num_frags=%u)\n", + alloc_id, + trans->dbg.fw_mon_ini[alloc_id].num_frags); + } + break; + default: + IWL_ERR(trans, "WRT: Invalid buffer destination\n"); + } +out: + if (dbg_flags) + *control_flags |= IWL_PRPH_SCRATCH_EARLY_DEBUG_EN | dbg_flags; +} + +int iwl_pcie_ctxt_info_gen3_init(struct iwl_trans *trans, + const struct fw_img *fw) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_context_info_gen3 *ctxt_info_gen3; + struct iwl_prph_scratch *prph_scratch; + struct iwl_prph_scratch_ctrl_cfg *prph_sc_ctrl; + struct iwl_prph_info *prph_info; + u32 control_flags = 0; + int ret; + int cmdq_size = max_t(u32, IWL_CMD_QUEUE_SIZE, + trans->cfg->min_txq_size); + + switch (trans_pcie->rx_buf_size) { + case IWL_AMSDU_DEF: + return -EINVAL; + case IWL_AMSDU_2K: + break; + case IWL_AMSDU_4K: + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_4K; + break; + case IWL_AMSDU_8K: + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_4K; + /* if firmware supports the ext size, tell it */ + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_EXT_8K; + break; + case IWL_AMSDU_12K: + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_4K; + /* if firmware supports the ext size, tell it */ + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_EXT_16K; + break; + } + + /* Allocate prph scratch */ + prph_scratch = dma_alloc_coherent(trans->dev, sizeof(*prph_scratch), + &trans_pcie->prph_scratch_dma_addr, + GFP_KERNEL); + if (!prph_scratch) + return -ENOMEM; + + prph_sc_ctrl = &prph_scratch->ctrl_cfg; + + prph_sc_ctrl->version.version = 0; + prph_sc_ctrl->version.mac_id = + cpu_to_le16((u16)iwl_read32(trans, CSR_HW_REV)); + prph_sc_ctrl->version.size = cpu_to_le16(sizeof(*prph_scratch) / 4); + + control_flags |= IWL_PRPH_SCRATCH_MTR_MODE; + control_flags |= IWL_PRPH_MTR_FORMAT_256B & IWL_PRPH_SCRATCH_MTR_FORMAT; + + /* initialize RX default queue */ + prph_sc_ctrl->rbd_cfg.free_rbd_addr = + cpu_to_le64(trans_pcie->rxq->bd_dma); + + iwl_pcie_ctxt_info_dbg_enable(trans, &prph_sc_ctrl->hwm_cfg, + &control_flags); + prph_sc_ctrl->control.control_flags = cpu_to_le32(control_flags); + + /* allocate ucode sections in dram and set addresses */ + ret = iwl_pcie_init_fw_sec(trans, fw, &prph_scratch->dram); + if (ret) + goto err_free_prph_scratch; + + + /* Allocate prph information + * currently we don't assign to the prph info anything, but it would get + * assigned later + * + * We also use the second half of this page to give the device some + * dummy TR/CR tail pointers - which shouldn't be necessary as we don't + * use this, but the hardware still reads/writes there and we can't let + * it go do that with a NULL pointer. + */ + BUILD_BUG_ON(sizeof(*prph_info) > PAGE_SIZE / 2); + prph_info = dma_alloc_coherent(trans->dev, PAGE_SIZE, + &trans_pcie->prph_info_dma_addr, + GFP_KERNEL); + if (!prph_info) { + ret = -ENOMEM; + goto err_free_prph_scratch; + } + + /* Allocate context info */ + ctxt_info_gen3 = dma_alloc_coherent(trans->dev, + sizeof(*ctxt_info_gen3), + &trans_pcie->ctxt_info_dma_addr, + GFP_KERNEL); + if (!ctxt_info_gen3) { + ret = -ENOMEM; + goto err_free_prph_info; + } + + ctxt_info_gen3->prph_info_base_addr = + cpu_to_le64(trans_pcie->prph_info_dma_addr); + ctxt_info_gen3->prph_scratch_base_addr = + cpu_to_le64(trans_pcie->prph_scratch_dma_addr); + ctxt_info_gen3->prph_scratch_size = + cpu_to_le32(sizeof(*prph_scratch)); + ctxt_info_gen3->cr_head_idx_arr_base_addr = + cpu_to_le64(trans_pcie->rxq->rb_stts_dma); + ctxt_info_gen3->tr_tail_idx_arr_base_addr = + cpu_to_le64(trans_pcie->prph_info_dma_addr + PAGE_SIZE / 2); + ctxt_info_gen3->cr_tail_idx_arr_base_addr = + cpu_to_le64(trans_pcie->prph_info_dma_addr + 3 * PAGE_SIZE / 4); + ctxt_info_gen3->mtr_base_addr = + cpu_to_le64(trans->txqs.txq[trans->txqs.cmd.q_id]->dma_addr); + ctxt_info_gen3->mcr_base_addr = + cpu_to_le64(trans_pcie->rxq->used_bd_dma); + ctxt_info_gen3->mtr_size = + cpu_to_le16(TFD_QUEUE_CB_SIZE(cmdq_size)); + ctxt_info_gen3->mcr_size = + cpu_to_le16(RX_QUEUE_CB_SIZE(trans->cfg->num_rbds)); + + trans_pcie->ctxt_info_gen3 = ctxt_info_gen3; + trans_pcie->prph_info = prph_info; + trans_pcie->prph_scratch = prph_scratch; + + /* Allocate IML */ + trans_pcie->iml = dma_alloc_coherent(trans->dev, trans->iml_len, + &trans_pcie->iml_dma_addr, + GFP_KERNEL); + if (!trans_pcie->iml) { + ret = -ENOMEM; + goto err_free_ctxt_info; + } + + memcpy(trans_pcie->iml, trans->iml, trans->iml_len); + + iwl_enable_fw_load_int_ctx_info(trans); + + /* kick FW self load */ + iwl_write64(trans, CSR_CTXT_INFO_ADDR, + trans_pcie->ctxt_info_dma_addr); + iwl_write64(trans, CSR_IML_DATA_ADDR, + trans_pcie->iml_dma_addr); + iwl_write32(trans, CSR_IML_SIZE_ADDR, trans->iml_len); + + iwl_set_bit(trans, CSR_CTXT_INFO_BOOT_CTRL, + CSR_AUTO_FUNC_BOOT_ENA); + + return 0; + +err_free_ctxt_info: + dma_free_coherent(trans->dev, sizeof(*trans_pcie->ctxt_info_gen3), + trans_pcie->ctxt_info_gen3, + trans_pcie->ctxt_info_dma_addr); + trans_pcie->ctxt_info_gen3 = NULL; +err_free_prph_info: + dma_free_coherent(trans->dev, PAGE_SIZE, prph_info, + trans_pcie->prph_info_dma_addr); + +err_free_prph_scratch: + dma_free_coherent(trans->dev, + sizeof(*prph_scratch), + prph_scratch, + trans_pcie->prph_scratch_dma_addr); + return ret; + +} + +void iwl_pcie_ctxt_info_gen3_free(struct iwl_trans *trans, bool alive) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (trans_pcie->iml) { + dma_free_coherent(trans->dev, trans->iml_len, trans_pcie->iml, + trans_pcie->iml_dma_addr); + trans_pcie->iml_dma_addr = 0; + trans_pcie->iml = NULL; + } + + iwl_pcie_ctxt_info_free_fw_img(trans); + + if (alive) + return; + + if (!trans_pcie->ctxt_info_gen3) + return; + + /* ctxt_info_gen3 and prph_scratch are still needed for PNVM load */ + dma_free_coherent(trans->dev, sizeof(*trans_pcie->ctxt_info_gen3), + trans_pcie->ctxt_info_gen3, + trans_pcie->ctxt_info_dma_addr); + trans_pcie->ctxt_info_dma_addr = 0; + trans_pcie->ctxt_info_gen3 = NULL; + + dma_free_coherent(trans->dev, sizeof(*trans_pcie->prph_scratch), + trans_pcie->prph_scratch, + trans_pcie->prph_scratch_dma_addr); + trans_pcie->prph_scratch_dma_addr = 0; + trans_pcie->prph_scratch = NULL; + + /* this is needed for the entire lifetime */ + dma_free_coherent(trans->dev, PAGE_SIZE, trans_pcie->prph_info, + trans_pcie->prph_info_dma_addr); + trans_pcie->prph_info_dma_addr = 0; + trans_pcie->prph_info = NULL; +} + +int iwl_trans_pcie_ctx_info_gen3_set_pnvm(struct iwl_trans *trans, + const void *data, u32 len) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_prph_scratch_ctrl_cfg *prph_sc_ctrl = + &trans_pcie->prph_scratch->ctrl_cfg; + int ret; + + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + return 0; + + /* only allocate the DRAM if not allocated yet */ + if (!trans->pnvm_loaded) { + if (WARN_ON(prph_sc_ctrl->pnvm_cfg.pnvm_size)) + return -EBUSY; + + ret = iwl_pcie_ctxt_info_alloc_dma(trans, data, len, + &trans_pcie->pnvm_dram); + if (ret < 0) { + IWL_DEBUG_FW(trans, "Failed to allocate PNVM DMA %d.\n", + ret); + return ret; + } + } + + prph_sc_ctrl->pnvm_cfg.pnvm_base_addr = + cpu_to_le64(trans_pcie->pnvm_dram.physical); + prph_sc_ctrl->pnvm_cfg.pnvm_size = + cpu_to_le32(trans_pcie->pnvm_dram.size); + + return 0; +} + +int iwl_trans_pcie_ctx_info_gen3_set_reduce_power(struct iwl_trans *trans, + const void *data, u32 len) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_prph_scratch_ctrl_cfg *prph_sc_ctrl = + &trans_pcie->prph_scratch->ctrl_cfg; + int ret; + + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + return 0; + + /* only allocate the DRAM if not allocated yet */ + if (!trans->reduce_power_loaded) { + if (WARN_ON(prph_sc_ctrl->reduce_power_cfg.size)) + return -EBUSY; + + ret = iwl_pcie_ctxt_info_alloc_dma(trans, data, len, + &trans_pcie->reduce_power_dram); + if (ret < 0) { + IWL_DEBUG_FW(trans, + "Failed to allocate reduce power DMA %d.\n", + ret); + return ret; + } + } + + prph_sc_ctrl->reduce_power_cfg.base_addr = + cpu_to_le64(trans_pcie->reduce_power_dram.physical); + prph_sc_ctrl->reduce_power_cfg.size = + cpu_to_le32(trans_pcie->reduce_power_dram.size); + + return 0; +} diff --git a/sys/contrib/dev/iwlwifi/pcie/ctxt-info.c b/sys/contrib/dev/iwlwifi/pcie/ctxt-info.c new file mode 100644 index 00000000000..74ce31fdf45 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/pcie/ctxt-info.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#include "iwl-trans.h" +#include "iwl-fh.h" +#include "iwl-context-info.h" +#include "internal.h" +#include "iwl-prph.h" + +static void *_iwl_pcie_ctxt_info_dma_alloc_coherent(struct iwl_trans *trans, + size_t size, + dma_addr_t *phys, + int depth) +{ + void *result; + + if (WARN(depth > 2, + "failed to allocate DMA memory not crossing 2^32 boundary")) + return NULL; + + result = dma_alloc_coherent(trans->dev, size, phys, GFP_KERNEL); + + if (!result) + return NULL; + + if (unlikely(iwl_txq_crosses_4g_boundary(*phys, size))) { + void *old = result; + dma_addr_t oldphys = *phys; + + result = _iwl_pcie_ctxt_info_dma_alloc_coherent(trans, size, + phys, + depth + 1); + dma_free_coherent(trans->dev, size, old, oldphys); + } + + return result; +} + +static void *iwl_pcie_ctxt_info_dma_alloc_coherent(struct iwl_trans *trans, + size_t size, + dma_addr_t *phys) +{ + return _iwl_pcie_ctxt_info_dma_alloc_coherent(trans, size, phys, 0); +} + +int iwl_pcie_ctxt_info_alloc_dma(struct iwl_trans *trans, + const void *data, u32 len, + struct iwl_dram_data *dram) +{ + dram->block = iwl_pcie_ctxt_info_dma_alloc_coherent(trans, len, + &dram->physical); + if (!dram->block) + return -ENOMEM; + + dram->size = len; + memcpy(dram->block, data, len); + + return 0; +} + +void iwl_pcie_ctxt_info_free_paging(struct iwl_trans *trans) +{ + struct iwl_self_init_dram *dram = &trans->init_dram; + int i; + + if (!dram->paging) { + WARN_ON(dram->paging_cnt); + return; + } + + /* free paging*/ + for (i = 0; i < dram->paging_cnt; i++) + dma_free_coherent(trans->dev, dram->paging[i].size, + dram->paging[i].block, + dram->paging[i].physical); + + kfree(dram->paging); + dram->paging_cnt = 0; + dram->paging = NULL; +} + +int iwl_pcie_init_fw_sec(struct iwl_trans *trans, + const struct fw_img *fw, + struct iwl_context_info_dram *ctxt_dram) +{ + struct iwl_self_init_dram *dram = &trans->init_dram; + int i, ret, lmac_cnt, umac_cnt, paging_cnt; + + if (WARN(dram->paging, + "paging shouldn't already be initialized (%d pages)\n", + dram->paging_cnt)) + iwl_pcie_ctxt_info_free_paging(trans); + + lmac_cnt = iwl_pcie_get_num_sections(fw, 0); + /* add 1 due to separator */ + umac_cnt = iwl_pcie_get_num_sections(fw, lmac_cnt + 1); + /* add 2 due to separators */ + paging_cnt = iwl_pcie_get_num_sections(fw, lmac_cnt + umac_cnt + 2); + + dram->fw = kcalloc(umac_cnt + lmac_cnt, sizeof(*dram->fw), GFP_KERNEL); + if (!dram->fw) + return -ENOMEM; + dram->paging = kcalloc(paging_cnt, sizeof(*dram->paging), GFP_KERNEL); + if (!dram->paging) + return -ENOMEM; + + /* initialize lmac sections */ + for (i = 0; i < lmac_cnt; i++) { + ret = iwl_pcie_ctxt_info_alloc_dma(trans, fw->sec[i].data, + fw->sec[i].len, + &dram->fw[dram->fw_cnt]); + if (ret) + return ret; + ctxt_dram->lmac_img[i] = + cpu_to_le64(dram->fw[dram->fw_cnt].physical); + dram->fw_cnt++; + } + + /* initialize umac sections */ + for (i = 0; i < umac_cnt; i++) { + /* access FW with +1 to make up for lmac separator */ + ret = iwl_pcie_ctxt_info_alloc_dma(trans, + fw->sec[dram->fw_cnt + 1].data, + fw->sec[dram->fw_cnt + 1].len, + &dram->fw[dram->fw_cnt]); + if (ret) + return ret; + ctxt_dram->umac_img[i] = + cpu_to_le64(dram->fw[dram->fw_cnt].physical); + dram->fw_cnt++; + } + + /* + * Initialize paging. + * Paging memory isn't stored in dram->fw as the umac and lmac - it is + * stored separately. + * This is since the timing of its release is different - + * while fw memory can be released on alive, the paging memory can be + * freed only when the device goes down. + * Given that, the logic here in accessing the fw image is a bit + * different - fw_cnt isn't changing so loop counter is added to it. + */ + for (i = 0; i < paging_cnt; i++) { + /* access FW with +2 to make up for lmac & umac separators */ + int fw_idx = dram->fw_cnt + i + 2; + + ret = iwl_pcie_ctxt_info_alloc_dma(trans, fw->sec[fw_idx].data, + fw->sec[fw_idx].len, + &dram->paging[i]); + if (ret) + return ret; + + ctxt_dram->virtual_img[i] = + cpu_to_le64(dram->paging[i].physical); + dram->paging_cnt++; + } + + return 0; +} + +int iwl_pcie_ctxt_info_init(struct iwl_trans *trans, + const struct fw_img *fw) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_context_info *ctxt_info; + struct iwl_context_info_rbd_cfg *rx_cfg; + u32 control_flags = 0, rb_size; + dma_addr_t phys; + int ret; + + ctxt_info = iwl_pcie_ctxt_info_dma_alloc_coherent(trans, + sizeof(*ctxt_info), + &phys); + if (!ctxt_info) + return -ENOMEM; + + trans_pcie->ctxt_info_dma_addr = phys; + + ctxt_info->version.version = 0; + ctxt_info->version.mac_id = + cpu_to_le16((u16)iwl_read32(trans, CSR_HW_REV)); + /* size is in DWs */ + ctxt_info->version.size = cpu_to_le16(sizeof(*ctxt_info) / 4); + + switch (trans_pcie->rx_buf_size) { + case IWL_AMSDU_2K: + rb_size = IWL_CTXT_INFO_RB_SIZE_2K; + break; + case IWL_AMSDU_4K: + rb_size = IWL_CTXT_INFO_RB_SIZE_4K; + break; + case IWL_AMSDU_8K: + rb_size = IWL_CTXT_INFO_RB_SIZE_8K; + break; + case IWL_AMSDU_12K: + rb_size = IWL_CTXT_INFO_RB_SIZE_16K; + break; + default: + WARN_ON(1); + rb_size = IWL_CTXT_INFO_RB_SIZE_4K; + } + + WARN_ON(RX_QUEUE_CB_SIZE(trans->cfg->num_rbds) > 12); + control_flags = IWL_CTXT_INFO_TFD_FORMAT_LONG; + control_flags |= + u32_encode_bits(RX_QUEUE_CB_SIZE(trans->cfg->num_rbds), + IWL_CTXT_INFO_RB_CB_SIZE); + control_flags |= u32_encode_bits(rb_size, IWL_CTXT_INFO_RB_SIZE); + ctxt_info->control.control_flags = cpu_to_le32(control_flags); + + /* initialize RX default queue */ + rx_cfg = &ctxt_info->rbd_cfg; + rx_cfg->free_rbd_addr = cpu_to_le64(trans_pcie->rxq->bd_dma); + rx_cfg->used_rbd_addr = cpu_to_le64(trans_pcie->rxq->used_bd_dma); + rx_cfg->status_wr_ptr = cpu_to_le64(trans_pcie->rxq->rb_stts_dma); + + /* initialize TX command queue */ + ctxt_info->hcmd_cfg.cmd_queue_addr = + cpu_to_le64(trans->txqs.txq[trans->txqs.cmd.q_id]->dma_addr); + ctxt_info->hcmd_cfg.cmd_queue_size = + TFD_QUEUE_CB_SIZE(IWL_CMD_QUEUE_SIZE); + + /* allocate ucode sections in dram and set addresses */ + ret = iwl_pcie_init_fw_sec(trans, fw, &ctxt_info->dram); + if (ret) { + dma_free_coherent(trans->dev, sizeof(*trans_pcie->ctxt_info), + ctxt_info, trans_pcie->ctxt_info_dma_addr); + return ret; + } + + trans_pcie->ctxt_info = ctxt_info; + + iwl_enable_fw_load_int_ctx_info(trans); + + /* Configure debug, if exists */ + if (iwl_pcie_dbg_on(trans)) + iwl_pcie_apply_destination(trans); + + /* kick FW self load */ + iwl_write64(trans, CSR_CTXT_INFO_BA, trans_pcie->ctxt_info_dma_addr); + + /* Context info will be released upon alive or failure to get one */ + + return 0; +} + +void iwl_pcie_ctxt_info_free(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!trans_pcie->ctxt_info) + return; + + dma_free_coherent(trans->dev, sizeof(*trans_pcie->ctxt_info), + trans_pcie->ctxt_info, + trans_pcie->ctxt_info_dma_addr); + trans_pcie->ctxt_info_dma_addr = 0; + trans_pcie->ctxt_info = NULL; + + iwl_pcie_ctxt_info_free_fw_img(trans); +} diff --git a/sys/contrib/dev/iwlwifi/pcie/drv.c b/sys/contrib/dev/iwlwifi/pcie/drv.c new file mode 100644 index 00000000000..c574f041f09 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/pcie/drv.c @@ -0,0 +1,1678 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include "fw/acpi.h" + +#include "iwl-trans.h" +#include "iwl-drv.h" +#include "iwl-prph.h" +#include "internal.h" + +#define TRANS_CFG_MARKER BIT(0) +#define _IS_A(cfg, _struct) __builtin_types_compatible_p(typeof(cfg), \ + struct _struct) +extern int _invalid_type; +#define _TRANS_CFG_MARKER(cfg) \ + (__builtin_choose_expr(_IS_A(cfg, iwl_cfg_trans_params), \ + TRANS_CFG_MARKER, \ + __builtin_choose_expr(_IS_A(cfg, iwl_cfg), 0, _invalid_type))) +#define _ASSIGN_CFG(cfg) (_TRANS_CFG_MARKER(cfg) + (kernel_ulong_t)&(cfg)) + +#define IWL_PCI_DEVICE(dev, subdev, cfg) \ + .vendor = PCI_VENDOR_ID_INTEL, .device = (dev), \ + .subvendor = PCI_ANY_ID, .subdevice = (subdev), \ + .driver_data = _ASSIGN_CFG(cfg) + +/* Hardware specific file defines the PCI IDs table for that hardware module */ +static const struct pci_device_id iwl_hw_card_ids[] = { +#if IS_ENABLED(CONFIG_IWLDVM) + {IWL_PCI_DEVICE(0x4232, 0x1201, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1301, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1204, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1304, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1205, iwl5100_bgn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1305, iwl5100_bgn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1206, iwl5100_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1306, iwl5100_abg_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1221, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1321, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1224, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1324, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1225, iwl5100_bgn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1325, iwl5100_bgn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1226, iwl5100_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1326, iwl5100_abg_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1211, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1311, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1214, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1314, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1215, iwl5100_bgn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1315, iwl5100_bgn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1216, iwl5100_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1316, iwl5100_abg_cfg)}, /* Half Mini Card */ + +/* 5300 Series WiFi */ + {IWL_PCI_DEVICE(0x4235, 0x1021, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1121, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1024, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1124, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1001, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1101, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1004, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1104, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1011, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1111, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1014, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1114, iwl5300_agn_cfg)}, /* Half Mini Card */ + +/* 5350 Series WiFi/WiMax */ + {IWL_PCI_DEVICE(0x423A, 0x1001, iwl5350_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423A, 0x1021, iwl5350_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423B, 0x1011, iwl5350_agn_cfg)}, /* Mini Card */ + +/* 5150 Series Wifi/WiMax */ + {IWL_PCI_DEVICE(0x423C, 0x1201, iwl5150_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1301, iwl5150_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1206, iwl5150_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1306, iwl5150_abg_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1221, iwl5150_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1321, iwl5150_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1326, iwl5150_abg_cfg)}, /* Half Mini Card */ + + {IWL_PCI_DEVICE(0x423D, 0x1211, iwl5150_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423D, 0x1311, iwl5150_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423D, 0x1216, iwl5150_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423D, 0x1316, iwl5150_abg_cfg)}, /* Half Mini Card */ + +/* 6x00 Series */ + {IWL_PCI_DEVICE(0x422B, 0x1101, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422B, 0x1108, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422B, 0x1121, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422B, 0x1128, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1301, iwl6000i_2agn_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1306, iwl6000i_2abg_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1307, iwl6000i_2bg_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1321, iwl6000i_2agn_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1326, iwl6000i_2abg_cfg)}, + {IWL_PCI_DEVICE(0x4238, 0x1111, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x4238, 0x1118, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x4239, 0x1311, iwl6000i_2agn_cfg)}, + {IWL_PCI_DEVICE(0x4239, 0x1316, iwl6000i_2abg_cfg)}, + +/* 6x05 Series */ + {IWL_PCI_DEVICE(0x0082, 0x1301, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1306, iwl6005_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1307, iwl6005_2bg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1308, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1321, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1326, iwl6005_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1328, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0x1311, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0x1318, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0x1316, iwl6005_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0xC020, iwl6005_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0xC220, iwl6005_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0xC228, iwl6005_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x4820, iwl6005_2agn_d_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1304, iwl6005_2agn_mow1_cfg)},/* low 5GHz active */ + {IWL_PCI_DEVICE(0x0082, 0x1305, iwl6005_2agn_mow2_cfg)},/* high 5GHz active */ + +/* 6x30 Series */ + {IWL_PCI_DEVICE(0x008A, 0x5305, iwl1030_bgn_cfg)}, + {IWL_PCI_DEVICE(0x008A, 0x5307, iwl1030_bg_cfg)}, + {IWL_PCI_DEVICE(0x008A, 0x5325, iwl1030_bgn_cfg)}, + {IWL_PCI_DEVICE(0x008A, 0x5327, iwl1030_bg_cfg)}, + {IWL_PCI_DEVICE(0x008B, 0x5315, iwl1030_bgn_cfg)}, + {IWL_PCI_DEVICE(0x008B, 0x5317, iwl1030_bg_cfg)}, + {IWL_PCI_DEVICE(0x0090, 0x5211, iwl6030_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0090, 0x5215, iwl6030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0090, 0x5216, iwl6030_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5201, iwl6030_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5205, iwl6030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5206, iwl6030_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5207, iwl6030_2bg_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5221, iwl6030_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5225, iwl6030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5226, iwl6030_2abg_cfg)}, + +/* 6x50 WiFi/WiMax Series */ + {IWL_PCI_DEVICE(0x0087, 0x1301, iwl6050_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0087, 0x1306, iwl6050_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0087, 0x1321, iwl6050_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0087, 0x1326, iwl6050_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0089, 0x1311, iwl6050_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0089, 0x1316, iwl6050_2abg_cfg)}, + +/* 6150 WiFi/WiMax Series */ + {IWL_PCI_DEVICE(0x0885, 0x1305, iwl6150_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0885, 0x1307, iwl6150_bg_cfg)}, + {IWL_PCI_DEVICE(0x0885, 0x1325, iwl6150_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0885, 0x1327, iwl6150_bg_cfg)}, + {IWL_PCI_DEVICE(0x0886, 0x1315, iwl6150_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0886, 0x1317, iwl6150_bg_cfg)}, + +/* 1000 Series WiFi */ + {IWL_PCI_DEVICE(0x0083, 0x1205, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1305, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1225, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1325, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1215, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1315, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1206, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1306, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1226, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1326, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1216, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1316, iwl1000_bg_cfg)}, + +/* 100 Series WiFi */ + {IWL_PCI_DEVICE(0x08AE, 0x1005, iwl100_bgn_cfg)}, + {IWL_PCI_DEVICE(0x08AE, 0x1007, iwl100_bg_cfg)}, + {IWL_PCI_DEVICE(0x08AF, 0x1015, iwl100_bgn_cfg)}, + {IWL_PCI_DEVICE(0x08AF, 0x1017, iwl100_bg_cfg)}, + {IWL_PCI_DEVICE(0x08AE, 0x1025, iwl100_bgn_cfg)}, + {IWL_PCI_DEVICE(0x08AE, 0x1027, iwl100_bg_cfg)}, + +/* 130 Series WiFi */ + {IWL_PCI_DEVICE(0x0896, 0x5005, iwl130_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0896, 0x5007, iwl130_bg_cfg)}, + {IWL_PCI_DEVICE(0x0897, 0x5015, iwl130_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0897, 0x5017, iwl130_bg_cfg)}, + {IWL_PCI_DEVICE(0x0896, 0x5025, iwl130_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0896, 0x5027, iwl130_bg_cfg)}, + +/* 2x00 Series */ + {IWL_PCI_DEVICE(0x0890, 0x4022, iwl2000_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0891, 0x4222, iwl2000_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0890, 0x4422, iwl2000_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0890, 0x4822, iwl2000_2bgn_d_cfg)}, + +/* 2x30 Series */ + {IWL_PCI_DEVICE(0x0887, 0x4062, iwl2030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0888, 0x4262, iwl2030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0887, 0x4462, iwl2030_2bgn_cfg)}, + +/* 6x35 Series */ + {IWL_PCI_DEVICE(0x088E, 0x4060, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x406A, iwl6035_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x4260, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x426A, iwl6035_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x4460, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x446A, iwl6035_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x4860, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x5260, iwl6035_2agn_cfg)}, + +/* 105 Series */ + {IWL_PCI_DEVICE(0x0894, 0x0022, iwl105_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0895, 0x0222, iwl105_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0894, 0x0422, iwl105_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0894, 0x0822, iwl105_bgn_d_cfg)}, + +/* 135 Series */ + {IWL_PCI_DEVICE(0x0892, 0x0062, iwl135_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0893, 0x0262, iwl135_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0892, 0x0462, iwl135_bgn_cfg)}, +#endif /* CONFIG_IWLDVM */ + +#if IS_ENABLED(CONFIG_IWLMVM) +/* 7260 Series */ + {IWL_PCI_DEVICE(0x08B1, 0x4070, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4072, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4170, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4C60, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4C70, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4060, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x406A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4160, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4062, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4162, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4270, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4272, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4260, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x426A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4262, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4470, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4472, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4460, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x446A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4462, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4870, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x486E, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4A70, iwl7260_2ac_cfg_high_temp)}, + {IWL_PCI_DEVICE(0x08B1, 0x4A6E, iwl7260_2ac_cfg_high_temp)}, + {IWL_PCI_DEVICE(0x08B1, 0x4A6C, iwl7260_2ac_cfg_high_temp)}, + {IWL_PCI_DEVICE(0x08B1, 0x4570, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4560, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4370, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4360, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x5070, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x5072, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x5170, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x5770, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4020, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x402A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4220, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4420, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC070, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC072, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC170, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC060, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC06A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC160, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC062, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC162, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC770, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC760, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC270, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xCC70, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xCC60, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC272, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC260, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC26A, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC262, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC470, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC472, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC460, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC462, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC570, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC560, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC370, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC360, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC020, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC02A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC220, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC420, iwl7260_2n_cfg)}, + +/* 3160 Series */ + {IWL_PCI_DEVICE(0x08B3, 0x0070, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0072, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0170, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0172, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0060, iwl3160_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0062, iwl3160_n_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x0270, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x0272, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0470, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0472, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x0370, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8070, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8072, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8170, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8172, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8060, iwl3160_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8062, iwl3160_n_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x8270, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x8370, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x8272, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8470, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8570, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x1070, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x1170, iwl3160_2ac_cfg)}, + +/* 3165 Series */ + {IWL_PCI_DEVICE(0x3165, 0x4010, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x4012, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3166, 0x4212, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x4410, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x4510, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x4110, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3166, 0x4310, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3166, 0x4210, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x8010, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x8110, iwl3165_2ac_cfg)}, + +/* 3168 Series */ + {IWL_PCI_DEVICE(0x24FB, 0x2010, iwl3168_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FB, 0x2110, iwl3168_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FB, 0x2050, iwl3168_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FB, 0x2150, iwl3168_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FB, 0x0000, iwl3168_2ac_cfg)}, + +/* 7265 Series */ + {IWL_PCI_DEVICE(0x095A, 0x5010, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5110, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5100, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5310, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5302, iwl7265_n_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5210, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5C10, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5012, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5412, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5410, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5510, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5400, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x1010, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5000, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x500A, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5200, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5002, iwl7265_n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5102, iwl7265_n_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5202, iwl7265_n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9010, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9012, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x900A, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9110, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9112, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x9210, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x9200, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9510, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x9310, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9410, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5020, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x502A, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5420, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5090, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5190, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5590, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5290, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5490, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5F10, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5212, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x520A, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9000, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9400, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9E10, iwl7265_2ac_cfg)}, + +/* 8000 Series */ + {IWL_PCI_DEVICE(0x24F3, 0x0010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x10B0, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0130, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1130, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0132, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1132, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x01F0, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0012, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1012, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0250, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0150, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1150, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0x0030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0x1030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xC010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xC110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xD010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xC050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xD050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xD0B0, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xB0B0, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0x8030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0x9030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0xC030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0xD030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8130, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9130, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8132, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9132, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8150, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9150, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0004, iwl8260_2n_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0044, iwl8260_2n_cfg)}, + {IWL_PCI_DEVICE(0x24F5, 0x0010, iwl4165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F6, 0x0030, iwl4165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0810, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0910, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0850, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0950, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0930, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0000, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x4010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0010, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0110, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1110, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1130, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0130, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1010, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x10D0, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0050, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0150, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x9010, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x8110, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x8050, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x8010, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0810, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x9110, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x8130, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0910, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0930, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0950, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0850, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1014, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x3E02, iwl8275_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x3E01, iwl8275_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1012, iwl8275_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0012, iwl8275_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0014, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x9074, iwl8265_2ac_cfg)}, + +/* 9000 Series */ + {IWL_PCI_DEVICE(0x2526, PCI_ANY_ID, iwl9000_trans_cfg)}, + {IWL_PCI_DEVICE(0x271B, PCI_ANY_ID, iwl9000_trans_cfg)}, + {IWL_PCI_DEVICE(0x271C, PCI_ANY_ID, iwl9000_trans_cfg)}, + {IWL_PCI_DEVICE(0x30DC, PCI_ANY_ID, iwl9560_long_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x31DC, PCI_ANY_ID, iwl9560_shared_clk_trans_cfg)}, + {IWL_PCI_DEVICE(0x9DF0, PCI_ANY_ID, iwl9560_trans_cfg)}, + {IWL_PCI_DEVICE(0xA370, PCI_ANY_ID, iwl9560_trans_cfg)}, + +/* Qu devices */ + {IWL_PCI_DEVICE(0x02F0, PCI_ANY_ID, iwl_qu_trans_cfg)}, + {IWL_PCI_DEVICE(0x06F0, PCI_ANY_ID, iwl_qu_trans_cfg)}, + + {IWL_PCI_DEVICE(0x34F0, PCI_ANY_ID, iwl_qu_medium_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x3DF0, PCI_ANY_ID, iwl_qu_medium_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x4DF0, PCI_ANY_ID, iwl_qu_medium_latency_trans_cfg)}, + + {IWL_PCI_DEVICE(0x43F0, PCI_ANY_ID, iwl_qu_long_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0xA0F0, PCI_ANY_ID, iwl_qu_long_latency_trans_cfg)}, + + {IWL_PCI_DEVICE(0x2720, PCI_ANY_ID, iwl_qnj_trans_cfg)}, + + {IWL_PCI_DEVICE(0x2723, PCI_ANY_ID, iwl_ax200_trans_cfg)}, + +/* So devices */ + {IWL_PCI_DEVICE(0x2725, PCI_ANY_ID, iwl_so_trans_cfg)}, + {IWL_PCI_DEVICE(0x2726, PCI_ANY_ID, iwl_snj_trans_cfg)}, + {IWL_PCI_DEVICE(0x7A70, PCI_ANY_ID, iwl_so_long_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x7AF0, PCI_ANY_ID, iwl_so_trans_cfg)}, + {IWL_PCI_DEVICE(0x51F0, PCI_ANY_ID, iwl_so_long_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x54F0, PCI_ANY_ID, iwl_so_long_latency_trans_cfg)}, + +/* Ma devices */ + {IWL_PCI_DEVICE(0x2729, PCI_ANY_ID, iwl_ma_trans_cfg)}, + {IWL_PCI_DEVICE(0x7E40, PCI_ANY_ID, iwl_ma_trans_cfg)}, + {IWL_PCI_DEVICE(0x7F70, PCI_ANY_ID, iwl_ma_trans_cfg)}, + +/* Bz devices */ + {IWL_PCI_DEVICE(0x2727, PCI_ANY_ID, iwl_bz_trans_cfg)}, +#endif /* CONFIG_IWLMVM */ + + {0} +}; +MODULE_DEVICE_TABLE(pci, iwl_hw_card_ids); + +#define _IWL_DEV_INFO(_device, _subdevice, _mac_type, _mac_step, _rf_type, \ + _rf_id, _no_160, _cores, _cdb, _cfg, _name) \ + { .device = (_device), .subdevice = (_subdevice), .cfg = &(_cfg), \ + .name = _name, .mac_type = _mac_type, .rf_type = _rf_type, \ + .no_160 = _no_160, .cores = _cores, .rf_id = _rf_id, \ + .mac_step = _mac_step, .cdb = _cdb } + +#define IWL_DEV_INFO(_device, _subdevice, _cfg, _name) \ + _IWL_DEV_INFO(_device, _subdevice, IWL_CFG_ANY, IWL_CFG_ANY, \ + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_ANY, \ + IWL_CFG_ANY, _cfg, _name) + +static const struct iwl_dev_info iwl_dev_info_table[] = { +#if IS_ENABLED(CONFIG_IWLMVM) +/* 9000 */ + IWL_DEV_INFO(0x2526, 0x1550, iwl9260_2ac_cfg, iwl9260_killer_1550_name), + IWL_DEV_INFO(0x2526, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_name), + IWL_DEV_INFO(0x2526, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0x30DC, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_name), + IWL_DEV_INFO(0x30DC, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0x31DC, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_name), + IWL_DEV_INFO(0x31DC, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0xA370, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_name), + IWL_DEV_INFO(0xA370, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0x54F0, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_160_name), + IWL_DEV_INFO(0x54F0, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0x51F0, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_160_name), + IWL_DEV_INFO(0x51F0, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_160_name), + IWL_DEV_INFO(0x51F0, 0x1691, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x51F0, 0x1692, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690i_name), + IWL_DEV_INFO(0x54F0, 0x1691, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x54F0, 0x1692, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690i_name), + IWL_DEV_INFO(0x7A70, 0x1691, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x7A70, 0x1692, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690i_name), + + IWL_DEV_INFO(0x271C, 0x0214, iwl9260_2ac_cfg, iwl9260_1_name), + IWL_DEV_INFO(0x7E40, 0x1691, iwl_cfg_ma_a0_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x7E40, 0x1692, iwl_cfg_ma_a0_gf4_a0, iwl_ax411_killer_1690i_name), + +/* AX200 */ + IWL_DEV_INFO(0x2723, IWL_CFG_ANY, iwl_ax200_cfg_cc, iwl_ax200_name), + IWL_DEV_INFO(0x2723, 0x1653, iwl_ax200_cfg_cc, iwl_ax200_killer_1650w_name), + IWL_DEV_INFO(0x2723, 0x1654, iwl_ax200_cfg_cc, iwl_ax200_killer_1650x_name), + + /* Qu with Hr */ + IWL_DEV_INFO(0x43F0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, iwl_ax201_killer_1650s_name), + IWL_DEV_INFO(0x43F0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, iwl_ax201_killer_1650i_name), + IWL_DEV_INFO(0x43F0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x0A10, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0xA0F0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0xA0F0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x6074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x0070, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x0074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x6074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x0078, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x007C, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x0310, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x1651, iwl_ax1650s_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x1652, iwl_ax1650i_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x2074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x4070, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x0070, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x0074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x0078, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x007C, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x0310, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x1651, iwl_ax1650s_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x1652, iwl_ax1650i_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x2074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x4070, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x0310, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x34F0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x34F0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + + IWL_DEV_INFO(0x3DF0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x0310, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x3DF0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x3DF0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + + IWL_DEV_INFO(0x4DF0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x0310, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x4DF0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x4DF0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x6074, iwl_ax201_cfg_qu_hr, NULL), + + /* So with HR */ + IWL_DEV_INFO(0x2725, 0x0090, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x2020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0024, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0310, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0510, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0A10, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0xE020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0xE024, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x4020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x6020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x6024, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x1673, iwlax210_2ax_cfg_ty_gf_a0, iwl_ax210_killer_1675w_name), + IWL_DEV_INFO(0x2725, 0x1674, iwlax210_2ax_cfg_ty_gf_a0, iwl_ax210_killer_1675x_name), + IWL_DEV_INFO(0x7A70, 0x0090, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x0098, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x00B0, iwlax411_2ax_cfg_so_gf4_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x0310, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x0510, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x0A10, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7AF0, 0x0090, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x0098, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x00B0, iwlax411_2ax_cfg_so_gf4_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x0310, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x0510, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x0A10, iwlax211_2ax_cfg_so_gf_a0, NULL), + + /* So with JF */ + IWL_DEV_INFO(0x7A70, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_160_name), + IWL_DEV_INFO(0x7A70, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_160_name), + IWL_DEV_INFO(0x7AF0, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_160_name), + IWL_DEV_INFO(0x7AF0, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_160_name), + + /* SnJ with HR */ + IWL_DEV_INFO(0x2725, 0x00B0, iwlax411_2ax_cfg_sosnj_gf4_a0, NULL), + IWL_DEV_INFO(0x2726, 0x0090, iwlax211_cfg_snj_gf_a0, NULL), + IWL_DEV_INFO(0x2726, 0x0098, iwlax211_cfg_snj_gf_a0, NULL), + IWL_DEV_INFO(0x2726, 0x00B0, iwlax411_2ax_cfg_sosnj_gf4_a0, NULL), + IWL_DEV_INFO(0x2726, 0x00B4, iwlax411_2ax_cfg_sosnj_gf4_a0, NULL), + IWL_DEV_INFO(0x2726, 0x0510, iwlax211_cfg_snj_gf_a0, NULL), + IWL_DEV_INFO(0x2726, 0x1651, iwl_cfg_snj_hr_b0, iwl_ax201_killer_1650s_name), + IWL_DEV_INFO(0x2726, 0x1652, iwl_cfg_snj_hr_b0, iwl_ax201_killer_1650i_name), + IWL_DEV_INFO(0x2726, 0x1671, iwlax211_cfg_snj_gf_a0, iwl_ax211_killer_1675s_name), + IWL_DEV_INFO(0x2726, 0x1672, iwlax211_cfg_snj_gf_a0, iwl_ax211_killer_1675i_name), + IWL_DEV_INFO(0x2726, 0x1691, iwlax411_2ax_cfg_sosnj_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x2726, 0x1692, iwlax411_2ax_cfg_sosnj_gf4_a0, iwl_ax411_killer_1690i_name), + IWL_DEV_INFO(0x7F70, 0x1691, iwlax411_2ax_cfg_sosnj_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x7F70, 0x1692, iwlax411_2ax_cfg_sosnj_gf4_a0, iwl_ax411_killer_1690i_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_2ac_cfg_soc, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_2ac_cfg_soc, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_2ac_cfg_soc, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_2ac_cfg_soc, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_2ac_cfg_soc, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_2ac_cfg_soc, iwl9560_name), + + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9461_160_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9461_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9462_160_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9462_name), + + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_CORES_BT_GNSS, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9270_160_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_CORES_BT_GNSS, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9270_name), + + _IWL_DEV_INFO(0x271B, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH1, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9162_160_name), + _IWL_DEV_INFO(0x271B, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH1, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9162_name), + + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9260_160_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9260_2ac_cfg, iwl9260_name), + +/* Qu with Jf */ + /* Qu B step */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_b0_jf_b0_cfg, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_b0_jf_b0_cfg, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_b0_jf_b0_cfg, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_b0_jf_b0_cfg, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_b0_jf_b0_cfg, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_b0_jf_b0_cfg, iwl9560_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1551, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_b0_jf_b0_cfg, iwl9560_killer_1550s_name), + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1552, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_b0_jf_b0_cfg, iwl9560_killer_1550i_name), + + /* Qu C step */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_c0_jf_b0_cfg, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_c0_jf_b0_cfg, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_c0_jf_b0_cfg, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_c0_jf_b0_cfg, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_c0_jf_b0_cfg, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_c0_jf_b0_cfg, iwl9560_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1551, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_c0_jf_b0_cfg, iwl9560_killer_1550s_name), + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1552, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qu_c0_jf_b0_cfg, iwl9560_killer_1550i_name), + + /* QuZ */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_quz_a0_jf_b0_cfg, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_quz_a0_jf_b0_cfg, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_quz_a0_jf_b0_cfg, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_quz_a0_jf_b0_cfg, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_quz_a0_jf_b0_cfg, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_quz_a0_jf_b0_cfg, iwl9560_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1551, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_quz_a0_jf_b0_cfg, iwl9560_killer_1550s_name), + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1552, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_quz_a0_jf_b0_cfg, iwl9560_killer_1550i_name), + + /* QnJ */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qnj_b0_jf_b0_cfg, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qnj_b0_jf_b0_cfg, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qnj_b0_jf_b0_cfg, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qnj_b0_jf_b0_cfg, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qnj_b0_jf_b0_cfg, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qnj_b0_jf_b0_cfg, iwl9560_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1551, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qnj_b0_jf_b0_cfg, iwl9560_killer_1550s_name), + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1552, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl9560_qnj_b0_jf_b0_cfg, iwl9560_killer_1550i_name), + +/* Qu with Hr */ + /* Qu B step */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_qu_b0_hr1_b0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_qu_b0_hr_b0, iwl_ax203_name), + + /* Qu C step */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_qu_c0_hr1_b0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_qu_c0_hr_b0, iwl_ax203_name), + + /* QuZ */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_quz_a0_hr1_b0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, SILICON_B_STEP, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_quz_a0_hr_b0, iwl_ax203_name), + +/* QnJ with Hr */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_qnj_b0_hr_b0_cfg, iwl_ax201_name), + +/* SnJ with Jf */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl_cfg_snj_a0_jf_b0, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl_cfg_snj_a0_jf_b0, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl_cfg_snj_a0_jf_b0, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl_cfg_snj_a0_jf_b0, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl_cfg_snj_a0_jf_b0, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwl_cfg_snj_a0_jf_b0, iwl9560_name), + +/* SnJ with Hr */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_snj_hr_b0, iwl_ax101_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_snj_hr_b0, iwl_ax201_name), + +/* Ma */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_ma_a0_hr_b0, iwl_ax201_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_ma_a0_gf_a0, iwl_ax211_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_CDB, + iwl_cfg_ma_a0_gf4_a0, iwl_ax211_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MR, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_ma_a0_mr_a0, iwl_ax221_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_ma_a0_fm_a0, iwl_ax231_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MR, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_snj_a0_mr_a0, iwl_ax221_name), + +/* So with Hr */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_so_a0_hr_a0, iwl_ax203_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_so_a0_hr_a0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_so_a0_hr_a0, iwl_ax201_name), + +/* So-F with Hr */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_so_a0_hr_a0, iwl_ax203_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_so_a0_hr_a0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_so_a0_hr_a0, iwl_ax201_name), + +/* So-F with Gf */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_name), + +/* Bz */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_bz_a0_hr_b0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_bz_a0_gf_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_CDB, + iwl_cfg_bz_a0_gf4_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MR, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_bz_a0_mr_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_bz_a0_fm_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_GL, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwl_cfg_gl_a0_fm_a0, iwl_bz_name), + +/* SoF with JF2 */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_name), + +/* SoF with JF */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_name), + +/* SoF with JF2 */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_name), + +/* SoF with JF */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_name), + +/* So with GF */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, + iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_name), + +/* So with JF2 */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_name), + +/* So with JF */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_name) + +#endif /* CONFIG_IWLMVM */ +}; + +/* + * In case that there is no OTP on the NIC, get the rf id and cdb info + * from the prph registers. + */ +static int get_crf_id(struct iwl_trans *iwl_trans) +{ + int ret = 0; + u32 wfpm_ctrl_addr; + u32 wfpm_otp_cfg_addr; + u32 sd_reg_ver_addr; + u32 cdb = 0; + u32 val; + + if (iwl_trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + wfpm_ctrl_addr = WFPM_CTRL_REG_GEN2; + wfpm_otp_cfg_addr = WFPM_OTP_CFG1_ADDR_GEN2; + sd_reg_ver_addr = SD_REG_VER_GEN2; + /* Qu/Pu families have other addresses */ + } else { + wfpm_ctrl_addr = WFPM_CTRL_REG; + wfpm_otp_cfg_addr = WFPM_OTP_CFG1_ADDR; + sd_reg_ver_addr = SD_REG_VER; + } + + if (!iwl_trans_grab_nic_access(iwl_trans)) { + IWL_ERR(iwl_trans, "Failed to grab nic access before reading crf id\n"); + ret = -EIO; + goto out; + } + + /* Enable access to peripheral registers */ + val = iwl_read_umac_prph_no_grab(iwl_trans, wfpm_ctrl_addr); + val |= ENABLE_WFPM; + iwl_write_umac_prph_no_grab(iwl_trans, wfpm_ctrl_addr, val); + + /* Read crf info */ + val = iwl_read_prph_no_grab(iwl_trans, sd_reg_ver_addr); + + /* Read cdb info (also contains the jacket info if needed in the future */ + cdb = iwl_read_umac_prph_no_grab(iwl_trans, wfpm_otp_cfg_addr); + + /* Map between crf id to rf id */ + switch (REG_CRF_ID_TYPE(val)) { + case REG_CRF_ID_TYPE_JF_1: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_JF1 << 12); + break; + case REG_CRF_ID_TYPE_JF_2: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_JF2 << 12); + break; + case REG_CRF_ID_TYPE_HR_NONE_CDB: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_HR1 << 12); + break; + case REG_CRF_ID_TYPE_HR_CDB: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_HR2 << 12); + break; + case REG_CRF_ID_TYPE_GF: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_GF << 12); + break; + case REG_CRF_ID_TYPE_MR: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_MR << 12); + break; + case REG_CRF_ID_TYPE_FM: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_FM << 12); + break; + default: + ret = -EIO; + IWL_ERR(iwl_trans, + "Can find a correct rfid for crf id 0x%x\n", + REG_CRF_ID_TYPE(val)); + goto out_release; + + } + + /* Set CDB capabilities */ + if (cdb & BIT(4)) { + iwl_trans->hw_rf_id += BIT(28); + IWL_INFO(iwl_trans, "Adding cdb to rf id\n"); + } + + IWL_INFO(iwl_trans, "Detected RF 0x%x from crf id 0x%x\n", + iwl_trans->hw_rf_id, REG_CRF_ID_TYPE(val)); + +out_release: + iwl_trans_release_nic_access(iwl_trans); + +out: + return ret; +} + +/* PCI registers */ +#define PCI_CFG_RETRY_TIMEOUT 0x041 + +static const struct iwl_dev_info * +iwl_pci_find_dev_info(u16 device, u16 subsystem_device, + u16 mac_type, u8 mac_step, + u16 rf_type, u8 cdb, u8 rf_id, u8 no_160, u8 cores) +{ + int i; + + for (i = ARRAY_SIZE(iwl_dev_info_table) - 1; i >= 0; i--) { + const struct iwl_dev_info *dev_info = &iwl_dev_info_table[i]; + + if (dev_info->device != (u16)IWL_CFG_ANY && + dev_info->device != device) + continue; + + if (dev_info->subdevice != (u16)IWL_CFG_ANY && + dev_info->subdevice != subsystem_device) + continue; + + if (dev_info->mac_type != (u16)IWL_CFG_ANY && + dev_info->mac_type != mac_type) + continue; + + if (dev_info->mac_step != (u8)IWL_CFG_ANY && + dev_info->mac_step != mac_step) + continue; + + if (dev_info->rf_type != (u16)IWL_CFG_ANY && + dev_info->rf_type != rf_type) + continue; + + if (dev_info->cdb != (u8)IWL_CFG_ANY && + dev_info->cdb != cdb) + continue; + + if (dev_info->rf_id != (u8)IWL_CFG_ANY && + dev_info->rf_id != rf_id) + continue; + + if (dev_info->no_160 != (u8)IWL_CFG_ANY && + dev_info->no_160 != no_160) + continue; + + if (dev_info->cores != (u8)IWL_CFG_ANY && + dev_info->cores != cores) + continue; + + return dev_info; + } + + return NULL; +} + +static int iwl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + const struct iwl_cfg_trans_params *trans; + const struct iwl_cfg *cfg_7265d __maybe_unused = NULL; + const struct iwl_dev_info *dev_info; + struct iwl_trans *iwl_trans; + struct iwl_trans_pcie *trans_pcie; + int ret; + const struct iwl_cfg *cfg; + + trans = (void *)(ent->driver_data & ~TRANS_CFG_MARKER); + + /* + * This is needed for backwards compatibility with the old + * tables, so we don't need to change all the config structs + * at the same time. The cfg is used to compare with the old + * full cfg structs. + */ + cfg = (void *)(ent->driver_data & ~TRANS_CFG_MARKER); + + /* make sure trans is the first element in iwl_cfg */ + BUILD_BUG_ON(offsetof(struct iwl_cfg, trans)); + + iwl_trans = iwl_trans_pcie_alloc(pdev, ent, trans); + if (IS_ERR(iwl_trans)) + return PTR_ERR(iwl_trans); + + trans_pcie = IWL_TRANS_GET_PCIE_TRANS(iwl_trans); + + /* + * Let's try to grab NIC access early here. Sometimes, NICs may + * fail to initialize, and if that happens it's better if we see + * issues early on (and can reprobe, per the logic inside), than + * first trying to load the firmware etc. and potentially only + * detecting any problems when the first interface is brought up. + */ + ret = iwl_finish_nic_init(iwl_trans); + if (ret) + goto out_free_trans; + if (iwl_trans_grab_nic_access(iwl_trans)) { + /* all good */ + iwl_trans_release_nic_access(iwl_trans); + } else { + ret = -EIO; + goto out_free_trans; + } + + iwl_trans->hw_rf_id = iwl_read32(iwl_trans, CSR_HW_RF_ID); + + /* + * The RF_ID is set to zero in blank OTP so read version to + * extract the RF_ID. + * This is relevant only for family 9000 and up. + */ + if (iwl_trans->trans_cfg->rf_id && + iwl_trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_9000 && + !CSR_HW_RFID_TYPE(iwl_trans->hw_rf_id) && get_crf_id(iwl_trans)) + goto out_free_trans; + + dev_info = iwl_pci_find_dev_info(pdev->device, pdev->subsystem_device, + CSR_HW_REV_TYPE(iwl_trans->hw_rev), + CSR_HW_REV_STEP(iwl_trans->hw_rev), + CSR_HW_RFID_TYPE(iwl_trans->hw_rf_id), + CSR_HW_RFID_IS_CDB(iwl_trans->hw_rf_id), + IWL_SUBDEVICE_RF_ID(pdev->subsystem_device), + IWL_SUBDEVICE_NO_160(pdev->subsystem_device), + IWL_SUBDEVICE_CORES(pdev->subsystem_device)); + + if (dev_info) { + iwl_trans->cfg = dev_info->cfg; + iwl_trans->name = dev_info->name; + } + +#if IS_ENABLED(CONFIG_IWLMVM) + + /* + * Workaround for problematic SnJ device: sometimes when + * certain RF modules are connected to SnJ, the device ID + * changes to QnJ's ID. So we are using QnJ's trans_cfg until + * here. But if we detect that the MAC type is actually SnJ, + * we should switch to it here to avoid problems later. + */ + if (CSR_HW_REV_TYPE(iwl_trans->hw_rev) == IWL_CFG_MAC_TYPE_SNJ) + iwl_trans->trans_cfg = &iwl_so_trans_cfg; + + /* + * special-case 7265D, it has the same PCI IDs. + * + * Note that because we already pass the cfg to the transport above, + * all the parameters that the transport uses must, until that is + * changed, be identical to the ones in the 7265D configuration. + */ + if (cfg == &iwl7265_2ac_cfg) + cfg_7265d = &iwl7265d_2ac_cfg; + else if (cfg == &iwl7265_2n_cfg) + cfg_7265d = &iwl7265d_2n_cfg; + else if (cfg == &iwl7265_n_cfg) + cfg_7265d = &iwl7265d_n_cfg; + if (cfg_7265d && + (iwl_trans->hw_rev & CSR_HW_REV_TYPE_MSK) == CSR_HW_REV_TYPE_7265D) + iwl_trans->cfg = cfg_7265d; + + if (cfg == &iwlax210_2ax_cfg_so_hr_a0) { + if (iwl_trans->hw_rev == CSR_HW_REV_TYPE_TY) { + iwl_trans->cfg = &iwlax210_2ax_cfg_ty_gf_a0; + } else if (CSR_HW_RFID_TYPE(iwl_trans->hw_rf_id) == + CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_JF)) { + iwl_trans->cfg = &iwlax210_2ax_cfg_so_jf_b0; + } else if (CSR_HW_RFID_TYPE(iwl_trans->hw_rf_id) == + CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_GF)) { + iwl_trans->cfg = &iwlax211_2ax_cfg_so_gf_a0; + } else if (CSR_HW_RFID_TYPE(iwl_trans->hw_rf_id) == + CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_GF4)) { + iwl_trans->cfg = &iwlax411_2ax_cfg_so_gf4_a0; + } + } + + /* + * This is a hack to switch from Qu B0 to Qu C0. We need to + * do this for all cfgs that use Qu B0, except for those using + * Jf, which have already been moved to the new table. The + * rest must be removed once we convert Qu with Hr as well. + */ + if (iwl_trans->hw_rev == CSR_HW_REV_TYPE_QU_C0) { + if (iwl_trans->cfg == &iwl_ax201_cfg_qu_hr) + iwl_trans->cfg = &iwl_ax201_cfg_qu_c0_hr_b0; + else if (iwl_trans->cfg == &killer1650s_2ax_cfg_qu_b0_hr_b0) + iwl_trans->cfg = &killer1650s_2ax_cfg_qu_c0_hr_b0; + else if (iwl_trans->cfg == &killer1650i_2ax_cfg_qu_b0_hr_b0) + iwl_trans->cfg = &killer1650i_2ax_cfg_qu_c0_hr_b0; + } + + /* same thing for QuZ... */ + if (iwl_trans->hw_rev == CSR_HW_REV_TYPE_QUZ) { + if (iwl_trans->cfg == &iwl_ax201_cfg_qu_hr) + iwl_trans->cfg = &iwl_ax201_cfg_quz_hr; + else if (iwl_trans->cfg == &killer1650s_2ax_cfg_qu_b0_hr_b0) + iwl_trans->cfg = &iwl_ax1650s_cfg_quz_hr; + else if (iwl_trans->cfg == &killer1650i_2ax_cfg_qu_b0_hr_b0) + iwl_trans->cfg = &iwl_ax1650i_cfg_quz_hr; + } + +#endif + /* + * If we didn't set the cfg yet, the PCI ID table entry should have + * been a full config - if yes, use it, otherwise fail. + */ + if (!iwl_trans->cfg) { + if (ent->driver_data & TRANS_CFG_MARKER) { + pr_err("No config found for PCI dev %04x/%04x, rev=0x%x, rfid=0x%x\n", + pdev->device, pdev->subsystem_device, + iwl_trans->hw_rev, iwl_trans->hw_rf_id); + ret = -EINVAL; + goto out_free_trans; + } + iwl_trans->cfg = cfg; + } + + /* if we don't have a name yet, copy name from the old cfg */ + if (!iwl_trans->name) + iwl_trans->name = iwl_trans->cfg->name; + + if (iwl_trans->trans_cfg->mq_rx_supported) { + if (WARN_ON(!iwl_trans->cfg->num_rbds)) { + ret = -EINVAL; + goto out_free_trans; + } + trans_pcie->num_rx_bufs = iwl_trans->cfg->num_rbds; + } else { + trans_pcie->num_rx_bufs = RX_QUEUE_SIZE; + } + + ret = iwl_trans_init(iwl_trans); + if (ret) + goto out_free_trans; + + pci_set_drvdata(pdev, iwl_trans); + iwl_trans->drv = iwl_drv_start(iwl_trans); + + if (IS_ERR(iwl_trans->drv)) { + ret = PTR_ERR(iwl_trans->drv); + goto out_free_trans; + } + + /* register transport layer debugfs here */ + iwl_trans_pcie_dbgfs_register(iwl_trans); + + return 0; + +out_free_trans: + iwl_trans_pcie_free(iwl_trans); + return ret; +} + +static void iwl_pci_remove(struct pci_dev *pdev) +{ + struct iwl_trans *trans = pci_get_drvdata(pdev); + + iwl_drv_stop(trans->drv); + + iwl_trans_pcie_free(trans); +} + +#ifdef CONFIG_PM_SLEEP + +static int iwl_pci_suspend(struct device *device) +{ + /* Before you put code here, think about WoWLAN. You cannot check here + * whether WoWLAN is enabled or not, and your code will run even if + * WoWLAN is enabled - don't kill the NIC, someone may need it in Sx. + */ + + return 0; +} + +static int iwl_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct iwl_trans *trans = pci_get_drvdata(pdev); + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + /* Before you put code here, think about WoWLAN. You cannot check here + * whether WoWLAN is enabled or not, and your code will run even if + * WoWLAN is enabled - the NIC may be alive. + */ + + /* + * We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state. + */ + pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00); + + if (!trans->op_mode) + return 0; + + /* In WOWLAN, let iwl_trans_pcie_d3_resume do the rest of the work */ + if (test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + return 0; + + /* reconfigure the MSI-X mapping to get the correct IRQ for rfkill */ + iwl_pcie_conf_msix_hw(trans_pcie); + + /* + * Enable rfkill interrupt (in order to keep track of the rfkill + * status). Must be locked to avoid processing a possible rfkill + * interrupt while in iwl_pcie_check_hw_rf_kill(). + */ + mutex_lock(&trans_pcie->mutex); + iwl_enable_rfkill_int(trans); + iwl_pcie_check_hw_rf_kill(trans); + mutex_unlock(&trans_pcie->mutex); + + return 0; +} + +static const struct dev_pm_ops iwl_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(iwl_pci_suspend, + iwl_pci_resume) +}; + +#define IWL_PM_OPS (&iwl_dev_pm_ops) + +#else /* CONFIG_PM_SLEEP */ + +#define IWL_PM_OPS NULL + +#endif /* CONFIG_PM_SLEEP */ + +static struct pci_driver iwl_pci_driver = { + .name = DRV_NAME, + .id_table = iwl_hw_card_ids, + .probe = iwl_pci_probe, + .remove = iwl_pci_remove, + .driver.pm = IWL_PM_OPS, +}; + +int __must_check iwl_pci_register_driver(void) +{ + int ret; + ret = pci_register_driver(&iwl_pci_driver); + if (ret) + pr_err("Unable to initialize PCI module\n"); + + return ret; +} + +void iwl_pci_unregister_driver(void) +{ + pci_unregister_driver(&iwl_pci_driver); +} diff --git a/sys/contrib/dev/iwlwifi/pcie/internal.h b/sys/contrib/dev/iwlwifi/pcie/internal.h new file mode 100644 index 00000000000..a43e56c7689 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/pcie/internal.h @@ -0,0 +1,812 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2003-2015, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_trans_int_pcie_h__ +#define __iwl_trans_int_pcie_h__ + +#include +#include +#include +#include +#include +#include +#include + +#include "iwl-fh.h" +#include "iwl-csr.h" +#include "iwl-trans.h" +#include "iwl-debug.h" +#include "iwl-io.h" +#include "iwl-op-mode.h" +#include "iwl-drv.h" +#include "queue/tx.h" + +/* + * RX related structures and functions + */ +#define RX_NUM_QUEUES 1 +#define RX_POST_REQ_ALLOC 2 +#define RX_CLAIM_REQ_ALLOC 8 +#define RX_PENDING_WATERMARK 16 +#define FIRST_RX_QUEUE 512 + +struct iwl_host_cmd; + +/*This file includes the declaration that are internal to the + * trans_pcie layer */ + +/** + * struct iwl_rx_mem_buffer + * @page_dma: bus address of rxb page + * @page: driver's pointer to the rxb page + * @list: list entry for the membuffer + * @invalid: rxb is in driver ownership - not owned by HW + * @vid: index of this rxb in the global table + * @offset: indicates which offset of the page (in bytes) + * this buffer uses (if multiple RBs fit into one page) + */ +struct iwl_rx_mem_buffer { + dma_addr_t page_dma; + struct page *page; + struct list_head list; + u32 offset; + u16 vid; + bool invalid; +}; + +/** + * struct isr_statistics - interrupt statistics + * + */ +struct isr_statistics { + u32 hw; + u32 sw; + u32 err_code; + u32 sch; + u32 alive; + u32 rfkill; + u32 ctkill; + u32 wakeup; + u32 rx; + u32 tx; + u32 unhandled; +}; + +/** + * struct iwl_rx_transfer_desc - transfer descriptor + * @addr: ptr to free buffer start address + * @rbid: unique tag of the buffer + * @reserved: reserved + */ +struct iwl_rx_transfer_desc { + __le16 rbid; + __le16 reserved[3]; + __le64 addr; +} __packed; + +#define IWL_RX_CD_FLAGS_FRAGMENTED BIT(0) + +/** + * struct iwl_rx_completion_desc - completion descriptor + * @reserved1: reserved + * @rbid: unique tag of the received buffer + * @flags: flags (0: fragmented, all others: reserved) + * @reserved2: reserved + */ +struct iwl_rx_completion_desc { + __le32 reserved1; + __le16 rbid; + u8 flags; + u8 reserved2[25]; +} __packed; + +/** + * struct iwl_rxq - Rx queue + * @id: queue index + * @bd: driver's pointer to buffer of receive buffer descriptors (rbd). + * Address size is 32 bit in pre-9000 devices and 64 bit in 9000 devices. + * In AX210 devices it is a pointer to a list of iwl_rx_transfer_desc's + * @bd_dma: bus address of buffer of receive buffer descriptors (rbd) + * @used_bd: driver's pointer to buffer of used receive buffer descriptors (rbd) + * @used_bd_dma: physical address of buffer of used receive buffer descriptors (rbd) + * @read: Shared index to newest available Rx buffer + * @write: Shared index to oldest written Rx packet + * @free_count: Number of pre-allocated buffers in rx_free + * @used_count: Number of RBDs handled to allocator to use for allocation + * @write_actual: + * @rx_free: list of RBDs with allocated RB ready for use + * @rx_used: list of RBDs with no RB attached + * @need_update: flag to indicate we need to update read/write index + * @rb_stts: driver's pointer to receive buffer status + * @rb_stts_dma: bus address of receive buffer status + * @lock: + * @queue: actual rx queue. Not used for multi-rx queue. + * @next_rb_is_fragment: indicates that the previous RB that we handled set + * the fragmented flag, so the next one is still another fragment + * + * NOTE: rx_free and rx_used are used as a FIFO for iwl_rx_mem_buffers + */ +struct iwl_rxq { + int id; + void *bd; + dma_addr_t bd_dma; + union { + void *used_bd; + __le32 *bd_32; + struct iwl_rx_completion_desc *cd; + }; + dma_addr_t used_bd_dma; + u32 read; + u32 write; + u32 free_count; + u32 used_count; + u32 write_actual; + u32 queue_size; + struct list_head rx_free; + struct list_head rx_used; + bool need_update, next_rb_is_fragment; + void *rb_stts; + dma_addr_t rb_stts_dma; + spinlock_t lock; + struct napi_struct napi; + struct iwl_rx_mem_buffer *queue[RX_QUEUE_SIZE]; +}; + +/** + * struct iwl_rb_allocator - Rx allocator + * @req_pending: number of requests the allcator had not processed yet + * @req_ready: number of requests honored and ready for claiming + * @rbd_allocated: RBDs with pages allocated and ready to be handled to + * the queue. This is a list of &struct iwl_rx_mem_buffer + * @rbd_empty: RBDs with no page attached for allocator use. This is a list + * of &struct iwl_rx_mem_buffer + * @lock: protects the rbd_allocated and rbd_empty lists + * @alloc_wq: work queue for background calls + * @rx_alloc: work struct for background calls + */ +struct iwl_rb_allocator { + atomic_t req_pending; + atomic_t req_ready; + struct list_head rbd_allocated; + struct list_head rbd_empty; + spinlock_t lock; + struct workqueue_struct *alloc_wq; + struct work_struct rx_alloc; +}; + +/** + * iwl_get_closed_rb_stts - get closed rb stts from different structs + * @rxq - the rxq to get the rb stts from + */ +static inline __le16 iwl_get_closed_rb_stts(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + __le16 *rb_stts = rxq->rb_stts; + + return READ_ONCE(*rb_stts); + } else { + struct iwl_rb_status *rb_stts = rxq->rb_stts; + + return READ_ONCE(rb_stts->closed_rb_num); + } +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +/** + * enum iwl_fw_mon_dbgfs_state - the different states of the monitor_data + * debugfs file + * + * @IWL_FW_MON_DBGFS_STATE_CLOSED: the file is closed. + * @IWL_FW_MON_DBGFS_STATE_OPEN: the file is open. + * @IWL_FW_MON_DBGFS_STATE_DISABLED: the file is disabled, once this state is + * set the file can no longer be used. + */ +enum iwl_fw_mon_dbgfs_state { + IWL_FW_MON_DBGFS_STATE_CLOSED, + IWL_FW_MON_DBGFS_STATE_OPEN, + IWL_FW_MON_DBGFS_STATE_DISABLED, +}; +#endif + +/** + * enum iwl_shared_irq_flags - level of sharing for irq + * @IWL_SHARED_IRQ_NON_RX: interrupt vector serves non rx causes. + * @IWL_SHARED_IRQ_FIRST_RSS: interrupt vector serves first RSS queue. + */ +enum iwl_shared_irq_flags { + IWL_SHARED_IRQ_NON_RX = BIT(0), + IWL_SHARED_IRQ_FIRST_RSS = BIT(1), +}; + +/** + * enum iwl_image_response_code - image response values + * @IWL_IMAGE_RESP_DEF: the default value of the register + * @IWL_IMAGE_RESP_SUCCESS: iml was read successfully + * @IWL_IMAGE_RESP_FAIL: iml reading failed + */ +enum iwl_image_response_code { + IWL_IMAGE_RESP_DEF = 0, + IWL_IMAGE_RESP_SUCCESS = 1, + IWL_IMAGE_RESP_FAIL = 2, +}; + +/** + * struct cont_rec: continuous recording data structure + * @prev_wr_ptr: the last address that was read in monitor_data + * debugfs file + * @prev_wrap_cnt: the wrap count that was used during the last read in + * monitor_data debugfs file + * @state: the state of monitor_data debugfs file as described + * in &iwl_fw_mon_dbgfs_state enum + * @mutex: locked while reading from monitor_data debugfs file + */ +#ifdef CONFIG_IWLWIFI_DEBUGFS +struct cont_rec { + u32 prev_wr_ptr; + u32 prev_wrap_cnt; + u8 state; + /* Used to sync monitor_data debugfs file with driver unload flow */ + struct mutex mutex; +}; +#endif + +enum iwl_pcie_fw_reset_state { + FW_RESET_IDLE, + FW_RESET_REQUESTED, + FW_RESET_OK, + FW_RESET_ERROR, +}; + +/** + * struct iwl_trans_pcie - PCIe transport specific data + * @rxq: all the RX queue data + * @rx_pool: initial pool of iwl_rx_mem_buffer for all the queues + * @global_table: table mapping received VID from hw to rxb + * @rba: allocator for RX replenishing + * @ctxt_info: context information for FW self init + * @ctxt_info_gen3: context information for gen3 devices + * @prph_info: prph info for self init + * @prph_scratch: prph scratch for self init + * @ctxt_info_dma_addr: dma addr of context information + * @prph_info_dma_addr: dma addr of prph info + * @prph_scratch_dma_addr: dma addr of prph scratch + * @ctxt_info_dma_addr: dma addr of context information + * @init_dram: DRAM data of firmware image (including paging). + * Context information addresses will be taken from here. + * This is driver's local copy for keeping track of size and + * count for allocating and freeing the memory. + * @iml: image loader image virtual address + * @iml_dma_addr: image loader image DMA address + * @trans: pointer to the generic transport area + * @scd_base_addr: scheduler sram base address in SRAM + * @kw: keep warm address + * @pnvm_dram: DRAM area that contains the PNVM data + * @pci_dev: basic pci-network driver stuff + * @hw_base: pci hardware address support + * @ucode_write_complete: indicates that the ucode has been copied. + * @ucode_write_waitq: wait queue for uCode load + * @cmd_queue - command queue number + * @def_rx_queue - default rx queue number + * @rx_buf_size: Rx buffer size + * @scd_set_active: should the transport configure the SCD for HCMD queue + * @rx_page_order: page order for receive buffer size + * @rx_buf_bytes: RX buffer (RB) size in bytes + * @reg_lock: protect hw register access + * @mutex: to protect stop_device / start_fw / start_hw + * @cmd_in_flight: true when we have a host command in flight +#ifdef CONFIG_IWLWIFI_DEBUGFS + * @fw_mon_data: fw continuous recording data +#endif + * @msix_entries: array of MSI-X entries + * @msix_enabled: true if managed to enable MSI-X + * @shared_vec_mask: the type of causes the shared vector handles + * (see iwl_shared_irq_flags). + * @alloc_vecs: the number of interrupt vectors allocated by the OS + * @def_irq: default irq for non rx causes + * @fh_init_mask: initial unmasked fh causes + * @hw_init_mask: initial unmasked hw causes + * @fh_mask: current unmasked fh causes + * @hw_mask: current unmasked hw causes + * @in_rescan: true if we have triggered a device rescan + * @base_rb_stts: base virtual address of receive buffer status for all queues + * @base_rb_stts_dma: base physical address of receive buffer status + * @supported_dma_mask: DMA mask to validate the actual address against, + * will be DMA_BIT_MASK(11) or DMA_BIT_MASK(12) depending on the device + * @alloc_page_lock: spinlock for the page allocator + * @alloc_page: allocated page to still use parts of + * @alloc_page_used: how much of the allocated page was already used (bytes) + * @rf_name: name/version of the CRF, if any + */ +struct iwl_trans_pcie { + struct iwl_rxq *rxq; + struct iwl_rx_mem_buffer *rx_pool; + struct iwl_rx_mem_buffer **global_table; + struct iwl_rb_allocator rba; + union { + struct iwl_context_info *ctxt_info; + struct iwl_context_info_gen3 *ctxt_info_gen3; + }; + struct iwl_prph_info *prph_info; + struct iwl_prph_scratch *prph_scratch; + void *iml; + dma_addr_t ctxt_info_dma_addr; + dma_addr_t prph_info_dma_addr; + dma_addr_t prph_scratch_dma_addr; + dma_addr_t iml_dma_addr; + struct iwl_trans *trans; + + struct net_device napi_dev; + + /* INT ICT Table */ + __le32 *ict_tbl; + dma_addr_t ict_tbl_dma; + int ict_index; + bool use_ict; + bool is_down, opmode_down; + s8 debug_rfkill; + struct isr_statistics isr_stats; + + spinlock_t irq_lock; + struct mutex mutex; + u32 inta_mask; + u32 scd_base_addr; + struct iwl_dma_ptr kw; + + struct iwl_dram_data pnvm_dram; + struct iwl_dram_data reduce_power_dram; + + struct iwl_txq *txq_memory; + + /* PCI bus related data */ + struct pci_dev *pci_dev; + void __iomem *hw_base; + + bool ucode_write_complete; + bool sx_complete; + wait_queue_head_t ucode_write_waitq; + wait_queue_head_t sx_waitq; + + u8 def_rx_queue; + u8 n_no_reclaim_cmds; + u8 no_reclaim_cmds[MAX_NO_RECLAIM_CMDS]; + u16 num_rx_bufs; + + enum iwl_amsdu_size rx_buf_size; + bool scd_set_active; + bool pcie_dbg_dumped_once; + u32 rx_page_order; + u32 rx_buf_bytes; + u32 supported_dma_mask; + + /* allocator lock for the two values below */ + spinlock_t alloc_page_lock; + struct page *alloc_page; + u32 alloc_page_used; + + /*protect hw register */ + spinlock_t reg_lock; + bool cmd_hold_nic_awake; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct cont_rec fw_mon_data; +#endif + + struct msix_entry msix_entries[IWL_MAX_RX_HW_QUEUES]; + bool msix_enabled; + u8 shared_vec_mask; + u32 alloc_vecs; + u32 def_irq; + u32 fh_init_mask; + u32 hw_init_mask; + u32 fh_mask; + u32 hw_mask; + cpumask_t affinity_mask[IWL_MAX_RX_HW_QUEUES]; + u16 tx_cmd_queue_size; + bool in_rescan; + + void *base_rb_stts; + dma_addr_t base_rb_stts_dma; + + bool fw_reset_handshake; + enum iwl_pcie_fw_reset_state fw_reset_state; + wait_queue_head_t fw_reset_waitq; + + char rf_name[32]; +}; + +static inline struct iwl_trans_pcie * +IWL_TRANS_GET_PCIE_TRANS(struct iwl_trans *trans) +{ + return (void *)trans->trans_specific; +} + +static inline void iwl_pcie_clear_irq(struct iwl_trans *trans, int queue) +{ + /* + * Before sending the interrupt the HW disables it to prevent + * a nested interrupt. This is done by writing 1 to the corresponding + * bit in the mask register. After handling the interrupt, it should be + * re-enabled by clearing this bit. This register is defined as + * write 1 clear (W1C) register, meaning that it's being clear + * by writing 1 to the bit. + */ + iwl_write32(trans, CSR_MSIX_AUTOMASK_ST_AD, BIT(queue)); +} + +static inline struct iwl_trans * +iwl_trans_pcie_get_trans(struct iwl_trans_pcie *trans_pcie) +{ + return container_of((void *)trans_pcie, struct iwl_trans, + trans_specific); +} + +/* + * Convention: trans API functions: iwl_trans_pcie_XXX + * Other functions: iwl_pcie_XXX + */ +struct iwl_trans +*iwl_trans_pcie_alloc(struct pci_dev *pdev, + const struct pci_device_id *ent, + const struct iwl_cfg_trans_params *cfg_trans); +void iwl_trans_pcie_free(struct iwl_trans *trans); + +bool __iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans); +#define _iwl_trans_pcie_grab_nic_access(trans) \ + __cond_lock(nic_access_nobh, \ + likely(__iwl_trans_pcie_grab_nic_access(trans))) + +/***************************************************** +* RX +******************************************************/ +int iwl_pcie_rx_init(struct iwl_trans *trans); +int iwl_pcie_gen2_rx_init(struct iwl_trans *trans); +irqreturn_t iwl_pcie_msix_isr(int irq, void *data); +irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id); +irqreturn_t iwl_pcie_irq_msix_handler(int irq, void *dev_id); +irqreturn_t iwl_pcie_irq_rx_msix_handler(int irq, void *dev_id); +int iwl_pcie_rx_stop(struct iwl_trans *trans); +void iwl_pcie_rx_free(struct iwl_trans *trans); +void iwl_pcie_free_rbs_pool(struct iwl_trans *trans); +void iwl_pcie_rx_init_rxb_lists(struct iwl_rxq *rxq); +void iwl_pcie_rxq_alloc_rbs(struct iwl_trans *trans, gfp_t priority, + struct iwl_rxq *rxq); + +/***************************************************** +* ICT - interrupt handling +******************************************************/ +irqreturn_t iwl_pcie_isr(int irq, void *data); +int iwl_pcie_alloc_ict(struct iwl_trans *trans); +void iwl_pcie_free_ict(struct iwl_trans *trans); +void iwl_pcie_reset_ict(struct iwl_trans *trans); +void iwl_pcie_disable_ict(struct iwl_trans *trans); + +/***************************************************** +* TX / HCMD +******************************************************/ +int iwl_pcie_tx_init(struct iwl_trans *trans); +void iwl_pcie_tx_start(struct iwl_trans *trans, u32 scd_base_addr); +int iwl_pcie_tx_stop(struct iwl_trans *trans); +void iwl_pcie_tx_free(struct iwl_trans *trans); +bool iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int queue, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int wdg_timeout); +void iwl_trans_pcie_txq_disable(struct iwl_trans *trans, int queue, + bool configure_scd); +void iwl_trans_pcie_txq_set_shared_mode(struct iwl_trans *trans, u32 txq_id, + bool shared_mode); +int iwl_trans_pcie_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int txq_id); +void iwl_pcie_txq_check_wrptrs(struct iwl_trans *trans); +int iwl_trans_pcie_send_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd); +void iwl_pcie_hcmd_complete(struct iwl_trans *trans, + struct iwl_rx_cmd_buffer *rxb); +void iwl_trans_pcie_tx_reset(struct iwl_trans *trans); + +/***************************************************** +* Error handling +******************************************************/ +void iwl_pcie_dump_csr(struct iwl_trans *trans); + +/***************************************************** +* Helpers +******************************************************/ +static inline void _iwl_disable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + clear_bit(STATUS_INT_ENABLED, &trans->status); + if (!trans_pcie->msix_enabled) { + /* disable interrupts from uCode/NIC to host */ + iwl_write32(trans, CSR_INT_MASK, 0x00000000); + + /* acknowledge/clear/reset any interrupts still pending + * from uCode or flow handler (Rx/Tx DMA) */ + iwl_write32(trans, CSR_INT, 0xffffffff); + iwl_write32(trans, CSR_FH_INT_STATUS, 0xffffffff); + } else { + /* disable all the interrupt we might use */ + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, + trans_pcie->fh_init_mask); + iwl_write32(trans, CSR_MSIX_HW_INT_MASK_AD, + trans_pcie->hw_init_mask); + } + IWL_DEBUG_ISR(trans, "Disabled interrupts\n"); +} + +static inline int iwl_pcie_get_num_sections(const struct fw_img *fw, + int start) +{ + int i = 0; + + while (start < fw->num_sec && + fw->sec[start].offset != CPU1_CPU2_SEPARATOR_SECTION && + fw->sec[start].offset != PAGING_SEPARATOR_SECTION) { + start++; + i++; + } + + return i; +} + +static inline void iwl_pcie_ctxt_info_free_fw_img(struct iwl_trans *trans) +{ + struct iwl_self_init_dram *dram = &trans->init_dram; + int i; + + if (!dram->fw) { + WARN_ON(dram->fw_cnt); + return; + } + + for (i = 0; i < dram->fw_cnt; i++) + dma_free_coherent(trans->dev, dram->fw[i].size, + dram->fw[i].block, dram->fw[i].physical); + + kfree(dram->fw); + dram->fw_cnt = 0; + dram->fw = NULL; +} + +static inline void iwl_disable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock_bh(&trans_pcie->irq_lock); + _iwl_disable_interrupts(trans); + spin_unlock_bh(&trans_pcie->irq_lock); +} + +static inline void _iwl_enable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + IWL_DEBUG_ISR(trans, "Enabling interrupts\n"); + set_bit(STATUS_INT_ENABLED, &trans->status); + if (!trans_pcie->msix_enabled) { + trans_pcie->inta_mask = CSR_INI_SET_MASK; + iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); + } else { + /* + * fh/hw_mask keeps all the unmasked causes. + * Unlike msi, in msix cause is enabled when it is unset. + */ + trans_pcie->hw_mask = trans_pcie->hw_init_mask; + trans_pcie->fh_mask = trans_pcie->fh_init_mask; + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, + ~trans_pcie->fh_mask); + iwl_write32(trans, CSR_MSIX_HW_INT_MASK_AD, + ~trans_pcie->hw_mask); + } +} + +static inline void iwl_enable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock_bh(&trans_pcie->irq_lock); + _iwl_enable_interrupts(trans); + spin_unlock_bh(&trans_pcie->irq_lock); +} +static inline void iwl_enable_hw_int_msk_msix(struct iwl_trans *trans, u32 msk) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + iwl_write32(trans, CSR_MSIX_HW_INT_MASK_AD, ~msk); + trans_pcie->hw_mask = msk; +} + +static inline void iwl_enable_fh_int_msk_msix(struct iwl_trans *trans, u32 msk) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, ~msk); + trans_pcie->fh_mask = msk; +} + +static inline void iwl_enable_fw_load_int(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + IWL_DEBUG_ISR(trans, "Enabling FW load interrupt\n"); + if (!trans_pcie->msix_enabled) { + trans_pcie->inta_mask = CSR_INT_BIT_FH_TX; + iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); + } else { + iwl_write32(trans, CSR_MSIX_HW_INT_MASK_AD, + trans_pcie->hw_init_mask); + iwl_enable_fh_int_msk_msix(trans, + MSIX_FH_INT_CAUSES_D2S_CH0_NUM); + } +} + +static inline void iwl_enable_fw_load_int_ctx_info(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + IWL_DEBUG_ISR(trans, "Enabling ALIVE interrupt only\n"); + + if (!trans_pcie->msix_enabled) { + /* + * When we'll receive the ALIVE interrupt, the ISR will call + * iwl_enable_fw_load_int_ctx_info again to set the ALIVE + * interrupt (which is not really needed anymore) but also the + * RX interrupt which will allow us to receive the ALIVE + * notification (which is Rx) and continue the flow. + */ + trans_pcie->inta_mask = CSR_INT_BIT_ALIVE | CSR_INT_BIT_FH_RX; + iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); + } else { + iwl_enable_hw_int_msk_msix(trans, + MSIX_HW_INT_CAUSES_REG_ALIVE); + /* + * Leave all the FH causes enabled to get the ALIVE + * notification. + */ + iwl_enable_fh_int_msk_msix(trans, trans_pcie->fh_init_mask); + } +} + +static inline const char *queue_name(struct device *dev, + struct iwl_trans_pcie *trans_p, int i) +{ + if (trans_p->shared_vec_mask) { + int vec = trans_p->shared_vec_mask & + IWL_SHARED_IRQ_FIRST_RSS ? 1 : 0; + + if (i == 0) + return DRV_NAME ":shared_IRQ"; + + return devm_kasprintf(dev, GFP_KERNEL, + DRV_NAME ":queue_%d", i + vec); + } + if (i == 0) + return DRV_NAME ":default_queue"; + + if (i == trans_p->alloc_vecs - 1) + return DRV_NAME ":exception"; + + return devm_kasprintf(dev, GFP_KERNEL, + DRV_NAME ":queue_%d", i); +} + +static inline void iwl_enable_rfkill_int(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + IWL_DEBUG_ISR(trans, "Enabling rfkill interrupt\n"); + if (!trans_pcie->msix_enabled) { + trans_pcie->inta_mask = CSR_INT_BIT_RF_KILL; + iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); + } else { + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, + trans_pcie->fh_init_mask); + iwl_enable_hw_int_msk_msix(trans, + MSIX_HW_INT_CAUSES_REG_RF_KILL); + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_9000) { + /* + * On 9000-series devices this bit isn't enabled by default, so + * when we power down the device we need set the bit to allow it + * to wake up the PCI-E bus for RF-kill interrupts. + */ + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_RFKILL_WAKE_L1A_EN); + } +} + +void iwl_pcie_handle_rfkill_irq(struct iwl_trans *trans); + +static inline bool iwl_is_rfkill_set(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->mutex); + + if (trans_pcie->debug_rfkill == 1) + return true; + + return !(iwl_read32(trans, CSR_GP_CNTRL) & + CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW); +} + +static inline void __iwl_trans_pcie_set_bits_mask(struct iwl_trans *trans, + u32 reg, u32 mask, u32 value) +{ + u32 v; + +#ifdef CONFIG_IWLWIFI_DEBUG + WARN_ON_ONCE(value & ~mask); +#endif + + v = iwl_read32(trans, reg); + v &= ~mask; + v |= value; + iwl_write32(trans, reg, v); +} + +static inline void __iwl_trans_pcie_clear_bit(struct iwl_trans *trans, + u32 reg, u32 mask) +{ + __iwl_trans_pcie_set_bits_mask(trans, reg, mask, 0); +} + +static inline void __iwl_trans_pcie_set_bit(struct iwl_trans *trans, + u32 reg, u32 mask) +{ + __iwl_trans_pcie_set_bits_mask(trans, reg, mask, mask); +} + +static inline bool iwl_pcie_dbg_on(struct iwl_trans *trans) +{ + return (trans->dbg.dest_tlv || iwl_trans_dbg_ini_valid(trans)); +} + +void iwl_trans_pcie_rf_kill(struct iwl_trans *trans, bool state); +void iwl_trans_pcie_dump_regs(struct iwl_trans *trans); + +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans); +#else +static inline void iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans) { } +#endif + +void iwl_pcie_rx_allocator_work(struct work_struct *data); + +/* common functions that are used by gen2 transport */ +int iwl_pcie_gen2_apm_init(struct iwl_trans *trans); +void iwl_pcie_apm_config(struct iwl_trans *trans); +int iwl_pcie_prepare_card_hw(struct iwl_trans *trans); +void iwl_pcie_synchronize_irqs(struct iwl_trans *trans); +bool iwl_pcie_check_hw_rf_kill(struct iwl_trans *trans); +void iwl_trans_pcie_handle_stop_rfkill(struct iwl_trans *trans, + bool was_in_rfkill); +void iwl_pcie_apm_stop_master(struct iwl_trans *trans); +void iwl_pcie_conf_msix_hw(struct iwl_trans_pcie *trans_pcie); +int iwl_pcie_alloc_dma_ptr(struct iwl_trans *trans, + struct iwl_dma_ptr *ptr, size_t size); +void iwl_pcie_free_dma_ptr(struct iwl_trans *trans, struct iwl_dma_ptr *ptr); +void iwl_pcie_apply_destination(struct iwl_trans *trans); + +/* common functions that are used by gen3 transport */ +void iwl_pcie_alloc_fw_monitor(struct iwl_trans *trans, u8 max_power); + +/* transport gen 2 exported functions */ +int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, bool run_in_rfkill); +void iwl_trans_pcie_gen2_fw_alive(struct iwl_trans *trans, u32 scd_addr); +int iwl_trans_pcie_gen2_send_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd); +void iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans); +void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans); +void iwl_pcie_d3_complete_suspend(struct iwl_trans *trans, + bool test, bool reset); +int iwl_pcie_gen2_enqueue_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd); +int iwl_pcie_enqueue_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd); +#endif /* __iwl_trans_int_pcie_h__ */ diff --git a/sys/contrib/dev/iwlwifi/pcie/rx.c b/sys/contrib/dev/iwlwifi/pcie/rx.c new file mode 100644 index 00000000000..2f306309578 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/pcie/rx.c @@ -0,0 +1,2328 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2003-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include +#include + +#include "iwl-prph.h" +#include "iwl-io.h" +#include "internal.h" +#include "iwl-op-mode.h" +#include "iwl-context-info-gen3.h" + +/****************************************************************************** + * + * RX path functions + * + ******************************************************************************/ + +/* + * Rx theory of operation + * + * Driver allocates a circular buffer of Receive Buffer Descriptors (RBDs), + * each of which point to Receive Buffers to be filled by the NIC. These get + * used not only for Rx frames, but for any command response or notification + * from the NIC. The driver and NIC manage the Rx buffers by means + * of indexes into the circular buffer. + * + * Rx Queue Indexes + * The host/firmware share two index registers for managing the Rx buffers. + * + * The READ index maps to the first position that the firmware may be writing + * to -- the driver can read up to (but not including) this position and get + * good data. + * The READ index is managed by the firmware once the card is enabled. + * + * The WRITE index maps to the last position the driver has read from -- the + * position preceding WRITE is the last slot the firmware can place a packet. + * + * The queue is empty (no good data) if WRITE = READ - 1, and is full if + * WRITE = READ. + * + * During initialization, the host sets up the READ queue position to the first + * INDEX position, and WRITE to the last (READ - 1 wrapped) + * + * When the firmware places a packet in a buffer, it will advance the READ index + * and fire the RX interrupt. The driver can then query the READ index and + * process as many packets as possible, moving the WRITE index forward as it + * resets the Rx queue buffers with new memory. + * + * The management in the driver is as follows: + * + A list of pre-allocated RBDs is stored in iwl->rxq->rx_free. + * When the interrupt handler is called, the request is processed. + * The page is either stolen - transferred to the upper layer + * or reused - added immediately to the iwl->rxq->rx_free list. + * + When the page is stolen - the driver updates the matching queue's used + * count, detaches the RBD and transfers it to the queue used list. + * When there are two used RBDs - they are transferred to the allocator empty + * list. Work is then scheduled for the allocator to start allocating + * eight buffers. + * When there are another 6 used RBDs - they are transferred to the allocator + * empty list and the driver tries to claim the pre-allocated buffers and + * add them to iwl->rxq->rx_free. If it fails - it continues to claim them + * until ready. + * When there are 8+ buffers in the free list - either from allocation or from + * 8 reused unstolen pages - restock is called to update the FW and indexes. + * + In order to make sure the allocator always has RBDs to use for allocation + * the allocator has initial pool in the size of num_queues*(8-2) - the + * maximum missing RBDs per allocation request (request posted with 2 + * empty RBDs, there is no guarantee when the other 6 RBDs are supplied). + * The queues supplies the recycle of the rest of the RBDs. + * + A received packet is processed and handed to the kernel network stack, + * detached from the iwl->rxq. The driver 'processed' index is updated. + * + If there are no allocated buffers in iwl->rxq->rx_free, + * the READ INDEX is not incremented and iwl->status(RX_STALLED) is set. + * If there were enough free buffers and RX_STALLED is set it is cleared. + * + * + * Driver sequence: + * + * iwl_rxq_alloc() Allocates rx_free + * iwl_pcie_rx_replenish() Replenishes rx_free list from rx_used, and calls + * iwl_pcie_rxq_restock. + * Used only during initialization. + * iwl_pcie_rxq_restock() Moves available buffers from rx_free into Rx + * queue, updates firmware pointers, and updates + * the WRITE index. + * iwl_pcie_rx_allocator() Background work for allocating pages. + * + * -- enable interrupts -- + * ISR - iwl_rx() Detach iwl_rx_mem_buffers from pool up to the + * READ INDEX, detaching the SKB from the pool. + * Moves the packet buffer from queue to rx_used. + * Posts and claims requests to the allocator. + * Calls iwl_pcie_rxq_restock to refill any empty + * slots. + * + * RBD life-cycle: + * + * Init: + * rxq.pool -> rxq.rx_used -> rxq.rx_free -> rxq.queue + * + * Regular Receive interrupt: + * Page Stolen: + * rxq.queue -> rxq.rx_used -> allocator.rbd_empty -> + * allocator.rbd_allocated -> rxq.rx_free -> rxq.queue + * Page not Stolen: + * rxq.queue -> rxq.rx_free -> rxq.queue + * ... + * + */ + +/* + * iwl_rxq_space - Return number of free slots available in queue. + */ +static int iwl_rxq_space(const struct iwl_rxq *rxq) +{ + /* Make sure rx queue size is a power of 2 */ + WARN_ON(rxq->queue_size & (rxq->queue_size - 1)); + + /* + * There can be up to (RX_QUEUE_SIZE - 1) free slots, to avoid ambiguity + * between empty and completely full queues. + * The following is equivalent to modulo by RX_QUEUE_SIZE and is well + * defined for negative dividends. + */ + return (rxq->read - rxq->write - 1) & (rxq->queue_size - 1); +} + +/* + * iwl_dma_addr2rbd_ptr - convert a DMA address to a uCode read buffer ptr + */ +static inline __le32 iwl_pcie_dma_addr2rbd_ptr(dma_addr_t dma_addr) +{ + return cpu_to_le32((u32)(dma_addr >> 8)); +} + +/* + * iwl_pcie_rx_stop - stops the Rx DMA + */ +int iwl_pcie_rx_stop(struct iwl_trans *trans) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + /* TODO: remove this once fw does it */ + iwl_write_umac_prph(trans, RFH_RXF_DMA_CFG_GEN3, 0); + return iwl_poll_umac_prph_bit(trans, RFH_GEN_STATUS_GEN3, + RXF_DMA_IDLE, RXF_DMA_IDLE, 1000); + } else if (trans->trans_cfg->mq_rx_supported) { + iwl_write_prph(trans, RFH_RXF_DMA_CFG, 0); + return iwl_poll_prph_bit(trans, RFH_GEN_STATUS, + RXF_DMA_IDLE, RXF_DMA_IDLE, 1000); + } else { + iwl_write_direct32(trans, FH_MEM_RCSR_CHNL0_CONFIG_REG, 0); + return iwl_poll_direct_bit(trans, FH_MEM_RSSR_RX_STATUS_REG, + FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE, + 1000); + } +} + +/* + * iwl_pcie_rxq_inc_wr_ptr - Update the write pointer for the RX queue + */ +static void iwl_pcie_rxq_inc_wr_ptr(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + u32 reg; + + lockdep_assert_held(&rxq->lock); + + /* + * explicitly wake up the NIC if: + * 1. shadow registers aren't enabled + * 2. there is a chance that the NIC is asleep + */ + if (!trans->trans_cfg->base_params->shadow_reg_enable && + test_bit(STATUS_TPOWER_PMI, &trans->status)) { + reg = iwl_read32(trans, CSR_UCODE_DRV_GP1); + + if (reg & CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP) { + IWL_DEBUG_INFO(trans, "Rx queue requesting wakeup, GP1 = 0x%x\n", + reg); + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + rxq->need_update = true; + return; + } + } + + rxq->write_actual = round_down(rxq->write, 8); + if (trans->trans_cfg->mq_rx_supported) + iwl_write32(trans, RFH_Q_FRBDCB_WIDX_TRG(rxq->id), + rxq->write_actual); + else + iwl_write32(trans, FH_RSCSR_CHNL0_WPTR, rxq->write_actual); +} + +static void iwl_pcie_rxq_check_wrptr(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int i; + + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + if (!rxq->need_update) + continue; + spin_lock_bh(&rxq->lock); + iwl_pcie_rxq_inc_wr_ptr(trans, rxq); + rxq->need_update = false; + spin_unlock_bh(&rxq->lock); + } +} + +static void iwl_pcie_restock_bd(struct iwl_trans *trans, + struct iwl_rxq *rxq, + struct iwl_rx_mem_buffer *rxb) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + struct iwl_rx_transfer_desc *bd = rxq->bd; + + BUILD_BUG_ON(sizeof(*bd) != 2 * sizeof(u64)); + + bd[rxq->write].addr = cpu_to_le64(rxb->page_dma); + bd[rxq->write].rbid = cpu_to_le16(rxb->vid); + } else { + __le64 *bd = rxq->bd; + + bd[rxq->write] = cpu_to_le64(rxb->page_dma | rxb->vid); + } + + IWL_DEBUG_PCI_RW(trans, "Assigned virtual RB ID %u to queue %d index %d\n", + (u32)rxb->vid, rxq->id, rxq->write); +} + +/* + * iwl_pcie_rxmq_restock - restock implementation for multi-queue rx + */ +static void iwl_pcie_rxmq_restock(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rx_mem_buffer *rxb; + + /* + * If the device isn't enabled - no need to try to add buffers... + * This can happen when we stop the device and still have an interrupt + * pending. We stop the APM before we sync the interrupts because we + * have to (see comment there). On the other hand, since the APM is + * stopped, we cannot access the HW (in particular not prph). + * So don't try to restock if the APM has been already stopped. + */ + if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + return; + + spin_lock_bh(&rxq->lock); + while (rxq->free_count) { + /* Get next free Rx buffer, remove from free list */ + rxb = list_first_entry(&rxq->rx_free, struct iwl_rx_mem_buffer, + list); + list_del(&rxb->list); + rxb->invalid = false; + /* some low bits are expected to be unset (depending on hw) */ + WARN_ON(rxb->page_dma & trans_pcie->supported_dma_mask); + /* Point to Rx buffer via next RBD in circular buffer */ + iwl_pcie_restock_bd(trans, rxq, rxb); + rxq->write = (rxq->write + 1) & (rxq->queue_size - 1); + rxq->free_count--; + } + spin_unlock_bh(&rxq->lock); + + /* + * If we've added more space for the firmware to place data, tell it. + * Increment device's write pointer in multiples of 8. + */ + if (rxq->write_actual != (rxq->write & ~0x7)) { + spin_lock_bh(&rxq->lock); + iwl_pcie_rxq_inc_wr_ptr(trans, rxq); + spin_unlock_bh(&rxq->lock); + } +} + +/* + * iwl_pcie_rxsq_restock - restock implementation for single queue rx + */ +static void iwl_pcie_rxsq_restock(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + struct iwl_rx_mem_buffer *rxb; + + /* + * If the device isn't enabled - not need to try to add buffers... + * This can happen when we stop the device and still have an interrupt + * pending. We stop the APM before we sync the interrupts because we + * have to (see comment there). On the other hand, since the APM is + * stopped, we cannot access the HW (in particular not prph). + * So don't try to restock if the APM has been already stopped. + */ + if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + return; + + spin_lock_bh(&rxq->lock); + while ((iwl_rxq_space(rxq) > 0) && (rxq->free_count)) { + __le32 *bd = (__le32 *)rxq->bd; + /* The overwritten rxb must be a used one */ + rxb = rxq->queue[rxq->write]; + BUG_ON(rxb && rxb->page); + + /* Get next free Rx buffer, remove from free list */ + rxb = list_first_entry(&rxq->rx_free, struct iwl_rx_mem_buffer, + list); + list_del(&rxb->list); + rxb->invalid = false; + + /* Point to Rx buffer via next RBD in circular buffer */ + bd[rxq->write] = iwl_pcie_dma_addr2rbd_ptr(rxb->page_dma); + rxq->queue[rxq->write] = rxb; + rxq->write = (rxq->write + 1) & RX_QUEUE_MASK; + rxq->free_count--; + } + spin_unlock_bh(&rxq->lock); + + /* If we've added more space for the firmware to place data, tell it. + * Increment device's write pointer in multiples of 8. */ + if (rxq->write_actual != (rxq->write & ~0x7)) { + spin_lock_bh(&rxq->lock); + iwl_pcie_rxq_inc_wr_ptr(trans, rxq); + spin_unlock_bh(&rxq->lock); + } +} + +/* + * iwl_pcie_rxq_restock - refill RX queue from pre-allocated pool + * + * If there are slots in the RX queue that need to be restocked, + * and we have free pre-allocated buffers, fill the ranks as much + * as we can, pulling from rx_free. + * + * This moves the 'write' index forward to catch up with 'processed', and + * also updates the memory address in the firmware to reference the new + * target buffer. + */ +static +void iwl_pcie_rxq_restock(struct iwl_trans *trans, struct iwl_rxq *rxq) +{ + if (trans->trans_cfg->mq_rx_supported) + iwl_pcie_rxmq_restock(trans, rxq); + else + iwl_pcie_rxsq_restock(trans, rxq); +} + +/* + * iwl_pcie_rx_alloc_page - allocates and returns a page. + * + */ +static struct page *iwl_pcie_rx_alloc_page(struct iwl_trans *trans, + u32 *offset, gfp_t priority) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + unsigned int rbsize = iwl_trans_get_rb_size(trans_pcie->rx_buf_size); + unsigned int allocsize = PAGE_SIZE << trans_pcie->rx_page_order; + struct page *page; + gfp_t gfp_mask = priority; + + if (trans_pcie->rx_page_order > 0) + gfp_mask |= __GFP_COMP; + + if (trans_pcie->alloc_page) { + spin_lock_bh(&trans_pcie->alloc_page_lock); + /* recheck */ + if (trans_pcie->alloc_page) { + *offset = trans_pcie->alloc_page_used; + page = trans_pcie->alloc_page; + trans_pcie->alloc_page_used += rbsize; + if (trans_pcie->alloc_page_used >= allocsize) + trans_pcie->alloc_page = NULL; + else + get_page(page); + spin_unlock_bh(&trans_pcie->alloc_page_lock); + return page; + } + spin_unlock_bh(&trans_pcie->alloc_page_lock); + } + + /* Alloc a new receive buffer */ + page = alloc_pages(gfp_mask, trans_pcie->rx_page_order); + if (!page) { + if (net_ratelimit()) + IWL_DEBUG_INFO(trans, "alloc_pages failed, order: %d\n", + trans_pcie->rx_page_order); + /* + * Issue an error if we don't have enough pre-allocated + * buffers. + */ + if (!(gfp_mask & __GFP_NOWARN) && net_ratelimit()) + IWL_CRIT(trans, + "Failed to alloc_pages\n"); + return NULL; + } + + if (2 * rbsize <= allocsize) { + spin_lock_bh(&trans_pcie->alloc_page_lock); + if (!trans_pcie->alloc_page) { + get_page(page); + trans_pcie->alloc_page = page; + trans_pcie->alloc_page_used = rbsize; + } + spin_unlock_bh(&trans_pcie->alloc_page_lock); + } + + *offset = 0; + return page; +} + +/* + * iwl_pcie_rxq_alloc_rbs - allocate a page for each used RBD + * + * A used RBD is an Rx buffer that has been given to the stack. To use it again + * a page must be allocated and the RBD must point to the page. This function + * doesn't change the HW pointer but handles the list of pages that is used by + * iwl_pcie_rxq_restock. The latter function will update the HW to use the newly + * allocated buffers. + */ +void iwl_pcie_rxq_alloc_rbs(struct iwl_trans *trans, gfp_t priority, + struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rx_mem_buffer *rxb; + struct page *page; + + while (1) { + unsigned int offset; + + spin_lock_bh(&rxq->lock); + if (list_empty(&rxq->rx_used)) { + spin_unlock_bh(&rxq->lock); + return; + } + spin_unlock_bh(&rxq->lock); + + page = iwl_pcie_rx_alloc_page(trans, &offset, priority); + if (!page) + return; + + spin_lock_bh(&rxq->lock); + + if (list_empty(&rxq->rx_used)) { + spin_unlock_bh(&rxq->lock); + __free_pages(page, trans_pcie->rx_page_order); + return; + } + rxb = list_first_entry(&rxq->rx_used, struct iwl_rx_mem_buffer, + list); + list_del(&rxb->list); + spin_unlock_bh(&rxq->lock); + + BUG_ON(rxb->page); + rxb->page = page; + rxb->offset = offset; + /* Get physical address of the RB */ + rxb->page_dma = + dma_map_page(trans->dev, page, rxb->offset, + trans_pcie->rx_buf_bytes, + DMA_FROM_DEVICE); + if (dma_mapping_error(trans->dev, rxb->page_dma)) { + rxb->page = NULL; + spin_lock_bh(&rxq->lock); + list_add(&rxb->list, &rxq->rx_used); + spin_unlock_bh(&rxq->lock); + __free_pages(page, trans_pcie->rx_page_order); + return; + } + + spin_lock_bh(&rxq->lock); + + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + + spin_unlock_bh(&rxq->lock); + } +} + +void iwl_pcie_free_rbs_pool(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int i; + + if (!trans_pcie->rx_pool) + return; + + for (i = 0; i < RX_POOL_SIZE(trans_pcie->num_rx_bufs); i++) { + if (!trans_pcie->rx_pool[i].page) + continue; + dma_unmap_page(trans->dev, trans_pcie->rx_pool[i].page_dma, + trans_pcie->rx_buf_bytes, DMA_FROM_DEVICE); + __free_pages(trans_pcie->rx_pool[i].page, + trans_pcie->rx_page_order); + trans_pcie->rx_pool[i].page = NULL; + } +} + +/* + * iwl_pcie_rx_allocator - Allocates pages in the background for RX queues + * + * Allocates for each received request 8 pages + * Called as a scheduled work item. + */ +static void iwl_pcie_rx_allocator(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + struct list_head local_empty; + int pending = atomic_read(&rba->req_pending); + + IWL_DEBUG_TPT(trans, "Pending allocation requests = %d\n", pending); + + /* If we were scheduled - there is at least one request */ + spin_lock_bh(&rba->lock); + /* swap out the rba->rbd_empty to a local list */ + list_replace_init(&rba->rbd_empty, &local_empty); + spin_unlock_bh(&rba->lock); + + while (pending) { + int i; + LIST_HEAD(local_allocated); + gfp_t gfp_mask = GFP_KERNEL; + + /* Do not post a warning if there are only a few requests */ + if (pending < RX_PENDING_WATERMARK) + gfp_mask |= __GFP_NOWARN; + + for (i = 0; i < RX_CLAIM_REQ_ALLOC;) { + struct iwl_rx_mem_buffer *rxb; + struct page *page; + + /* List should never be empty - each reused RBD is + * returned to the list, and initial pool covers any + * possible gap between the time the page is allocated + * to the time the RBD is added. + */ + BUG_ON(list_empty(&local_empty)); + /* Get the first rxb from the rbd list */ + rxb = list_first_entry(&local_empty, + struct iwl_rx_mem_buffer, list); + BUG_ON(rxb->page); + + /* Alloc a new receive buffer */ + page = iwl_pcie_rx_alloc_page(trans, &rxb->offset, + gfp_mask); + if (!page) + continue; + rxb->page = page; + + /* Get physical address of the RB */ + rxb->page_dma = dma_map_page(trans->dev, page, + rxb->offset, + trans_pcie->rx_buf_bytes, + DMA_FROM_DEVICE); + if (dma_mapping_error(trans->dev, rxb->page_dma)) { + rxb->page = NULL; + __free_pages(page, trans_pcie->rx_page_order); + continue; + } + + /* move the allocated entry to the out list */ + list_move(&rxb->list, &local_allocated); + i++; + } + + atomic_dec(&rba->req_pending); + pending--; + + if (!pending) { + pending = atomic_read(&rba->req_pending); + if (pending) + IWL_DEBUG_TPT(trans, + "Got more pending allocation requests = %d\n", + pending); + } + + spin_lock_bh(&rba->lock); + /* add the allocated rbds to the allocator allocated list */ + list_splice_tail(&local_allocated, &rba->rbd_allocated); + /* get more empty RBDs for current pending requests */ + list_splice_tail_init(&rba->rbd_empty, &local_empty); + spin_unlock_bh(&rba->lock); + + atomic_inc(&rba->req_ready); + + } + + spin_lock_bh(&rba->lock); + /* return unused rbds to the allocator empty list */ + list_splice_tail(&local_empty, &rba->rbd_empty); + spin_unlock_bh(&rba->lock); + + IWL_DEBUG_TPT(trans, "%s, exit.\n", __func__); +} + +/* + * iwl_pcie_rx_allocator_get - returns the pre-allocated pages +.* +.* Called by queue when the queue posted allocation request and + * has freed 8 RBDs in order to restock itself. + * This function directly moves the allocated RBs to the queue's ownership + * and updates the relevant counters. + */ +static void iwl_pcie_rx_allocator_get(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + int i; + + lockdep_assert_held(&rxq->lock); + + /* + * atomic_dec_if_positive returns req_ready - 1 for any scenario. + * If req_ready is 0 atomic_dec_if_positive will return -1 and this + * function will return early, as there are no ready requests. + * atomic_dec_if_positive will perofrm the *actual* decrement only if + * req_ready > 0, i.e. - there are ready requests and the function + * hands one request to the caller. + */ + if (atomic_dec_if_positive(&rba->req_ready) < 0) + return; + + spin_lock(&rba->lock); + for (i = 0; i < RX_CLAIM_REQ_ALLOC; i++) { + /* Get next free Rx buffer, remove it from free list */ + struct iwl_rx_mem_buffer *rxb = + list_first_entry(&rba->rbd_allocated, + struct iwl_rx_mem_buffer, list); + + list_move(&rxb->list, &rxq->rx_free); + } + spin_unlock(&rba->lock); + + rxq->used_count -= RX_CLAIM_REQ_ALLOC; + rxq->free_count += RX_CLAIM_REQ_ALLOC; +} + +void iwl_pcie_rx_allocator_work(struct work_struct *data) +{ + struct iwl_rb_allocator *rba_p = + container_of(data, struct iwl_rb_allocator, rx_alloc); + struct iwl_trans_pcie *trans_pcie = + container_of(rba_p, struct iwl_trans_pcie, rba); + + iwl_pcie_rx_allocator(trans_pcie->trans); +} + +static int iwl_pcie_free_bd_size(struct iwl_trans *trans, bool use_rx_td) +{ + struct iwl_rx_transfer_desc *rx_td; + + if (use_rx_td) + return sizeof(*rx_td); + else + return trans->trans_cfg->mq_rx_supported ? sizeof(__le64) : + sizeof(__le32); +} + +static void iwl_pcie_free_rxq_dma(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + bool use_rx_td = (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210); + int free_size = iwl_pcie_free_bd_size(trans, use_rx_td); + + if (rxq->bd) + dma_free_coherent(trans->dev, + free_size * rxq->queue_size, + rxq->bd, rxq->bd_dma); + rxq->bd_dma = 0; + rxq->bd = NULL; + + rxq->rb_stts_dma = 0; + rxq->rb_stts = NULL; + + if (rxq->used_bd) + dma_free_coherent(trans->dev, + (use_rx_td ? sizeof(*rxq->cd) : + sizeof(__le32)) * rxq->queue_size, + rxq->used_bd, rxq->used_bd_dma); + rxq->used_bd_dma = 0; + rxq->used_bd = NULL; +} + +static int iwl_pcie_alloc_rxq_dma(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct device *dev = trans->dev; + int i; + int free_size; + bool use_rx_td = (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210); + size_t rb_stts_size = use_rx_td ? sizeof(__le16) : + sizeof(struct iwl_rb_status); + + spin_lock_init(&rxq->lock); + if (trans->trans_cfg->mq_rx_supported) + rxq->queue_size = trans->cfg->num_rbds; + else + rxq->queue_size = RX_QUEUE_SIZE; + + free_size = iwl_pcie_free_bd_size(trans, use_rx_td); + + /* + * Allocate the circular buffer of Read Buffer Descriptors + * (RBDs) + */ + rxq->bd = dma_alloc_coherent(dev, free_size * rxq->queue_size, + &rxq->bd_dma, GFP_KERNEL); + if (!rxq->bd) + goto err; + + if (trans->trans_cfg->mq_rx_supported) { + rxq->used_bd = dma_alloc_coherent(dev, + (use_rx_td ? sizeof(*rxq->cd) : sizeof(__le32)) * rxq->queue_size, + &rxq->used_bd_dma, + GFP_KERNEL); + if (!rxq->used_bd) + goto err; + } + + rxq->rb_stts = (void *)((u8 *)trans_pcie->base_rb_stts + rxq->id * rb_stts_size); + rxq->rb_stts_dma = + trans_pcie->base_rb_stts_dma + rxq->id * rb_stts_size; + + return 0; + +err: + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + iwl_pcie_free_rxq_dma(trans, rxq); + } + + return -ENOMEM; +} + +static int iwl_pcie_rx_alloc(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + int i, ret; + size_t rb_stts_size = trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210 ? + sizeof(__le16) : sizeof(struct iwl_rb_status); + + if (WARN_ON(trans_pcie->rxq)) + return -EINVAL; + + trans_pcie->rxq = kcalloc(trans->num_rx_queues, sizeof(struct iwl_rxq), + GFP_KERNEL); + trans_pcie->rx_pool = kcalloc(RX_POOL_SIZE(trans_pcie->num_rx_bufs), + sizeof(trans_pcie->rx_pool[0]), + GFP_KERNEL); + trans_pcie->global_table = + kcalloc(RX_POOL_SIZE(trans_pcie->num_rx_bufs), + sizeof(trans_pcie->global_table[0]), + GFP_KERNEL); + if (!trans_pcie->rxq || !trans_pcie->rx_pool || + !trans_pcie->global_table) { + ret = -ENOMEM; + goto err; + } + + spin_lock_init(&rba->lock); + + /* + * Allocate the driver's pointer to receive buffer status. + * Allocate for all queues continuously (HW requirement). + */ + trans_pcie->base_rb_stts = + dma_alloc_coherent(trans->dev, + rb_stts_size * trans->num_rx_queues, + &trans_pcie->base_rb_stts_dma, + GFP_KERNEL); + if (!trans_pcie->base_rb_stts) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + rxq->id = i; + ret = iwl_pcie_alloc_rxq_dma(trans, rxq); + if (ret) + goto err; + } + return 0; + +err: + if (trans_pcie->base_rb_stts) { + dma_free_coherent(trans->dev, + rb_stts_size * trans->num_rx_queues, + trans_pcie->base_rb_stts, + trans_pcie->base_rb_stts_dma); + trans_pcie->base_rb_stts = NULL; + trans_pcie->base_rb_stts_dma = 0; + } + kfree(trans_pcie->rx_pool); + trans_pcie->rx_pool = NULL; + kfree(trans_pcie->global_table); + trans_pcie->global_table = NULL; + kfree(trans_pcie->rxq); + trans_pcie->rxq = NULL; + + return ret; +} + +static void iwl_pcie_rx_hw_init(struct iwl_trans *trans, struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 rb_size; + const u32 rfdnlog = RX_QUEUE_SIZE_LOG; /* 256 RBDs */ + + switch (trans_pcie->rx_buf_size) { + case IWL_AMSDU_4K: + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K; + break; + case IWL_AMSDU_8K: + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_8K; + break; + case IWL_AMSDU_12K: + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_12K; + break; + default: + WARN_ON(1); + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K; + } + + if (!iwl_trans_grab_nic_access(trans)) + return; + + /* Stop Rx DMA */ + iwl_write32(trans, FH_MEM_RCSR_CHNL0_CONFIG_REG, 0); + /* reset and flush pointers */ + iwl_write32(trans, FH_MEM_RCSR_CHNL0_RBDCB_WPTR, 0); + iwl_write32(trans, FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ, 0); + iwl_write32(trans, FH_RSCSR_CHNL0_RDPTR, 0); + + /* Reset driver's Rx queue write index */ + iwl_write32(trans, FH_RSCSR_CHNL0_RBDCB_WPTR_REG, 0); + + /* Tell device where to find RBD circular buffer in DRAM */ + iwl_write32(trans, FH_RSCSR_CHNL0_RBDCB_BASE_REG, + (u32)(rxq->bd_dma >> 8)); + + /* Tell device where in DRAM to update its Rx status */ + iwl_write32(trans, FH_RSCSR_CHNL0_STTS_WPTR_REG, + rxq->rb_stts_dma >> 4); + + /* Enable Rx DMA + * FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY is set because of HW bug in + * the credit mechanism in 5000 HW RX FIFO + * Direct rx interrupts to hosts + * Rx buffer size 4 or 8k or 12k + * RB timeout 0x10 + * 256 RBDs + */ + iwl_write32(trans, FH_MEM_RCSR_CHNL0_CONFIG_REG, + FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL | + FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY | + FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL | + rb_size | + (RX_RB_TIMEOUT << FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) | + (rfdnlog << FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS)); + + iwl_trans_release_nic_access(trans); + + /* Set interrupt coalescing timer to default (2048 usecs) */ + iwl_write8(trans, CSR_INT_COALESCING, IWL_HOST_INT_TIMEOUT_DEF); + + /* W/A for interrupt coalescing bug in 7260 and 3160 */ + if (trans->cfg->host_interrupt_operation_mode) + iwl_set_bit(trans, CSR_INT_COALESCING, IWL_HOST_INT_OPER_MODE); +} + +static void iwl_pcie_rx_mq_hw_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 rb_size, enabled = 0; + int i; + + switch (trans_pcie->rx_buf_size) { + case IWL_AMSDU_2K: + rb_size = RFH_RXF_DMA_RB_SIZE_2K; + break; + case IWL_AMSDU_4K: + rb_size = RFH_RXF_DMA_RB_SIZE_4K; + break; + case IWL_AMSDU_8K: + rb_size = RFH_RXF_DMA_RB_SIZE_8K; + break; + case IWL_AMSDU_12K: + rb_size = RFH_RXF_DMA_RB_SIZE_12K; + break; + default: + WARN_ON(1); + rb_size = RFH_RXF_DMA_RB_SIZE_4K; + } + + if (!iwl_trans_grab_nic_access(trans)) + return; + + /* Stop Rx DMA */ + iwl_write_prph_no_grab(trans, RFH_RXF_DMA_CFG, 0); + /* disable free amd used rx queue operation */ + iwl_write_prph_no_grab(trans, RFH_RXF_RXQ_ACTIVE, 0); + + for (i = 0; i < trans->num_rx_queues; i++) { + /* Tell device where to find RBD free table in DRAM */ + iwl_write_prph64_no_grab(trans, + RFH_Q_FRBDCB_BA_LSB(i), + trans_pcie->rxq[i].bd_dma); + /* Tell device where to find RBD used table in DRAM */ + iwl_write_prph64_no_grab(trans, + RFH_Q_URBDCB_BA_LSB(i), + trans_pcie->rxq[i].used_bd_dma); + /* Tell device where in DRAM to update its Rx status */ + iwl_write_prph64_no_grab(trans, + RFH_Q_URBD_STTS_WPTR_LSB(i), + trans_pcie->rxq[i].rb_stts_dma); + /* Reset device indice tables */ + iwl_write_prph_no_grab(trans, RFH_Q_FRBDCB_WIDX(i), 0); + iwl_write_prph_no_grab(trans, RFH_Q_FRBDCB_RIDX(i), 0); + iwl_write_prph_no_grab(trans, RFH_Q_URBDCB_WIDX(i), 0); + + enabled |= BIT(i) | BIT(i + 16); + } + + /* + * Enable Rx DMA + * Rx buffer size 4 or 8k or 12k + * Min RB size 4 or 8 + * Drop frames that exceed RB size + * 512 RBDs + */ + iwl_write_prph_no_grab(trans, RFH_RXF_DMA_CFG, + RFH_DMA_EN_ENABLE_VAL | rb_size | + RFH_RXF_DMA_MIN_RB_4_8 | + RFH_RXF_DMA_DROP_TOO_LARGE_MASK | + RFH_RXF_DMA_RBDCB_SIZE_512); + + /* + * Activate DMA snooping. + * Set RX DMA chunk size to 64B for IOSF and 128B for PCIe + * Default queue is 0 + */ + iwl_write_prph_no_grab(trans, RFH_GEN_CFG, + RFH_GEN_CFG_RFH_DMA_SNOOP | + RFH_GEN_CFG_VAL(DEFAULT_RXQ_NUM, 0) | + RFH_GEN_CFG_SERVICE_DMA_SNOOP | + RFH_GEN_CFG_VAL(RB_CHUNK_SIZE, + trans->trans_cfg->integrated ? + RFH_GEN_CFG_RB_CHUNK_SIZE_64 : + RFH_GEN_CFG_RB_CHUNK_SIZE_128)); + /* Enable the relevant rx queues */ + iwl_write_prph_no_grab(trans, RFH_RXF_RXQ_ACTIVE, enabled); + + iwl_trans_release_nic_access(trans); + + /* Set interrupt coalescing timer to default (2048 usecs) */ + iwl_write8(trans, CSR_INT_COALESCING, IWL_HOST_INT_TIMEOUT_DEF); +} + +void iwl_pcie_rx_init_rxb_lists(struct iwl_rxq *rxq) +{ + lockdep_assert_held(&rxq->lock); + + INIT_LIST_HEAD(&rxq->rx_free); + INIT_LIST_HEAD(&rxq->rx_used); + rxq->free_count = 0; + rxq->used_count = 0; +} + +static int iwl_pcie_rx_handle(struct iwl_trans *trans, int queue, int budget); + +static int iwl_pcie_napi_poll(struct napi_struct *napi, int budget) +{ + struct iwl_rxq *rxq = container_of(napi, struct iwl_rxq, napi); + struct iwl_trans_pcie *trans_pcie; + struct iwl_trans *trans; + int ret; + + trans_pcie = container_of(napi->dev, struct iwl_trans_pcie, napi_dev); + trans = trans_pcie->trans; + + ret = iwl_pcie_rx_handle(trans, rxq->id, budget); + + IWL_DEBUG_ISR(trans, "[%d] handled %d, budget %d\n", + rxq->id, ret, budget); + + if (ret < budget) { + spin_lock(&trans_pcie->irq_lock); + if (test_bit(STATUS_INT_ENABLED, &trans->status)) + _iwl_enable_interrupts(trans); + spin_unlock(&trans_pcie->irq_lock); + + napi_complete_done(&rxq->napi, ret); + } + + return ret; +} + +static int iwl_pcie_napi_poll_msix(struct napi_struct *napi, int budget) +{ + struct iwl_rxq *rxq = container_of(napi, struct iwl_rxq, napi); + struct iwl_trans_pcie *trans_pcie; + struct iwl_trans *trans; + int ret; + + trans_pcie = container_of(napi->dev, struct iwl_trans_pcie, napi_dev); + trans = trans_pcie->trans; + + ret = iwl_pcie_rx_handle(trans, rxq->id, budget); + IWL_DEBUG_ISR(trans, "[%d] handled %d, budget %d\n", rxq->id, ret, + budget); + + if (ret < budget) { + int irq_line = rxq->id; + + /* FIRST_RSS is shared with line 0 */ + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS && + rxq->id == 1) + irq_line = 0; + + spin_lock(&trans_pcie->irq_lock); + iwl_pcie_clear_irq(trans, irq_line); + spin_unlock(&trans_pcie->irq_lock); + + napi_complete_done(&rxq->napi, ret); + } + + return ret; +} + +static int _iwl_pcie_rx_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *def_rxq; + struct iwl_rb_allocator *rba = &trans_pcie->rba; + int i, err, queue_size, allocator_pool_size, num_alloc; + + if (!trans_pcie->rxq) { + err = iwl_pcie_rx_alloc(trans); + if (err) + return err; + } + def_rxq = trans_pcie->rxq; + + cancel_work_sync(&rba->rx_alloc); + + spin_lock_bh(&rba->lock); + atomic_set(&rba->req_pending, 0); + atomic_set(&rba->req_ready, 0); + INIT_LIST_HEAD(&rba->rbd_allocated); + INIT_LIST_HEAD(&rba->rbd_empty); + spin_unlock_bh(&rba->lock); + + /* free all first - we overwrite everything here */ + iwl_pcie_free_rbs_pool(trans); + + for (i = 0; i < RX_QUEUE_SIZE; i++) + def_rxq->queue[i] = NULL; + + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + spin_lock_bh(&rxq->lock); + /* + * Set read write pointer to reflect that we have processed + * and used all buffers, but have not restocked the Rx queue + * with fresh buffers + */ + rxq->read = 0; + rxq->write = 0; + rxq->write_actual = 0; + memset(rxq->rb_stts, 0, + (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210) ? + sizeof(__le16) : sizeof(struct iwl_rb_status)); + + iwl_pcie_rx_init_rxb_lists(rxq); + + spin_unlock_bh(&rxq->lock); + + if (!rxq->napi.poll) { + int (*poll)(struct napi_struct *, int) = iwl_pcie_napi_poll; + + if (trans_pcie->msix_enabled) + poll = iwl_pcie_napi_poll_msix; + + netif_napi_add(&trans_pcie->napi_dev, &rxq->napi, + poll, NAPI_POLL_WEIGHT); + napi_enable(&rxq->napi); + } + + } + + /* move the pool to the default queue and allocator ownerships */ + queue_size = trans->trans_cfg->mq_rx_supported ? + trans_pcie->num_rx_bufs - 1 : RX_QUEUE_SIZE; + allocator_pool_size = trans->num_rx_queues * + (RX_CLAIM_REQ_ALLOC - RX_POST_REQ_ALLOC); + num_alloc = queue_size + allocator_pool_size; + + for (i = 0; i < num_alloc; i++) { + struct iwl_rx_mem_buffer *rxb = &trans_pcie->rx_pool[i]; + + if (i < allocator_pool_size) + list_add(&rxb->list, &rba->rbd_empty); + else + list_add(&rxb->list, &def_rxq->rx_used); + trans_pcie->global_table[i] = rxb; + rxb->vid = (u16)(i + 1); + rxb->invalid = true; + } + + iwl_pcie_rxq_alloc_rbs(trans, GFP_KERNEL, def_rxq); + + return 0; +} + +int iwl_pcie_rx_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret = _iwl_pcie_rx_init(trans); + + if (ret) + return ret; + + if (trans->trans_cfg->mq_rx_supported) + iwl_pcie_rx_mq_hw_init(trans); + else + iwl_pcie_rx_hw_init(trans, trans_pcie->rxq); + + iwl_pcie_rxq_restock(trans, trans_pcie->rxq); + + spin_lock_bh(&trans_pcie->rxq->lock); + iwl_pcie_rxq_inc_wr_ptr(trans, trans_pcie->rxq); + spin_unlock_bh(&trans_pcie->rxq->lock); + + return 0; +} + +int iwl_pcie_gen2_rx_init(struct iwl_trans *trans) +{ + /* Set interrupt coalescing timer to default (2048 usecs) */ + iwl_write8(trans, CSR_INT_COALESCING, IWL_HOST_INT_TIMEOUT_DEF); + + /* + * We don't configure the RFH. + * Restock will be done at alive, after firmware configured the RFH. + */ + return _iwl_pcie_rx_init(trans); +} + +void iwl_pcie_rx_free(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + int i; + size_t rb_stts_size = trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210 ? + sizeof(__le16) : sizeof(struct iwl_rb_status); + + /* + * if rxq is NULL, it means that nothing has been allocated, + * exit now + */ + if (!trans_pcie->rxq) { + IWL_DEBUG_INFO(trans, "Free NULL rx context\n"); + return; + } + + cancel_work_sync(&rba->rx_alloc); + + iwl_pcie_free_rbs_pool(trans); + + if (trans_pcie->base_rb_stts) { + dma_free_coherent(trans->dev, + rb_stts_size * trans->num_rx_queues, + trans_pcie->base_rb_stts, + trans_pcie->base_rb_stts_dma); + trans_pcie->base_rb_stts = NULL; + trans_pcie->base_rb_stts_dma = 0; + } + + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + iwl_pcie_free_rxq_dma(trans, rxq); + + if (rxq->napi.poll) { + napi_disable(&rxq->napi); + netif_napi_del(&rxq->napi); + } + } + kfree(trans_pcie->rx_pool); + kfree(trans_pcie->global_table); + kfree(trans_pcie->rxq); + + if (trans_pcie->alloc_page) + __free_pages(trans_pcie->alloc_page, trans_pcie->rx_page_order); +} + +static void iwl_pcie_rx_move_to_allocator(struct iwl_rxq *rxq, + struct iwl_rb_allocator *rba) +{ + spin_lock(&rba->lock); + list_splice_tail_init(&rxq->rx_used, &rba->rbd_empty); + spin_unlock(&rba->lock); +} + +/* + * iwl_pcie_rx_reuse_rbd - Recycle used RBDs + * + * Called when a RBD can be reused. The RBD is transferred to the allocator. + * When there are 2 empty RBDs - a request for allocation is posted + */ +static void iwl_pcie_rx_reuse_rbd(struct iwl_trans *trans, + struct iwl_rx_mem_buffer *rxb, + struct iwl_rxq *rxq, bool emergency) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + + /* Move the RBD to the used list, will be moved to allocator in batches + * before claiming or posting a request*/ + list_add_tail(&rxb->list, &rxq->rx_used); + + if (unlikely(emergency)) + return; + + /* Count the allocator owned RBDs */ + rxq->used_count++; + + /* If we have RX_POST_REQ_ALLOC new released rx buffers - + * issue a request for allocator. Modulo RX_CLAIM_REQ_ALLOC is + * used for the case we failed to claim RX_CLAIM_REQ_ALLOC, + * after but we still need to post another request. + */ + if ((rxq->used_count % RX_CLAIM_REQ_ALLOC) == RX_POST_REQ_ALLOC) { + /* Move the 2 RBDs to the allocator ownership. + Allocator has another 6 from pool for the request completion*/ + iwl_pcie_rx_move_to_allocator(rxq, rba); + + atomic_inc(&rba->req_pending); + queue_work(rba->alloc_wq, &rba->rx_alloc); + } +} + +static void iwl_pcie_rx_handle_rb(struct iwl_trans *trans, + struct iwl_rxq *rxq, + struct iwl_rx_mem_buffer *rxb, + bool emergency, + int i) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + bool page_stolen = false; + int max_len = trans_pcie->rx_buf_bytes; + u32 offset = 0; + + if (WARN_ON(!rxb)) + return; + + dma_unmap_page(trans->dev, rxb->page_dma, max_len, DMA_FROM_DEVICE); + + while (offset + sizeof(u32) + sizeof(struct iwl_cmd_header) < max_len) { + struct iwl_rx_packet *pkt; + bool reclaim; + int len; + struct iwl_rx_cmd_buffer rxcb = { + ._offset = rxb->offset + offset, + ._rx_page_order = trans_pcie->rx_page_order, + ._page = rxb->page, + ._page_stolen = false, + .truesize = max_len, + }; + + pkt = rxb_addr(&rxcb); + + if (pkt->len_n_flags == cpu_to_le32(FH_RSCSR_FRAME_INVALID)) { + IWL_DEBUG_RX(trans, + "Q %d: RB end marker at offset %d\n", + rxq->id, offset); + break; + } + + WARN((le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_RXQ_MASK) >> + FH_RSCSR_RXQ_POS != rxq->id, + "frame on invalid queue - is on %d and indicates %d\n", + rxq->id, + (le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_RXQ_MASK) >> + FH_RSCSR_RXQ_POS); + + IWL_DEBUG_RX(trans, + "Q %d: cmd at offset %d: %s (%.2x.%2x, seq 0x%x)\n", + rxq->id, offset, + iwl_get_cmd_string(trans, + iwl_cmd_id(pkt->hdr.cmd, + pkt->hdr.group_id, + 0)), + pkt->hdr.group_id, pkt->hdr.cmd, + le16_to_cpu(pkt->hdr.sequence)); + + len = iwl_rx_packet_len(pkt); + len += sizeof(u32); /* account for status word */ + + offset += ALIGN(len, FH_RSCSR_FRAME_ALIGN); + + /* check that what the device tells us made sense */ + if (offset > max_len) + break; + + trace_iwlwifi_dev_rx(trans->dev, trans, pkt, len); + trace_iwlwifi_dev_rx_data(trans->dev, trans, pkt, len); + + /* Reclaim a command buffer only if this packet is a response + * to a (driver-originated) command. + * If the packet (e.g. Rx frame) originated from uCode, + * there is no command buffer to reclaim. + * Ucode should set SEQ_RX_FRAME bit if ucode-originated, + * but apparently a few don't get set; catch them here. */ + reclaim = !(pkt->hdr.sequence & SEQ_RX_FRAME); + if (reclaim && !pkt->hdr.group_id) { + int i; + + for (i = 0; i < trans_pcie->n_no_reclaim_cmds; i++) { + if (trans_pcie->no_reclaim_cmds[i] == + pkt->hdr.cmd) { + reclaim = false; + break; + } + } + } + + if (rxq->id == trans_pcie->def_rx_queue) + iwl_op_mode_rx(trans->op_mode, &rxq->napi, + &rxcb); + else + iwl_op_mode_rx_rss(trans->op_mode, &rxq->napi, + &rxcb, rxq->id); + + /* + * After here, we should always check rxcb._page_stolen, + * if it is true then one of the handlers took the page. + */ + + if (reclaim) { + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int index = SEQ_TO_INDEX(sequence); + int cmd_index = iwl_txq_get_cmd_index(txq, index); + + kfree_sensitive(txq->entries[cmd_index].free_buf); + txq->entries[cmd_index].free_buf = NULL; + + /* Invoke any callbacks, transfer the buffer to caller, + * and fire off the (possibly) blocking + * iwl_trans_send_cmd() + * as we reclaim the driver command queue */ + if (!rxcb._page_stolen) + iwl_pcie_hcmd_complete(trans, &rxcb); + else + IWL_WARN(trans, "Claim null rxb?\n"); + } + + page_stolen |= rxcb._page_stolen; + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + break; + } + + /* page was stolen from us -- free our reference */ + if (page_stolen) { + __free_pages(rxb->page, trans_pcie->rx_page_order); + rxb->page = NULL; + } + + /* Reuse the page if possible. For notification packets and + * SKBs that fail to Rx correctly, add them back into the + * rx_free list for reuse later. */ + if (rxb->page != NULL) { + rxb->page_dma = + dma_map_page(trans->dev, rxb->page, rxb->offset, + trans_pcie->rx_buf_bytes, + DMA_FROM_DEVICE); + if (dma_mapping_error(trans->dev, rxb->page_dma)) { + /* + * free the page(s) as well to not break + * the invariant that the items on the used + * list have no page(s) + */ + __free_pages(rxb->page, trans_pcie->rx_page_order); + rxb->page = NULL; + iwl_pcie_rx_reuse_rbd(trans, rxb, rxq, emergency); + } else { + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + } + } else + iwl_pcie_rx_reuse_rbd(trans, rxb, rxq, emergency); +} + +static struct iwl_rx_mem_buffer *iwl_pcie_get_rxb(struct iwl_trans *trans, + struct iwl_rxq *rxq, int i, + bool *join) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rx_mem_buffer *rxb; + u16 vid; + + BUILD_BUG_ON(sizeof(struct iwl_rx_completion_desc) != 32); + + if (!trans->trans_cfg->mq_rx_supported) { + rxb = rxq->queue[i]; + rxq->queue[i] = NULL; + return rxb; + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + vid = le16_to_cpu(rxq->cd[i].rbid); + *join = rxq->cd[i].flags & IWL_RX_CD_FLAGS_FRAGMENTED; + } else { + vid = le32_to_cpu(rxq->bd_32[i]) & 0x0FFF; /* 12-bit VID */ + } + + if (!vid || vid > RX_POOL_SIZE(trans_pcie->num_rx_bufs)) + goto out_err; + + rxb = trans_pcie->global_table[vid - 1]; + if (rxb->invalid) + goto out_err; + + IWL_DEBUG_RX(trans, "Got virtual RB ID %u\n", (u32)rxb->vid); + + rxb->invalid = true; + + return rxb; + +out_err: + WARN(1, "Invalid rxb from HW %u\n", (u32)vid); + iwl_force_nmi(trans); + return NULL; +} + +/* + * iwl_pcie_rx_handle - Main entry function for receiving responses from fw + */ +static int iwl_pcie_rx_handle(struct iwl_trans *trans, int queue, int budget) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq; + u32 r, i, count = 0, handled = 0; + bool emergency = false; + + if (WARN_ON_ONCE(!trans_pcie->rxq || !trans_pcie->rxq[queue].bd)) + return budget; + + rxq = &trans_pcie->rxq[queue]; + +restart: + spin_lock(&rxq->lock); + /* uCode's read index (stored in shared DRAM) indicates the last Rx + * buffer that the driver may process (last buffer filled by ucode). */ + r = le16_to_cpu(iwl_get_closed_rb_stts(trans, rxq)) & 0x0FFF; + i = rxq->read; + + /* W/A 9000 device step A0 wrap-around bug */ + r &= (rxq->queue_size - 1); + + /* Rx interrupt, but nothing sent from uCode */ + if (i == r) + IWL_DEBUG_RX(trans, "Q %d: HW = SW = %d\n", rxq->id, r); + + while (i != r && ++handled < budget) { + struct iwl_rb_allocator *rba = &trans_pcie->rba; + struct iwl_rx_mem_buffer *rxb; + /* number of RBDs still waiting for page allocation */ + u32 rb_pending_alloc = + atomic_read(&trans_pcie->rba.req_pending) * + RX_CLAIM_REQ_ALLOC; + bool join = false; + + if (unlikely(rb_pending_alloc >= rxq->queue_size / 2 && + !emergency)) { + iwl_pcie_rx_move_to_allocator(rxq, rba); + emergency = true; + IWL_DEBUG_TPT(trans, + "RX path is in emergency. Pending allocations %d\n", + rb_pending_alloc); + } + + IWL_DEBUG_RX(trans, "Q %d: HW = %d, SW = %d\n", rxq->id, r, i); + + rxb = iwl_pcie_get_rxb(trans, rxq, i, &join); + if (!rxb) + goto out; + + if (unlikely(join || rxq->next_rb_is_fragment)) { + rxq->next_rb_is_fragment = join; + /* + * We can only get a multi-RB in the following cases: + * - firmware issue, sending a too big notification + * - sniffer mode with a large A-MSDU + * - large MTU frames (>2k) + * since the multi-RB functionality is limited to newer + * hardware that cannot put multiple entries into a + * single RB. + * + * Right now, the higher layers aren't set up to deal + * with that, so discard all of these. + */ + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + } else { + iwl_pcie_rx_handle_rb(trans, rxq, rxb, emergency, i); + } + + i = (i + 1) & (rxq->queue_size - 1); + + /* + * If we have RX_CLAIM_REQ_ALLOC released rx buffers - + * try to claim the pre-allocated buffers from the allocator. + * If not ready - will try to reclaim next time. + * There is no need to reschedule work - allocator exits only + * on success + */ + if (rxq->used_count >= RX_CLAIM_REQ_ALLOC) + iwl_pcie_rx_allocator_get(trans, rxq); + + if (rxq->used_count % RX_CLAIM_REQ_ALLOC == 0 && !emergency) { + /* Add the remaining empty RBDs for allocator use */ + iwl_pcie_rx_move_to_allocator(rxq, rba); + } else if (emergency) { + count++; + if (count == 8) { + count = 0; + if (rb_pending_alloc < rxq->queue_size / 3) { + IWL_DEBUG_TPT(trans, + "RX path exited emergency. Pending allocations %d\n", + rb_pending_alloc); + emergency = false; + } + + rxq->read = i; + spin_unlock(&rxq->lock); + iwl_pcie_rxq_alloc_rbs(trans, GFP_ATOMIC, rxq); + iwl_pcie_rxq_restock(trans, rxq); + goto restart; + } + } + } +out: + /* Backtrack one entry */ + rxq->read = i; + spin_unlock(&rxq->lock); + + /* + * handle a case where in emergency there are some unallocated RBDs. + * those RBDs are in the used list, but are not tracked by the queue's + * used_count which counts allocator owned RBDs. + * unallocated emergency RBDs must be allocated on exit, otherwise + * when called again the function may not be in emergency mode and + * they will be handed to the allocator with no tracking in the RBD + * allocator counters, which will lead to them never being claimed back + * by the queue. + * by allocating them here, they are now in the queue free list, and + * will be restocked by the next call of iwl_pcie_rxq_restock. + */ + if (unlikely(emergency && count)) + iwl_pcie_rxq_alloc_rbs(trans, GFP_ATOMIC, rxq); + + iwl_pcie_rxq_restock(trans, rxq); + + return handled; +} + +static struct iwl_trans_pcie *iwl_pcie_get_trans_pcie(struct msix_entry *entry) +{ + u8 queue = entry->entry; + struct msix_entry *entries = entry - queue; + + return container_of(entries, struct iwl_trans_pcie, msix_entries[0]); +} + +/* + * iwl_pcie_rx_msix_handle - Main entry function for receiving responses from fw + * This interrupt handler should be used with RSS queue only. + */ +irqreturn_t iwl_pcie_irq_rx_msix_handler(int irq, void *dev_id) +{ + struct msix_entry *entry = dev_id; + struct iwl_trans_pcie *trans_pcie = iwl_pcie_get_trans_pcie(entry); + struct iwl_trans *trans = trans_pcie->trans; + struct iwl_rxq *rxq = &trans_pcie->rxq[entry->entry]; + + trace_iwlwifi_dev_irq_msix(trans->dev, entry, false, 0, 0); + + if (WARN_ON(entry->entry >= trans->num_rx_queues)) + return IRQ_NONE; + + if (WARN_ONCE(!rxq, + "[%d] Got MSI-X interrupt before we have Rx queues", + entry->entry)) + return IRQ_NONE; + + lock_map_acquire(&trans->sync_cmd_lockdep_map); + IWL_DEBUG_ISR(trans, "[%d] Got interrupt\n", entry->entry); + + local_bh_disable(); + if (napi_schedule_prep(&rxq->napi)) + __napi_schedule(&rxq->napi); + else + iwl_pcie_clear_irq(trans, entry->entry); + local_bh_enable(); + + lock_map_release(&trans->sync_cmd_lockdep_map); + + return IRQ_HANDLED; +} + +/* + * iwl_pcie_irq_handle_error - called for HW or SW error interrupt from card + */ +static void iwl_pcie_irq_handle_error(struct iwl_trans *trans) +{ + int i; + + /* W/A for WiFi/WiMAX coex and WiMAX own the RF */ + if (trans->cfg->internal_wimax_coex && + !trans->cfg->apmg_not_supported && + (!(iwl_read_prph(trans, APMG_CLK_CTRL_REG) & + APMS_CLK_VAL_MRB_FUNC_MODE) || + (iwl_read_prph(trans, APMG_PS_CTRL_REG) & + APMG_PS_CTRL_VAL_RESET_REQ))) { + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + iwl_op_mode_wimax_active(trans->op_mode); + wake_up(&trans->wait_command_queue); + return; + } + + for (i = 0; i < trans->trans_cfg->base_params->num_of_queues; i++) { + if (!trans->txqs.txq[i]) + continue; + del_timer(&trans->txqs.txq[i]->stuck_timer); + } + + /* The STATUS_FW_ERROR bit is set in this function. This must happen + * before we wake up the command caller, to ensure a proper cleanup. */ + iwl_trans_fw_error(trans, false); + + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + wake_up(&trans->wait_command_queue); +} + +static u32 iwl_pcie_int_cause_non_ict(struct iwl_trans *trans) +{ + u32 inta; + + lockdep_assert_held(&IWL_TRANS_GET_PCIE_TRANS(trans)->irq_lock); + + trace_iwlwifi_dev_irq(trans->dev); + + /* Discover which interrupts are active/pending */ + inta = iwl_read32(trans, CSR_INT); + + /* the thread will service interrupts and re-enable them */ + return inta; +} + +/* a device (PCI-E) page is 4096 bytes long */ +#define ICT_SHIFT 12 +#define ICT_SIZE (1 << ICT_SHIFT) +#define ICT_COUNT (ICT_SIZE / sizeof(u32)) + +/* interrupt handler using ict table, with this interrupt driver will + * stop using INTA register to get device's interrupt, reading this register + * is expensive, device will write interrupts in ICT dram table, increment + * index then will fire interrupt to driver, driver will OR all ICT table + * entries from current index up to table entry with 0 value. the result is + * the interrupt we need to service, driver will set the entries back to 0 and + * set index. + */ +static u32 iwl_pcie_int_cause_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 inta; + u32 val = 0; + u32 read; + + trace_iwlwifi_dev_irq(trans->dev); + + /* Ignore interrupt if there's nothing in NIC to service. + * This may be due to IRQ shared with another device, + * or due to sporadic interrupts thrown from our NIC. */ + read = le32_to_cpu(trans_pcie->ict_tbl[trans_pcie->ict_index]); + trace_iwlwifi_dev_ict_read(trans->dev, trans_pcie->ict_index, read); + if (!read) + return 0; + + /* + * Collect all entries up to the first 0, starting from ict_index; + * note we already read at ict_index. + */ + do { + val |= read; + IWL_DEBUG_ISR(trans, "ICT index %d value 0x%08X\n", + trans_pcie->ict_index, read); + trans_pcie->ict_tbl[trans_pcie->ict_index] = 0; + trans_pcie->ict_index = + ((trans_pcie->ict_index + 1) & (ICT_COUNT - 1)); + + read = le32_to_cpu(trans_pcie->ict_tbl[trans_pcie->ict_index]); + trace_iwlwifi_dev_ict_read(trans->dev, trans_pcie->ict_index, + read); + } while (read); + + /* We should not get this value, just ignore it. */ + if (val == 0xffffffff) + val = 0; + + /* + * this is a w/a for a h/w bug. the h/w bug may cause the Rx bit + * (bit 15 before shifting it to 31) to clear when using interrupt + * coalescing. fortunately, bits 18 and 19 stay set when this happens + * so we use them to decide on the real state of the Rx bit. + * In order words, bit 15 is set if bit 18 or bit 19 are set. + */ + if (val & 0xC0000) + val |= 0x8000; + + inta = (0xff & val) | ((0xff00 & val) << 16); + return inta; +} + +void iwl_pcie_handle_rfkill_irq(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + bool hw_rfkill, prev, report; + + mutex_lock(&trans_pcie->mutex); + prev = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + hw_rfkill = iwl_is_rfkill_set(trans); + if (hw_rfkill) { + set_bit(STATUS_RFKILL_OPMODE, &trans->status); + set_bit(STATUS_RFKILL_HW, &trans->status); + } + if (trans_pcie->opmode_down) + report = hw_rfkill; + else + report = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + + IWL_WARN(trans, "RF_KILL bit toggled to %s.\n", + hw_rfkill ? "disable radio" : "enable radio"); + + isr_stats->rfkill++; + + if (prev != report) + iwl_trans_pcie_rf_kill(trans, report); + mutex_unlock(&trans_pcie->mutex); + + if (hw_rfkill) { + if (test_and_clear_bit(STATUS_SYNC_HCMD_ACTIVE, + &trans->status)) + IWL_DEBUG_RF_KILL(trans, + "Rfkill while SYNC HCMD in flight\n"); + wake_up(&trans->wait_command_queue); + } else { + clear_bit(STATUS_RFKILL_HW, &trans->status); + if (trans_pcie->opmode_down) + clear_bit(STATUS_RFKILL_OPMODE, &trans->status); + } +} + +irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id) +{ + struct iwl_trans *trans = dev_id; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + u32 inta = 0; + u32 handled = 0; + bool polling = false; + + lock_map_acquire(&trans->sync_cmd_lockdep_map); + + spin_lock_bh(&trans_pcie->irq_lock); + + /* dram interrupt table not set yet, + * use legacy interrupt. + */ + if (likely(trans_pcie->use_ict)) + inta = iwl_pcie_int_cause_ict(trans); + else + inta = iwl_pcie_int_cause_non_ict(trans); + +#ifdef CONFIG_IWLWIFI_DEBUG + if (iwl_have_debug_level(IWL_DL_ISR)) { + IWL_DEBUG_ISR(trans, + "ISR inta 0x%08x, enabled 0x%08x(sw), enabled(hw) 0x%08x, fh 0x%08x\n", + inta, trans_pcie->inta_mask, + iwl_read32(trans, CSR_INT_MASK), + iwl_read32(trans, CSR_FH_INT_STATUS)); + if (inta & (~trans_pcie->inta_mask)) + IWL_DEBUG_ISR(trans, + "We got a masked interrupt (0x%08x)\n", + inta & (~trans_pcie->inta_mask)); + } +#endif + + inta &= trans_pcie->inta_mask; + + /* + * Ignore interrupt if there's nothing in NIC to service. + * This may be due to IRQ shared with another device, + * or due to sporadic interrupts thrown from our NIC. + */ + if (unlikely(!inta)) { + IWL_DEBUG_ISR(trans, "Ignore interrupt, inta == 0\n"); + /* + * Re-enable interrupts here since we don't + * have anything to service + */ + if (test_bit(STATUS_INT_ENABLED, &trans->status)) + _iwl_enable_interrupts(trans); + spin_unlock_bh(&trans_pcie->irq_lock); + lock_map_release(&trans->sync_cmd_lockdep_map); + return IRQ_NONE; + } + + if (unlikely(inta == 0xFFFFFFFF || (inta & 0xFFFFFFF0) == 0xa5a5a5a0)) { + /* + * Hardware disappeared. It might have + * already raised an interrupt. + */ + IWL_WARN(trans, "HARDWARE GONE?? INTA == 0x%08x\n", inta); + spin_unlock_bh(&trans_pcie->irq_lock); + goto out; + } + + /* Ack/clear/reset pending uCode interrupts. + * Note: Some bits in CSR_INT are "OR" of bits in CSR_FH_INT_STATUS, + */ + /* There is a hardware bug in the interrupt mask function that some + * interrupts (i.e. CSR_INT_BIT_SCD) can still be generated even if + * they are disabled in the CSR_INT_MASK register. Furthermore the + * ICT interrupt handling mechanism has another bug that might cause + * these unmasked interrupts fail to be detected. We workaround the + * hardware bugs here by ACKing all the possible interrupts so that + * interrupt coalescing can still be achieved. + */ + iwl_write32(trans, CSR_INT, inta | ~trans_pcie->inta_mask); + +#ifdef CONFIG_IWLWIFI_DEBUG + if (iwl_have_debug_level(IWL_DL_ISR)) + IWL_DEBUG_ISR(trans, "inta 0x%08x, enabled 0x%08x\n", + inta, iwl_read32(trans, CSR_INT_MASK)); +#endif + + spin_unlock_bh(&trans_pcie->irq_lock); + + /* Now service all interrupt bits discovered above. */ + if (inta & CSR_INT_BIT_HW_ERR) { + IWL_ERR(trans, "Hardware error detected. Restarting.\n"); + + /* Tell the device to stop sending interrupts */ + iwl_disable_interrupts(trans); + + isr_stats->hw++; + iwl_pcie_irq_handle_error(trans); + + handled |= CSR_INT_BIT_HW_ERR; + + goto out; + } + + /* NIC fires this, but we don't use it, redundant with WAKEUP */ + if (inta & CSR_INT_BIT_SCD) { + IWL_DEBUG_ISR(trans, + "Scheduler finished to transmit the frame/frames.\n"); + isr_stats->sch++; + } + + /* Alive notification via Rx interrupt will do the real work */ + if (inta & CSR_INT_BIT_ALIVE) { + IWL_DEBUG_ISR(trans, "Alive interrupt\n"); + isr_stats->alive++; + if (trans->trans_cfg->gen2) { + /* + * We can restock, since firmware configured + * the RFH + */ + iwl_pcie_rxmq_restock(trans, trans_pcie->rxq); + } + + handled |= CSR_INT_BIT_ALIVE; + } + + /* Safely ignore these bits for debug checks below */ + inta &= ~(CSR_INT_BIT_SCD | CSR_INT_BIT_ALIVE); + + /* HW RF KILL switch toggled */ + if (inta & CSR_INT_BIT_RF_KILL) { + iwl_pcie_handle_rfkill_irq(trans); + handled |= CSR_INT_BIT_RF_KILL; + } + + /* Chip got too hot and stopped itself */ + if (inta & CSR_INT_BIT_CT_KILL) { + IWL_ERR(trans, "Microcode CT kill error detected.\n"); + isr_stats->ctkill++; + handled |= CSR_INT_BIT_CT_KILL; + } + + /* Error detected by uCode */ + if (inta & CSR_INT_BIT_SW_ERR) { + IWL_ERR(trans, "Microcode SW error detected. " + " Restarting 0x%X.\n", inta); + isr_stats->sw++; + iwl_pcie_irq_handle_error(trans); + handled |= CSR_INT_BIT_SW_ERR; + } + + /* uCode wakes up after power-down sleep */ + if (inta & CSR_INT_BIT_WAKEUP) { + IWL_DEBUG_ISR(trans, "Wakeup interrupt\n"); + iwl_pcie_rxq_check_wrptr(trans); + iwl_pcie_txq_check_wrptrs(trans); + + isr_stats->wakeup++; + + handled |= CSR_INT_BIT_WAKEUP; + } + + /* All uCode command responses, including Tx command responses, + * Rx "responses" (frame-received notification), and other + * notifications from uCode come through here*/ + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX | + CSR_INT_BIT_RX_PERIODIC)) { + IWL_DEBUG_ISR(trans, "Rx interrupt\n"); + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX)) { + handled |= (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX); + iwl_write32(trans, CSR_FH_INT_STATUS, + CSR_FH_INT_RX_MASK); + } + if (inta & CSR_INT_BIT_RX_PERIODIC) { + handled |= CSR_INT_BIT_RX_PERIODIC; + iwl_write32(trans, + CSR_INT, CSR_INT_BIT_RX_PERIODIC); + } + /* Sending RX interrupt require many steps to be done in the + * the device: + * 1- write interrupt to current index in ICT table. + * 2- dma RX frame. + * 3- update RX shared data to indicate last write index. + * 4- send interrupt. + * This could lead to RX race, driver could receive RX interrupt + * but the shared data changes does not reflect this; + * periodic interrupt will detect any dangling Rx activity. + */ + + /* Disable periodic interrupt; we use it as just a one-shot. */ + iwl_write8(trans, CSR_INT_PERIODIC_REG, + CSR_INT_PERIODIC_DIS); + + /* + * Enable periodic interrupt in 8 msec only if we received + * real RX interrupt (instead of just periodic int), to catch + * any dangling Rx interrupt. If it was just the periodic + * interrupt, there was no dangling Rx activity, and no need + * to extend the periodic interrupt; one-shot is enough. + */ + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX)) + iwl_write8(trans, CSR_INT_PERIODIC_REG, + CSR_INT_PERIODIC_ENA); + + isr_stats->rx++; + + local_bh_disable(); + if (napi_schedule_prep(&trans_pcie->rxq[0].napi)) { + polling = true; + __napi_schedule(&trans_pcie->rxq[0].napi); + } + local_bh_enable(); + } + + /* This "Tx" DMA channel is used only for loading uCode */ + if (inta & CSR_INT_BIT_FH_TX) { + iwl_write32(trans, CSR_FH_INT_STATUS, CSR_FH_INT_TX_MASK); + IWL_DEBUG_ISR(trans, "uCode load interrupt\n"); + isr_stats->tx++; + handled |= CSR_INT_BIT_FH_TX; + /* Wake up uCode load routine, now that load is complete */ + trans_pcie->ucode_write_complete = true; + wake_up(&trans_pcie->ucode_write_waitq); + } + + if (inta & ~handled) { + IWL_ERR(trans, "Unhandled INTA bits 0x%08x\n", inta & ~handled); + isr_stats->unhandled++; + } + + if (inta & ~(trans_pcie->inta_mask)) { + IWL_WARN(trans, "Disabled INTA bits 0x%08x were pending\n", + inta & ~trans_pcie->inta_mask); + } + + if (!polling) { + spin_lock_bh(&trans_pcie->irq_lock); + /* only Re-enable all interrupt if disabled by irq */ + if (test_bit(STATUS_INT_ENABLED, &trans->status)) + _iwl_enable_interrupts(trans); + /* we are loading the firmware, enable FH_TX interrupt only */ + else if (handled & CSR_INT_BIT_FH_TX) + iwl_enable_fw_load_int(trans); + /* Re-enable RF_KILL if it occurred */ + else if (handled & CSR_INT_BIT_RF_KILL) + iwl_enable_rfkill_int(trans); + /* Re-enable the ALIVE / Rx interrupt if it occurred */ + else if (handled & (CSR_INT_BIT_ALIVE | CSR_INT_BIT_FH_RX)) + iwl_enable_fw_load_int_ctx_info(trans); + spin_unlock_bh(&trans_pcie->irq_lock); + } + +out: + lock_map_release(&trans->sync_cmd_lockdep_map); + return IRQ_HANDLED; +} + +/****************************************************************************** + * + * ICT functions + * + ******************************************************************************/ + +/* Free dram table */ +void iwl_pcie_free_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (trans_pcie->ict_tbl) { + dma_free_coherent(trans->dev, ICT_SIZE, + trans_pcie->ict_tbl, + trans_pcie->ict_tbl_dma); + trans_pcie->ict_tbl = NULL; + trans_pcie->ict_tbl_dma = 0; + } +} + +/* + * allocate dram shared table, it is an aligned memory + * block of ICT_SIZE. + * also reset all data related to ICT table interrupt. + */ +int iwl_pcie_alloc_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + trans_pcie->ict_tbl = + dma_alloc_coherent(trans->dev, ICT_SIZE, + &trans_pcie->ict_tbl_dma, GFP_KERNEL); + if (!trans_pcie->ict_tbl) + return -ENOMEM; + + /* just an API sanity check ... it is guaranteed to be aligned */ + if (WARN_ON(trans_pcie->ict_tbl_dma & (ICT_SIZE - 1))) { + iwl_pcie_free_ict(trans); + return -EINVAL; + } + + return 0; +} + +/* Device is going up inform it about using ICT interrupt table, + * also we need to tell the driver to start using ICT interrupt. + */ +void iwl_pcie_reset_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 val; + + if (!trans_pcie->ict_tbl) + return; + + spin_lock_bh(&trans_pcie->irq_lock); + _iwl_disable_interrupts(trans); + + memset(trans_pcie->ict_tbl, 0, ICT_SIZE); + + val = trans_pcie->ict_tbl_dma >> ICT_SHIFT; + + val |= CSR_DRAM_INT_TBL_ENABLE | + CSR_DRAM_INIT_TBL_WRAP_CHECK | + CSR_DRAM_INIT_TBL_WRITE_POINTER; + + IWL_DEBUG_ISR(trans, "CSR_DRAM_INT_TBL_REG =0x%x\n", val); + + iwl_write32(trans, CSR_DRAM_INT_TBL_REG, val); + trans_pcie->use_ict = true; + trans_pcie->ict_index = 0; + iwl_write32(trans, CSR_INT, trans_pcie->inta_mask); + _iwl_enable_interrupts(trans); + spin_unlock_bh(&trans_pcie->irq_lock); +} + +/* Device is going down disable ict interrupt usage */ +void iwl_pcie_disable_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock_bh(&trans_pcie->irq_lock); + trans_pcie->use_ict = false; + spin_unlock_bh(&trans_pcie->irq_lock); +} + +irqreturn_t iwl_pcie_isr(int irq, void *data) +{ + struct iwl_trans *trans = data; + + if (!trans) + return IRQ_NONE; + + /* Disable (but don't clear!) interrupts here to avoid + * back-to-back ISRs and sporadic interrupts from our NIC. + * If we have something to service, the tasklet will re-enable ints. + * If we *don't* have something, we'll re-enable before leaving here. + */ + iwl_write32(trans, CSR_INT_MASK, 0x00000000); + + return IRQ_WAKE_THREAD; +} + +irqreturn_t iwl_pcie_msix_isr(int irq, void *data) +{ + return IRQ_WAKE_THREAD; +} + +irqreturn_t iwl_pcie_irq_msix_handler(int irq, void *dev_id) +{ + struct msix_entry *entry = dev_id; + struct iwl_trans_pcie *trans_pcie = iwl_pcie_get_trans_pcie(entry); + struct iwl_trans *trans = trans_pcie->trans; + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + u32 inta_fh_msk = ~MSIX_FH_INT_CAUSES_DATA_QUEUE; + u32 inta_fh, inta_hw; + bool polling = false; + bool sw_err; + + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_NON_RX) + inta_fh_msk |= MSIX_FH_INT_CAUSES_Q0; + + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS) + inta_fh_msk |= MSIX_FH_INT_CAUSES_Q1; + + lock_map_acquire(&trans->sync_cmd_lockdep_map); + + spin_lock_bh(&trans_pcie->irq_lock); + inta_fh = iwl_read32(trans, CSR_MSIX_FH_INT_CAUSES_AD); + inta_hw = iwl_read32(trans, CSR_MSIX_HW_INT_CAUSES_AD); + /* + * Clear causes registers to avoid being handling the same cause. + */ + iwl_write32(trans, CSR_MSIX_FH_INT_CAUSES_AD, inta_fh & inta_fh_msk); + iwl_write32(trans, CSR_MSIX_HW_INT_CAUSES_AD, inta_hw); + spin_unlock_bh(&trans_pcie->irq_lock); + + trace_iwlwifi_dev_irq_msix(trans->dev, entry, true, inta_fh, inta_hw); + + if (unlikely(!(inta_fh | inta_hw))) { + IWL_DEBUG_ISR(trans, "Ignore interrupt, inta == 0\n"); + lock_map_release(&trans->sync_cmd_lockdep_map); + return IRQ_NONE; + } + +#ifdef CONFIG_IWLWIFI_DEBUG + if (iwl_have_debug_level(IWL_DL_ISR)) { + IWL_DEBUG_ISR(trans, + "ISR[%d] inta_fh 0x%08x, enabled (sw) 0x%08x (hw) 0x%08x\n", + entry->entry, inta_fh, trans_pcie->fh_mask, + iwl_read32(trans, CSR_MSIX_FH_INT_MASK_AD)); + if (inta_fh & ~trans_pcie->fh_mask) + IWL_DEBUG_ISR(trans, + "We got a masked interrupt (0x%08x)\n", + inta_fh & ~trans_pcie->fh_mask); + } +#endif + + inta_fh &= trans_pcie->fh_mask; + + if ((trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_NON_RX) && + inta_fh & MSIX_FH_INT_CAUSES_Q0) { + local_bh_disable(); + if (napi_schedule_prep(&trans_pcie->rxq[0].napi)) { + polling = true; + __napi_schedule(&trans_pcie->rxq[0].napi); + } + local_bh_enable(); + } + + if ((trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS) && + inta_fh & MSIX_FH_INT_CAUSES_Q1) { + local_bh_disable(); + if (napi_schedule_prep(&trans_pcie->rxq[1].napi)) { + polling = true; + __napi_schedule(&trans_pcie->rxq[1].napi); + } + local_bh_enable(); + } + + /* This "Tx" DMA channel is used only for loading uCode */ + if (inta_fh & MSIX_FH_INT_CAUSES_D2S_CH0_NUM) { + IWL_DEBUG_ISR(trans, "uCode load interrupt\n"); + isr_stats->tx++; + /* + * Wake up uCode load routine, + * now that load is complete + */ + trans_pcie->ucode_write_complete = true; + wake_up(&trans_pcie->ucode_write_waitq); + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + sw_err = inta_hw & MSIX_HW_INT_CAUSES_REG_SW_ERR_BZ; + else + sw_err = inta_hw & MSIX_HW_INT_CAUSES_REG_SW_ERR; + + /* Error detected by uCode */ + if ((inta_fh & MSIX_FH_INT_CAUSES_FH_ERR) || sw_err) { + IWL_ERR(trans, + "Microcode SW error detected. Restarting 0x%X.\n", + inta_fh); + isr_stats->sw++; + /* during FW reset flow report errors from there */ + if (trans_pcie->fw_reset_state == FW_RESET_REQUESTED) { + trans_pcie->fw_reset_state = FW_RESET_ERROR; + wake_up(&trans_pcie->fw_reset_waitq); + } else { + iwl_pcie_irq_handle_error(trans); + } + } + + /* After checking FH register check HW register */ +#ifdef CONFIG_IWLWIFI_DEBUG + if (iwl_have_debug_level(IWL_DL_ISR)) { + IWL_DEBUG_ISR(trans, + "ISR[%d] inta_hw 0x%08x, enabled (sw) 0x%08x (hw) 0x%08x\n", + entry->entry, inta_hw, trans_pcie->hw_mask, + iwl_read32(trans, CSR_MSIX_HW_INT_MASK_AD)); + if (inta_hw & ~trans_pcie->hw_mask) + IWL_DEBUG_ISR(trans, + "We got a masked interrupt 0x%08x\n", + inta_hw & ~trans_pcie->hw_mask); + } +#endif + + inta_hw &= trans_pcie->hw_mask; + + /* Alive notification via Rx interrupt will do the real work */ + if (inta_hw & MSIX_HW_INT_CAUSES_REG_ALIVE) { + IWL_DEBUG_ISR(trans, "Alive interrupt\n"); + isr_stats->alive++; + if (trans->trans_cfg->gen2) { + /* We can restock, since firmware configured the RFH */ + iwl_pcie_rxmq_restock(trans, trans_pcie->rxq); + } + } + + if (inta_hw & MSIX_HW_INT_CAUSES_REG_WAKEUP) { + u32 sleep_notif = + le32_to_cpu(trans_pcie->prph_info->sleep_notif); + if (sleep_notif == IWL_D3_SLEEP_STATUS_SUSPEND || + sleep_notif == IWL_D3_SLEEP_STATUS_RESUME) { + IWL_DEBUG_ISR(trans, + "Sx interrupt: sleep notification = 0x%x\n", + sleep_notif); + trans_pcie->sx_complete = true; + wake_up(&trans_pcie->sx_waitq); + } else { + /* uCode wakes up after power-down sleep */ + IWL_DEBUG_ISR(trans, "Wakeup interrupt\n"); + iwl_pcie_rxq_check_wrptr(trans); + iwl_pcie_txq_check_wrptrs(trans); + + isr_stats->wakeup++; + } + } + + /* Chip got too hot and stopped itself */ + if (inta_hw & MSIX_HW_INT_CAUSES_REG_CT_KILL) { + IWL_ERR(trans, "Microcode CT kill error detected.\n"); + isr_stats->ctkill++; + } + + /* HW RF KILL switch toggled */ + if (inta_hw & MSIX_HW_INT_CAUSES_REG_RF_KILL) + iwl_pcie_handle_rfkill_irq(trans); + + if (inta_hw & MSIX_HW_INT_CAUSES_REG_HW_ERR) { + IWL_ERR(trans, + "Hardware error detected. Restarting.\n"); + + isr_stats->hw++; + trans->dbg.hw_error = true; + iwl_pcie_irq_handle_error(trans); + } + + if (inta_hw & MSIX_HW_INT_CAUSES_REG_RESET_DONE) { + IWL_DEBUG_ISR(trans, "Reset flow completed\n"); + trans_pcie->fw_reset_state = FW_RESET_OK; + wake_up(&trans_pcie->fw_reset_waitq); + } + + if (!polling) + iwl_pcie_clear_irq(trans, entry->entry); + + lock_map_release(&trans->sync_cmd_lockdep_map); + + return IRQ_HANDLED; +} diff --git a/sys/contrib/dev/iwlwifi/pcie/trans-gen2.c b/sys/contrib/dev/iwlwifi/pcie/trans-gen2.c new file mode 100644 index 00000000000..e1b3ce6162c --- /dev/null +++ b/sys/contrib/dev/iwlwifi/pcie/trans-gen2.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#if defined(__FreeBSD__) +#include +#endif +#include "iwl-trans.h" +#include "iwl-prph.h" +#include "iwl-context-info.h" +#include "iwl-context-info-gen3.h" +#include "internal.h" +#include "fw/dbg.h" + +#define FW_RESET_TIMEOUT (HZ / 5) + +/* + * Start up NIC's basic functionality after it has been reset + * (e.g. after platform boot, or shutdown via iwl_pcie_apm_stop()) + * NOTE: This does not load uCode nor start the embedded processor + */ +int iwl_pcie_gen2_apm_init(struct iwl_trans *trans) +{ + int ret = 0; + + IWL_DEBUG_INFO(trans, "Init card's basic functions\n"); + + /* + * Use "set_bit" below rather than "write", to preserve any hardware + * bits already set by default after reset. + */ + + /* + * Disable L0s without affecting L1; + * don't wait for ICH L0s (ICH bug W/A) + */ + iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX); + + /* Set FH wait threshold to maximum (HW error during stress W/A) */ + iwl_set_bit(trans, CSR_DBG_HPET_MEM_REG, CSR_DBG_HPET_MEM_REG_VAL); + + /* + * Enable HAP INTA (interrupt from management bus) to + * wake device's PCI Express link L1a -> L0s + */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A); + + iwl_pcie_apm_config(trans); + + ret = iwl_finish_nic_init(trans); + if (ret) + return ret; + + set_bit(STATUS_DEVICE_ENABLED, &trans->status); + + return 0; +} + +static void iwl_pcie_gen2_apm_stop(struct iwl_trans *trans, bool op_mode_leave) +{ + IWL_DEBUG_INFO(trans, "Stop card, put in low power state\n"); + + if (op_mode_leave) { + if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + iwl_pcie_gen2_apm_init(trans); + + /* inform ME that we are leaving */ + iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PREPARE | + CSR_HW_IF_CONFIG_REG_ENABLE_PME); + mdelay(1); + iwl_clear_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + mdelay(5); + } + + clear_bit(STATUS_DEVICE_ENABLED, &trans->status); + + /* Stop device's DMA activity */ + iwl_pcie_apm_stop_master(trans); + + iwl_trans_sw_reset(trans); + + /* + * Clear "initialization complete" bit to move adapter from + * D0A* (powered-up Active) --> D0U* (Uninitialized) state. + */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_INIT); + else + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_INIT_DONE); +} + +static void iwl_trans_pcie_fw_reset_handshake(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + trans_pcie->fw_reset_state = FW_RESET_REQUESTED; + + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + iwl_write_umac_prph(trans, UREG_NIC_SET_NMI_DRIVER, + UREG_NIC_SET_NMI_DRIVER_RESET_HANDSHAKE); + else + iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6, + UREG_DOORBELL_TO_ISR6_RESET_HANDSHAKE); + + /* wait 200ms */ + ret = wait_event_timeout(trans_pcie->fw_reset_waitq, + trans_pcie->fw_reset_state != FW_RESET_REQUESTED, + FW_RESET_TIMEOUT); + if (!ret || trans_pcie->fw_reset_state == FW_RESET_ERROR) { + IWL_INFO(trans, + "firmware didn't ACK the reset - continue anyway\n"); + iwl_trans_fw_error(trans, true); + } + + trans_pcie->fw_reset_state = FW_RESET_IDLE; +} + +void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->mutex); + + if (trans_pcie->is_down) + return; + + if (trans->state >= IWL_TRANS_FW_STARTED) + if (trans_pcie->fw_reset_handshake) + iwl_trans_pcie_fw_reset_handshake(trans); + + trans_pcie->is_down = true; + + /* tell the device to stop sending interrupts */ + iwl_disable_interrupts(trans); + + /* device going down, Stop using ICT table */ + iwl_pcie_disable_ict(trans); + + /* + * If a HW restart happens during firmware loading, + * then the firmware loading might call this function + * and later it might be called again due to the + * restart. So don't process again if the device is + * already dead. + */ + if (test_and_clear_bit(STATUS_DEVICE_ENABLED, &trans->status)) { + IWL_DEBUG_INFO(trans, + "DEVICE_ENABLED bit was set and is now cleared\n"); + iwl_txq_gen2_tx_free(trans); + iwl_pcie_rx_stop(trans); + } + + iwl_pcie_ctxt_info_free_paging(trans); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + iwl_pcie_ctxt_info_gen3_free(trans, false); + else + iwl_pcie_ctxt_info_free(trans); + + /* Stop the device, and put it in low power state */ + iwl_pcie_gen2_apm_stop(trans, false); + + iwl_trans_sw_reset(trans); + + /* + * Upon stop, the IVAR table gets erased, so msi-x won't + * work. This causes a bug in RF-KILL flows, since the interrupt + * that enables radio won't fire on the correct irq, and the + * driver won't be able to handle the interrupt. + * Configure the IVAR table again after reset. + */ + iwl_pcie_conf_msix_hw(trans_pcie); + + /* + * Upon stop, the APM issues an interrupt if HW RF kill is set. + * This is a bug in certain verions of the hardware. + * Certain devices also keep sending HW RF kill interrupt all + * the time, unless the interrupt is ACKed even if the interrupt + * should be masked. Re-ACK all the interrupts here. + */ + iwl_disable_interrupts(trans); + + /* clear all status bits */ + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + clear_bit(STATUS_INT_ENABLED, &trans->status); + clear_bit(STATUS_TPOWER_PMI, &trans->status); + + /* + * Even if we stop the HW, we still want the RF kill + * interrupt + */ + iwl_enable_rfkill_int(trans); + + /* re-take ownership to prevent other users from stealing the device */ + iwl_pcie_prepare_card_hw(trans); +} + +void iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool was_in_rfkill; + + iwl_op_mode_time_point(trans->op_mode, + IWL_FW_INI_TIME_POINT_HOST_DEVICE_DISABLE, + NULL); + + mutex_lock(&trans_pcie->mutex); + trans_pcie->opmode_down = true; + was_in_rfkill = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + _iwl_trans_pcie_gen2_stop_device(trans); + iwl_trans_pcie_handle_stop_rfkill(trans, was_in_rfkill); + mutex_unlock(&trans_pcie->mutex); +} + +static int iwl_pcie_gen2_nic_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int queue_size = max_t(u32, IWL_CMD_QUEUE_SIZE, + trans->cfg->min_txq_size); + + /* TODO: most of the logic can be removed in A0 - but not in Z0 */ + spin_lock_bh(&trans_pcie->irq_lock); + iwl_pcie_gen2_apm_init(trans); + spin_unlock_bh(&trans_pcie->irq_lock); + + iwl_op_mode_nic_config(trans->op_mode); + + /* Allocate the RX queue, or reset if it is already allocated */ + if (iwl_pcie_gen2_rx_init(trans)) + return -ENOMEM; + + /* Allocate or reset and init all Tx and Command queues */ + if (iwl_txq_gen2_init(trans, trans->txqs.cmd.q_id, queue_size)) + return -ENOMEM; + + /* enable shadow regs in HW */ + iwl_set_bit(trans, CSR_MAC_SHADOW_REG_CTRL, 0x800FFFFF); + IWL_DEBUG_INFO(trans, "Enabling shadow registers in device\n"); + + return 0; +} + +static void iwl_pcie_get_rf_name(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + char *buf = trans_pcie->rf_name; + size_t buflen = sizeof(trans_pcie->rf_name); + size_t pos; + u32 version; + + if (buf[0]) + return; + + switch (CSR_HW_RFID_TYPE(trans->hw_rf_id)) { + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_JF): + pos = scnprintf(buf, buflen, "JF"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_GF): + pos = scnprintf(buf, buflen, "GF"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_GF4): + pos = scnprintf(buf, buflen, "GF4"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HR): + pos = scnprintf(buf, buflen, "HR"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HR1): + pos = scnprintf(buf, buflen, "HR1"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HRCDB): + pos = scnprintf(buf, buflen, "HRCDB"); + break; + default: + return; + } + + switch (CSR_HW_RFID_TYPE(trans->hw_rf_id)) { + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HR): + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HR1): + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HRCDB): + version = iwl_read_prph(trans, CNVI_MBOX_C); + switch (version) { + case 0x20000: + pos += scnprintf(buf + pos, buflen - pos, " B3"); + break; + case 0x120000: + pos += scnprintf(buf + pos, buflen - pos, " B5"); + break; + default: + pos += scnprintf(buf + pos, buflen - pos, + " (0x%x)", version); + break; + } + break; + default: + break; + } + + pos += scnprintf(buf + pos, buflen - pos, ", rfid=0x%x", + trans->hw_rf_id); + + IWL_INFO(trans, "Detected RF %s\n", buf); + + /* + * also add a \n for debugfs - need to do it after printing + * since our IWL_INFO machinery wants to see a static \n at + * the end of the string + */ + pos += scnprintf(buf + pos, buflen - pos, "\n"); +} + +void iwl_trans_pcie_gen2_fw_alive(struct iwl_trans *trans, u32 scd_addr) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + iwl_pcie_reset_ict(trans); + + /* make sure all queue are not stopped/used */ + memset(trans->txqs.queue_stopped, 0, + sizeof(trans->txqs.queue_stopped)); + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + /* now that we got alive we can free the fw image & the context info. + * paging memory cannot be freed included since FW will still use it + */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + iwl_pcie_ctxt_info_gen3_free(trans, true); + else + iwl_pcie_ctxt_info_free(trans); + + /* + * Re-enable all the interrupts, including the RF-Kill one, now that + * the firmware is alive. + */ + iwl_enable_interrupts(trans); + mutex_lock(&trans_pcie->mutex); + iwl_pcie_check_hw_rf_kill(trans); + + iwl_pcie_get_rf_name(trans); + mutex_unlock(&trans_pcie->mutex); +} + +static void iwl_pcie_set_ltr(struct iwl_trans *trans) +{ + u32 ltr_val = CSR_LTR_LONG_VAL_AD_NO_SNOOP_REQ | + u32_encode_bits(CSR_LTR_LONG_VAL_AD_SCALE_USEC, + CSR_LTR_LONG_VAL_AD_NO_SNOOP_SCALE) | + u32_encode_bits(250, + CSR_LTR_LONG_VAL_AD_NO_SNOOP_VAL) | + CSR_LTR_LONG_VAL_AD_SNOOP_REQ | + u32_encode_bits(CSR_LTR_LONG_VAL_AD_SCALE_USEC, + CSR_LTR_LONG_VAL_AD_SNOOP_SCALE) | + u32_encode_bits(250, CSR_LTR_LONG_VAL_AD_SNOOP_VAL); + + /* + * To workaround hardware latency issues during the boot process, + * initialize the LTR to ~250 usec (see ltr_val above). + * The firmware initializes this again later (to a smaller value). + */ + if ((trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_AX210 || + trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000) && + !trans->trans_cfg->integrated) { + iwl_write32(trans, CSR_LTR_LONG_VAL_AD, ltr_val); + } else if (trans->trans_cfg->integrated && + trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000) { + iwl_write_prph(trans, HPM_MAC_LTR_CSR, HPM_MAC_LRT_ENABLE_ALL); + iwl_write_prph(trans, HPM_UMAC_LTR, ltr_val); + } +} + +int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, bool run_in_rfkill) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool hw_rfkill; + int ret; + + /* This may fail if AMT took ownership of the device */ + if (iwl_pcie_prepare_card_hw(trans)) { + IWL_WARN(trans, "Exit HW not ready\n"); + ret = -EIO; + goto out; + } + + iwl_enable_rfkill_int(trans); + + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + + /* + * We enabled the RF-Kill interrupt and the handler may very + * well be running. Disable the interrupts to make sure no other + * interrupt can be fired. + */ + iwl_disable_interrupts(trans); + + /* Make sure it finished running */ + iwl_pcie_synchronize_irqs(trans); + + mutex_lock(&trans_pcie->mutex); + + /* If platform's RF_KILL switch is NOT set to KILL */ + hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); + if (hw_rfkill && !run_in_rfkill) { + ret = -ERFKILL; + goto out; + } + + /* Someone called stop_device, don't try to start_fw */ + if (trans_pcie->is_down) { + IWL_WARN(trans, + "Can't start_fw since the HW hasn't been started\n"); + ret = -EIO; + goto out; + } + + /* make sure rfkill handshake bits are cleared */ + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + /* clear (again), then enable host interrupts */ + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + + ret = iwl_pcie_gen2_nic_init(trans); + if (ret) { + IWL_ERR(trans, "Unable to init nic\n"); + goto out; + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + ret = iwl_pcie_ctxt_info_gen3_init(trans, fw); + else + ret = iwl_pcie_ctxt_info_init(trans, fw); + if (ret) + goto out; + + iwl_pcie_set_ltr(trans); + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + iwl_write32(trans, CSR_FUNC_SCRATCH, CSR_FUNC_SCRATCH_INIT_VALUE); + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_ROM_START); + } else if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + iwl_write_umac_prph(trans, UREG_CPU_INIT_RUN, 1); + } else { + iwl_write_prph(trans, UREG_CPU_INIT_RUN, 1); + } + + /* re-check RF-Kill state since we may have missed the interrupt */ + hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); + if (hw_rfkill && !run_in_rfkill) + ret = -ERFKILL; + +out: + mutex_unlock(&trans_pcie->mutex); + return ret; +} diff --git a/sys/contrib/dev/iwlwifi/pcie/trans.c b/sys/contrib/dev/iwlwifi/pcie/trans.c new file mode 100644 index 00000000000..ee3e6cbe27c --- /dev/null +++ b/sys/contrib/dev/iwlwifi/pcie/trans.c @@ -0,0 +1,3679 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2007-2015, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__FreeBSD__) +#include +#endif + +#include "iwl-drv.h" +#include "iwl-trans.h" +#include "iwl-csr.h" +#include "iwl-prph.h" +#include "iwl-scd.h" +#include "iwl-agn-hw.h" +#include "fw/error-dump.h" +#include "fw/dbg.h" +#include "fw/api/tx.h" +#include "internal.h" +#include "iwl-fh.h" +#include "iwl-context-info-gen3.h" + +/* extended range in FW SRAM */ +#define IWL_FW_MEM_EXTENDED_START 0x40000 +#define IWL_FW_MEM_EXTENDED_END 0x57FFF + +void iwl_trans_pcie_dump_regs(struct iwl_trans *trans) +{ +#define PCI_DUMP_SIZE 352 +#define PCI_MEM_DUMP_SIZE 64 +#define PCI_PARENT_DUMP_SIZE 524 +#define PREFIX_LEN 32 + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct pci_dev *pdev = trans_pcie->pci_dev; + u32 i, pos, alloc_size, *ptr, *buf; + char *prefix; + + if (trans_pcie->pcie_dbg_dumped_once) + return; + + /* Should be a multiple of 4 */ + BUILD_BUG_ON(PCI_DUMP_SIZE > 4096 || PCI_DUMP_SIZE & 0x3); + BUILD_BUG_ON(PCI_MEM_DUMP_SIZE > 4096 || PCI_MEM_DUMP_SIZE & 0x3); + BUILD_BUG_ON(PCI_PARENT_DUMP_SIZE > 4096 || PCI_PARENT_DUMP_SIZE & 0x3); + + /* Alloc a max size buffer */ + alloc_size = PCI_ERR_ROOT_ERR_SRC + 4 + PREFIX_LEN; + alloc_size = max_t(u32, alloc_size, PCI_DUMP_SIZE + PREFIX_LEN); + alloc_size = max_t(u32, alloc_size, PCI_MEM_DUMP_SIZE + PREFIX_LEN); + alloc_size = max_t(u32, alloc_size, PCI_PARENT_DUMP_SIZE + PREFIX_LEN); + + buf = kmalloc(alloc_size, GFP_ATOMIC); + if (!buf) + return; + prefix = (char *)buf + alloc_size - PREFIX_LEN; + + IWL_ERR(trans, "iwlwifi transaction failed, dumping registers\n"); + + /* Print wifi device registers */ + sprintf(prefix, "iwlwifi %s: ", pci_name(pdev)); + IWL_ERR(trans, "iwlwifi device config registers:\n"); + for (i = 0, ptr = buf; i < PCI_DUMP_SIZE; i += 4, ptr++) + if (pci_read_config_dword(pdev, i, ptr)) + goto err_read; + iwl_print_hex_dump(NULL, IWL_DL_ANY, prefix, (u8 *)buf, i); + + IWL_ERR(trans, "iwlwifi device memory mapped registers:\n"); + for (i = 0, ptr = buf; i < PCI_MEM_DUMP_SIZE; i += 4, ptr++) + *ptr = iwl_read32(trans, i); + iwl_print_hex_dump(NULL, IWL_DL_ANY, prefix, (u8 *)buf, i); + + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + if (pos) { + IWL_ERR(trans, "iwlwifi device AER capability structure:\n"); + for (i = 0, ptr = buf; i < PCI_ERR_ROOT_COMMAND; i += 4, ptr++) + if (pci_read_config_dword(pdev, pos + i, ptr)) + goto err_read; + iwl_print_hex_dump(NULL, IWL_DL_ANY, prefix, (u8 *)buf, i); + } + + /* Print parent device registers next */ + if (!pdev->bus->self) + goto out; + + pdev = pdev->bus->self; + sprintf(prefix, "iwlwifi %s: ", pci_name(pdev)); + + IWL_ERR(trans, "iwlwifi parent port (%s) config registers:\n", + pci_name(pdev)); + for (i = 0, ptr = buf; i < PCI_PARENT_DUMP_SIZE; i += 4, ptr++) + if (pci_read_config_dword(pdev, i, ptr)) + goto err_read; + iwl_print_hex_dump(NULL, IWL_DL_ANY, prefix, (u8 *)buf, i); + + /* Print root port AER registers */ + pos = 0; + pdev = pcie_find_root_port(pdev); + if (pdev) + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + if (pos) { + IWL_ERR(trans, "iwlwifi root port (%s) AER cap structure:\n", + pci_name(pdev)); + sprintf(prefix, "iwlwifi %s: ", pci_name(pdev)); + for (i = 0, ptr = buf; i <= PCI_ERR_ROOT_ERR_SRC; i += 4, ptr++) + if (pci_read_config_dword(pdev, pos + i, ptr)) + goto err_read; + iwl_print_hex_dump(NULL, IWL_DL_ANY, prefix, (u8 *)buf, i); + } + goto out; + +err_read: + iwl_print_hex_dump(NULL, IWL_DL_ANY, prefix, (u8 *)buf, i); + IWL_ERR(trans, "Read failed at 0x%X\n", i); +out: + trans_pcie->pcie_dbg_dumped_once = 1; + kfree(buf); +} + +static void iwl_trans_pcie_sw_reset(struct iwl_trans *trans) +{ + /* Reset entire device - do controller reset (results in SHRD_HW_RST) */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_SW_RESET); + else + iwl_set_bit(trans, CSR_RESET, + CSR_RESET_REG_FLAG_SW_RESET); + usleep_range(5000, 6000); +} + +static void iwl_pcie_free_fw_monitor(struct iwl_trans *trans) +{ + struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + + if (!fw_mon->size) + return; + + dma_free_coherent(trans->dev, fw_mon->size, fw_mon->block, + fw_mon->physical); + + fw_mon->block = NULL; + fw_mon->physical = 0; + fw_mon->size = 0; +} + +static void iwl_pcie_alloc_fw_monitor_block(struct iwl_trans *trans, + u8 max_power, u8 min_power) +{ + struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + void *block = NULL; + dma_addr_t physical = 0; + u32 size = 0; + u8 power; + + if (fw_mon->size) + return; + + for (power = max_power; power >= min_power; power--) { + size = BIT(power); + block = dma_alloc_coherent(trans->dev, size, &physical, + GFP_KERNEL | __GFP_NOWARN); + if (!block) + continue; + + IWL_INFO(trans, + "Allocated 0x%08x bytes for firmware monitor.\n", + size); + break; + } + + if (WARN_ON_ONCE(!block)) + return; + + if (power != max_power) + IWL_ERR(trans, + "Sorry - debug buffer is only %luK while you requested %luK\n", + (unsigned long)BIT(power - 10), + (unsigned long)BIT(max_power - 10)); + + fw_mon->block = block; + fw_mon->physical = physical; + fw_mon->size = size; +} + +void iwl_pcie_alloc_fw_monitor(struct iwl_trans *trans, u8 max_power) +{ + if (!max_power) { + /* default max_power is maximum */ + max_power = 26; + } else { + max_power += 11; + } + + if (WARN(max_power > 26, + "External buffer size for monitor is too big %d, check the FW TLV\n", + max_power)) + return; + + if (trans->dbg.fw_mon.size) + return; + + iwl_pcie_alloc_fw_monitor_block(trans, max_power, 11); +} + +static u32 iwl_trans_pcie_read_shr(struct iwl_trans *trans, u32 reg) +{ + iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_CTRL_REG, + ((reg & 0x0000ffff) | (2 << 28))); + return iwl_read32(trans, HEEP_CTRL_WRD_PCIEX_DATA_REG); +} + +static void iwl_trans_pcie_write_shr(struct iwl_trans *trans, u32 reg, u32 val) +{ + iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_DATA_REG, val); + iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_CTRL_REG, + ((reg & 0x0000ffff) | (3 << 28))); +} + +static void iwl_pcie_set_pwr(struct iwl_trans *trans, bool vaux) +{ + if (trans->cfg->apmg_not_supported) + return; + + if (vaux && pci_pme_capable(to_pci_dev(trans->dev), PCI_D3cold)) + iwl_set_bits_mask_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VAUX, + ~APMG_PS_CTRL_MSK_PWR_SRC); + else + iwl_set_bits_mask_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VMAIN, + ~APMG_PS_CTRL_MSK_PWR_SRC); +} + +/* PCI registers */ +#define PCI_CFG_RETRY_TIMEOUT 0x041 + +void iwl_pcie_apm_config(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u16 lctl; + u16 cap; + + /* + * L0S states have been found to be unstable with our devices + * and in newer hardware they are not officially supported at + * all, so we must always set the L0S_DISABLED bit. + */ + iwl_set_bit(trans, CSR_GIO_REG, CSR_GIO_REG_VAL_L0S_DISABLED); + + pcie_capability_read_word(trans_pcie->pci_dev, PCI_EXP_LNKCTL, &lctl); + trans->pm_support = !(lctl & PCI_EXP_LNKCTL_ASPM_L0S); + + pcie_capability_read_word(trans_pcie->pci_dev, PCI_EXP_DEVCTL2, &cap); + trans->ltr_enabled = cap & PCI_EXP_DEVCTL2_LTR_EN; + IWL_DEBUG_POWER(trans, "L1 %sabled - LTR %sabled\n", + (lctl & PCI_EXP_LNKCTL_ASPM_L1) ? "En" : "Dis", + trans->ltr_enabled ? "En" : "Dis"); +} + +/* + * Start up NIC's basic functionality after it has been reset + * (e.g. after platform boot, or shutdown via iwl_pcie_apm_stop()) + * NOTE: This does not load uCode nor start the embedded processor + */ +static int iwl_pcie_apm_init(struct iwl_trans *trans) +{ + int ret; + + IWL_DEBUG_INFO(trans, "Init card's basic functions\n"); + + /* + * Use "set_bit" below rather than "write", to preserve any hardware + * bits already set by default after reset. + */ + + /* Disable L0S exit timer (platform NMI Work/Around) */ + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) + iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER); + + /* + * Disable L0s without affecting L1; + * don't wait for ICH L0s (ICH bug W/A) + */ + iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX); + + /* Set FH wait threshold to maximum (HW error during stress W/A) */ + iwl_set_bit(trans, CSR_DBG_HPET_MEM_REG, CSR_DBG_HPET_MEM_REG_VAL); + + /* + * Enable HAP INTA (interrupt from management bus) to + * wake device's PCI Express link L1a -> L0s + */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A); + + iwl_pcie_apm_config(trans); + + /* Configure analog phase-lock-loop before activating to D0A */ + if (trans->trans_cfg->base_params->pll_cfg) + iwl_set_bit(trans, CSR_ANA_PLL_CFG, CSR50_ANA_PLL_CFG_VAL); + + ret = iwl_finish_nic_init(trans); + if (ret) + return ret; + + if (trans->cfg->host_interrupt_operation_mode) { + /* + * This is a bit of an abuse - This is needed for 7260 / 3160 + * only check host_interrupt_operation_mode even if this is + * not related to host_interrupt_operation_mode. + * + * Enable the oscillator to count wake up time for L1 exit. This + * consumes slightly more power (100uA) - but allows to be sure + * that we wake up from L1 on time. + * + * This looks weird: read twice the same register, discard the + * value, set a bit, and yet again, read that same register + * just to discard the value. But that's the way the hardware + * seems to like it. + */ + iwl_read_prph(trans, OSC_CLK); + iwl_read_prph(trans, OSC_CLK); + iwl_set_bits_prph(trans, OSC_CLK, OSC_CLK_FORCE_CONTROL); + iwl_read_prph(trans, OSC_CLK); + iwl_read_prph(trans, OSC_CLK); + } + + /* + * Enable DMA clock and wait for it to stabilize. + * + * Write to "CLK_EN_REG"; "1" bits enable clocks, while "0" + * bits do not disable clocks. This preserves any hardware + * bits already set by default in "CLK_CTRL_REG" after reset. + */ + if (!trans->cfg->apmg_not_supported) { + iwl_write_prph(trans, APMG_CLK_EN_REG, + APMG_CLK_VAL_DMA_CLK_RQT); + udelay(20); + + /* Disable L1-Active */ + iwl_set_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_L1_ACT_DIS); + + /* Clear the interrupt in APMG if the NIC is in RFKILL */ + iwl_write_prph(trans, APMG_RTC_INT_STT_REG, + APMG_RTC_INT_STT_RFKILL); + } + + set_bit(STATUS_DEVICE_ENABLED, &trans->status); + + return 0; +} + +/* + * Enable LP XTAL to avoid HW bug where device may consume much power if + * FW is not loaded after device reset. LP XTAL is disabled by default + * after device HW reset. Do it only if XTAL is fed by internal source. + * Configure device's "persistence" mode to avoid resetting XTAL again when + * SHRD_HW_RST occurs in S3. + */ +static void iwl_pcie_apm_lp_xtal_enable(struct iwl_trans *trans) +{ + int ret; + u32 apmg_gp1_reg; + u32 apmg_xtal_cfg_reg; + u32 dl_cfg_reg; + + /* Force XTAL ON */ + __iwl_trans_pcie_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_XTAL_ON); + + iwl_trans_pcie_sw_reset(trans); + + ret = iwl_finish_nic_init(trans); + if (WARN_ON(ret)) { + /* Release XTAL ON request */ + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_XTAL_ON); + return; + } + + /* + * Clear "disable persistence" to avoid LP XTAL resetting when + * SHRD_HW_RST is applied in S3. + */ + iwl_clear_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_PERSIST_DIS); + + /* + * Force APMG XTAL to be active to prevent its disabling by HW + * caused by APMG idle state. + */ + apmg_xtal_cfg_reg = iwl_trans_pcie_read_shr(trans, + SHR_APMG_XTAL_CFG_REG); + iwl_trans_pcie_write_shr(trans, SHR_APMG_XTAL_CFG_REG, + apmg_xtal_cfg_reg | + SHR_APMG_XTAL_CFG_XTAL_ON_REQ); + + iwl_trans_pcie_sw_reset(trans); + + /* Enable LP XTAL by indirect access through CSR */ + apmg_gp1_reg = iwl_trans_pcie_read_shr(trans, SHR_APMG_GP1_REG); + iwl_trans_pcie_write_shr(trans, SHR_APMG_GP1_REG, apmg_gp1_reg | + SHR_APMG_GP1_WF_XTAL_LP_EN | + SHR_APMG_GP1_CHICKEN_BIT_SELECT); + + /* Clear delay line clock power up */ + dl_cfg_reg = iwl_trans_pcie_read_shr(trans, SHR_APMG_DL_CFG_REG); + iwl_trans_pcie_write_shr(trans, SHR_APMG_DL_CFG_REG, dl_cfg_reg & + ~SHR_APMG_DL_CFG_DL_CLOCK_POWER_UP); + + /* + * Enable persistence mode to avoid LP XTAL resetting when + * SHRD_HW_RST is applied in S3. + */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PERSIST_MODE); + + /* + * Clear "initialization complete" bit to move adapter from + * D0A* (powered-up Active) --> D0U* (Uninitialized) state. + */ + iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + /* Activates XTAL resources monitor */ + __iwl_trans_pcie_set_bit(trans, CSR_MONITOR_CFG_REG, + CSR_MONITOR_XTAL_RESOURCES); + + /* Release XTAL ON request */ + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_XTAL_ON); + udelay(10); + + /* Release APMG XTAL */ + iwl_trans_pcie_write_shr(trans, SHR_APMG_XTAL_CFG_REG, + apmg_xtal_cfg_reg & + ~SHR_APMG_XTAL_CFG_XTAL_ON_REQ); +} + +void iwl_pcie_apm_stop_master(struct iwl_trans *trans) +{ + int ret; + + /* stop device's busmaster DMA activity */ + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_REQ); + + ret = iwl_poll_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_STATUS, + CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_STATUS, + 100); + msleep(100); + } else { + iwl_set_bit(trans, CSR_RESET, CSR_RESET_REG_FLAG_STOP_MASTER); + + ret = iwl_poll_bit(trans, CSR_RESET, + CSR_RESET_REG_FLAG_MASTER_DISABLED, + CSR_RESET_REG_FLAG_MASTER_DISABLED, 100); + } + + if (ret < 0) + IWL_WARN(trans, "Master Disable Timed Out, 100 usec\n"); + + IWL_DEBUG_INFO(trans, "stop master\n"); +} + +static void iwl_pcie_apm_stop(struct iwl_trans *trans, bool op_mode_leave) +{ + IWL_DEBUG_INFO(trans, "Stop card, put in low power state\n"); + + if (op_mode_leave) { + if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + iwl_pcie_apm_init(trans); + + /* inform ME that we are leaving */ + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000) + iwl_set_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_WAKE_ME); + else if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_8000) { + iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PREPARE | + CSR_HW_IF_CONFIG_REG_ENABLE_PME); + mdelay(1); + iwl_clear_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + } + mdelay(5); + } + + clear_bit(STATUS_DEVICE_ENABLED, &trans->status); + + /* Stop device's DMA activity */ + iwl_pcie_apm_stop_master(trans); + + if (trans->cfg->lp_xtal_workaround) { + iwl_pcie_apm_lp_xtal_enable(trans); + return; + } + + iwl_trans_pcie_sw_reset(trans); + + /* + * Clear "initialization complete" bit to move adapter from + * D0A* (powered-up Active) --> D0U* (Uninitialized) state. + */ + iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); +} + +static int iwl_pcie_nic_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + /* nic_init */ + spin_lock_bh(&trans_pcie->irq_lock); + ret = iwl_pcie_apm_init(trans); + spin_unlock_bh(&trans_pcie->irq_lock); + + if (ret) + return ret; + + iwl_pcie_set_pwr(trans, false); + + iwl_op_mode_nic_config(trans->op_mode); + + /* Allocate the RX queue, or reset if it is already allocated */ + ret = iwl_pcie_rx_init(trans); + if (ret) + return ret; + + /* Allocate or reset and init all Tx and Command queues */ + if (iwl_pcie_tx_init(trans)) { + iwl_pcie_rx_free(trans); + return -ENOMEM; + } + + if (trans->trans_cfg->base_params->shadow_reg_enable) { + /* enable shadow regs in HW */ + iwl_set_bit(trans, CSR_MAC_SHADOW_REG_CTRL, 0x800FFFFF); + IWL_DEBUG_INFO(trans, "Enabling shadow registers in device\n"); + } + + return 0; +} + +#define HW_READY_TIMEOUT (50) + +/* Note: returns poll_bit return value, which is >= 0 if success */ +static int iwl_pcie_set_hw_ready(struct iwl_trans *trans) +{ + int ret; + + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY); + + /* See if we got it */ + ret = iwl_poll_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY, + HW_READY_TIMEOUT); + + if (ret >= 0) + iwl_set_bit(trans, CSR_MBOX_SET_REG, CSR_MBOX_SET_REG_OS_ALIVE); + + IWL_DEBUG_INFO(trans, "hardware%s ready\n", ret < 0 ? " not" : ""); + return ret; +} + +/* Note: returns standard 0/-ERROR code */ +int iwl_pcie_prepare_card_hw(struct iwl_trans *trans) +{ + int ret; + int t = 0; + int iter; + + IWL_DEBUG_INFO(trans, "iwl_trans_prepare_card_hw enter\n"); + + ret = iwl_pcie_set_hw_ready(trans); + /* If the card is ready, exit 0 */ + if (ret >= 0) + return 0; + + iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + usleep_range(1000, 2000); + + for (iter = 0; iter < 10; iter++) { + /* If HW is not ready, prepare the conditions to check again */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PREPARE); + + do { + ret = iwl_pcie_set_hw_ready(trans); + if (ret >= 0) + return 0; + + usleep_range(200, 1000); + t += 200; + } while (t < 150000); + msleep(25); + } + + IWL_ERR(trans, "Couldn't prepare the card\n"); + + return ret; +} + +/* + * ucode + */ +static void iwl_pcie_load_firmware_chunk_fh(struct iwl_trans *trans, + u32 dst_addr, dma_addr_t phy_addr, + u32 byte_cnt) +{ + iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(FH_SRVC_CHNL), + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE); + + iwl_write32(trans, FH_SRVC_CHNL_SRAM_ADDR_REG(FH_SRVC_CHNL), + dst_addr); + + iwl_write32(trans, FH_TFDIB_CTRL0_REG(FH_SRVC_CHNL), + phy_addr & FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK); + + iwl_write32(trans, FH_TFDIB_CTRL1_REG(FH_SRVC_CHNL), + (iwl_get_dma_hi_addr(phy_addr) + << FH_MEM_TFDIB_REG1_ADDR_BITSHIFT) | byte_cnt); + + iwl_write32(trans, FH_TCSR_CHNL_TX_BUF_STS_REG(FH_SRVC_CHNL), + BIT(FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM) | + BIT(FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX) | + FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID); + + iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(FH_SRVC_CHNL), + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE | + FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD); +} + +static int iwl_pcie_load_firmware_chunk(struct iwl_trans *trans, + u32 dst_addr, dma_addr_t phy_addr, + u32 byte_cnt) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + trans_pcie->ucode_write_complete = false; + + if (!iwl_trans_grab_nic_access(trans)) + return -EIO; + + iwl_pcie_load_firmware_chunk_fh(trans, dst_addr, phy_addr, + byte_cnt); + iwl_trans_release_nic_access(trans); + + ret = wait_event_timeout(trans_pcie->ucode_write_waitq, + trans_pcie->ucode_write_complete, 5 * HZ); + if (!ret) { + IWL_ERR(trans, "Failed to load firmware chunk!\n"); + iwl_trans_pcie_dump_regs(trans); + return -ETIMEDOUT; + } + + return 0; +} + +static int iwl_pcie_load_section(struct iwl_trans *trans, u8 section_num, + const struct fw_desc *section) +{ + u8 *v_addr; + dma_addr_t p_addr; + u32 offset, chunk_sz = min_t(u32, FH_MEM_TB_MAX_LENGTH, section->len); + int ret = 0; + + IWL_DEBUG_FW(trans, "[%d] uCode section being loaded...\n", + section_num); + + v_addr = dma_alloc_coherent(trans->dev, chunk_sz, &p_addr, + GFP_KERNEL | __GFP_NOWARN); + if (!v_addr) { + IWL_DEBUG_INFO(trans, "Falling back to small chunks of DMA\n"); + chunk_sz = PAGE_SIZE; + v_addr = dma_alloc_coherent(trans->dev, chunk_sz, + &p_addr, GFP_KERNEL); + if (!v_addr) + return -ENOMEM; + } + + for (offset = 0; offset < section->len; offset += chunk_sz) { + u32 copy_size, dst_addr; + bool extended_addr = false; + + copy_size = min_t(u32, chunk_sz, section->len - offset); + dst_addr = section->offset + offset; + + if (dst_addr >= IWL_FW_MEM_EXTENDED_START && + dst_addr <= IWL_FW_MEM_EXTENDED_END) + extended_addr = true; + + if (extended_addr) + iwl_set_bits_prph(trans, LMPM_CHICK, + LMPM_CHICK_EXTENDED_ADDR_SPACE); + + memcpy(v_addr, (const u8 *)section->data + offset, copy_size); + ret = iwl_pcie_load_firmware_chunk(trans, dst_addr, p_addr, + copy_size); + + if (extended_addr) + iwl_clear_bits_prph(trans, LMPM_CHICK, + LMPM_CHICK_EXTENDED_ADDR_SPACE); + + if (ret) { + IWL_ERR(trans, + "Could not load the [%d] uCode section\n", + section_num); + break; + } + } + + dma_free_coherent(trans->dev, chunk_sz, v_addr, p_addr); + return ret; +} + +static int iwl_pcie_load_cpu_sections_8000(struct iwl_trans *trans, + const struct fw_img *image, + int cpu, + int *first_ucode_section) +{ + int shift_param; + int i, ret = 0, sec_num = 0x1; + u32 val, last_read_idx = 0; + + if (cpu == 1) { + shift_param = 0; + *first_ucode_section = 0; + } else { + shift_param = 16; + (*first_ucode_section)++; + } + + for (i = *first_ucode_section; i < image->num_sec; i++) { + last_read_idx = i; + + /* + * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between + * CPU1 to CPU2. + * PAGING_SEPARATOR_SECTION delimiter - separate between + * CPU2 non paged to CPU2 paging sec. + */ + if (!image->sec[i].data || + image->sec[i].offset == CPU1_CPU2_SEPARATOR_SECTION || + image->sec[i].offset == PAGING_SEPARATOR_SECTION) { + IWL_DEBUG_FW(trans, + "Break since Data not valid or Empty section, sec = %d\n", + i); + break; + } + + ret = iwl_pcie_load_section(trans, i, &image->sec[i]); + if (ret) + return ret; + + /* Notify ucode of loaded section number and status */ + val = iwl_read_direct32(trans, FH_UCODE_LOAD_STATUS); + val = val | (sec_num << shift_param); + iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS, val); + + sec_num = (sec_num << 1) | 0x1; + } + + *first_ucode_section = last_read_idx; + + iwl_enable_interrupts(trans); + + if (trans->trans_cfg->use_tfh) { + if (cpu == 1) + iwl_write_prph(trans, UREG_UCODE_LOAD_STATUS, + 0xFFFF); + else + iwl_write_prph(trans, UREG_UCODE_LOAD_STATUS, + 0xFFFFFFFF); + } else { + if (cpu == 1) + iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS, + 0xFFFF); + else + iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS, + 0xFFFFFFFF); + } + + return 0; +} + +static int iwl_pcie_load_cpu_sections(struct iwl_trans *trans, + const struct fw_img *image, + int cpu, + int *first_ucode_section) +{ + int i, ret = 0; + u32 last_read_idx = 0; + + if (cpu == 1) + *first_ucode_section = 0; + else + (*first_ucode_section)++; + + for (i = *first_ucode_section; i < image->num_sec; i++) { + last_read_idx = i; + + /* + * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between + * CPU1 to CPU2. + * PAGING_SEPARATOR_SECTION delimiter - separate between + * CPU2 non paged to CPU2 paging sec. + */ + if (!image->sec[i].data || + image->sec[i].offset == CPU1_CPU2_SEPARATOR_SECTION || + image->sec[i].offset == PAGING_SEPARATOR_SECTION) { + IWL_DEBUG_FW(trans, + "Break since Data not valid or Empty section, sec = %d\n", + i); + break; + } + + ret = iwl_pcie_load_section(trans, i, &image->sec[i]); + if (ret) + return ret; + } + + *first_ucode_section = last_read_idx; + + return 0; +} + +static void iwl_pcie_apply_destination_ini(struct iwl_trans *trans) +{ + enum iwl_fw_ini_allocation_id alloc_id = IWL_FW_INI_ALLOCATION_ID_DBGC1; + struct iwl_fw_ini_allocation_tlv *fw_mon_cfg = + &trans->dbg.fw_mon_cfg[alloc_id]; + struct iwl_dram_data *frag; + + if (!iwl_trans_dbg_ini_valid(trans)) + return; + + if (le32_to_cpu(fw_mon_cfg->buf_location) == + IWL_FW_INI_LOCATION_SRAM_PATH) { + IWL_DEBUG_FW(trans, "WRT: Applying SMEM buffer destination\n"); + /* set sram monitor by enabling bit 7 */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_MONITOR_SRAM); + + return; + } + + if (le32_to_cpu(fw_mon_cfg->buf_location) != + IWL_FW_INI_LOCATION_DRAM_PATH || + !trans->dbg.fw_mon_ini[alloc_id].num_frags) + return; + + frag = &trans->dbg.fw_mon_ini[alloc_id].frags[0]; + + IWL_DEBUG_FW(trans, "WRT: Applying DRAM destination (alloc_id=%u)\n", + alloc_id); + + iwl_write_umac_prph(trans, MON_BUFF_BASE_ADDR_VER2, + frag->physical >> MON_BUFF_SHIFT_VER2); + iwl_write_umac_prph(trans, MON_BUFF_END_ADDR_VER2, + (frag->physical + frag->size - 256) >> + MON_BUFF_SHIFT_VER2); +} + +void iwl_pcie_apply_destination(struct iwl_trans *trans) +{ + const struct iwl_fw_dbg_dest_tlv_v1 *dest = trans->dbg.dest_tlv; + const struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + int i; + + if (iwl_trans_dbg_ini_valid(trans)) { + iwl_pcie_apply_destination_ini(trans); + return; + } + + IWL_INFO(trans, "Applying debug destination %s\n", + get_fw_dbg_mode_string(dest->monitor_mode)); + + if (dest->monitor_mode == EXTERNAL_MODE) + iwl_pcie_alloc_fw_monitor(trans, dest->size_power); + else + IWL_WARN(trans, "PCI should have external buffer debug\n"); + + for (i = 0; i < trans->dbg.n_dest_reg; i++) { + u32 addr = le32_to_cpu(dest->reg_ops[i].addr); + u32 val = le32_to_cpu(dest->reg_ops[i].val); + + switch (dest->reg_ops[i].op) { + case CSR_ASSIGN: + iwl_write32(trans, addr, val); + break; + case CSR_SETBIT: + iwl_set_bit(trans, addr, BIT(val)); + break; + case CSR_CLEARBIT: + iwl_clear_bit(trans, addr, BIT(val)); + break; + case PRPH_ASSIGN: + iwl_write_prph(trans, addr, val); + break; + case PRPH_SETBIT: + iwl_set_bits_prph(trans, addr, BIT(val)); + break; + case PRPH_CLEARBIT: + iwl_clear_bits_prph(trans, addr, BIT(val)); + break; + case PRPH_BLOCKBIT: + if (iwl_read_prph(trans, addr) & BIT(val)) { + IWL_ERR(trans, + "BIT(%u) in address 0x%x is 1, stopping FW configuration\n", + val, addr); + goto monitor; + } + break; + default: + IWL_ERR(trans, "FW debug - unknown OP %d\n", + dest->reg_ops[i].op); + break; + } + } + +monitor: + if (dest->monitor_mode == EXTERNAL_MODE && fw_mon->size) { + iwl_write_prph(trans, le32_to_cpu(dest->base_reg), + fw_mon->physical >> dest->base_shift); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_8000) + iwl_write_prph(trans, le32_to_cpu(dest->end_reg), + (fw_mon->physical + fw_mon->size - + 256) >> dest->end_shift); + else + iwl_write_prph(trans, le32_to_cpu(dest->end_reg), + (fw_mon->physical + fw_mon->size) >> + dest->end_shift); + } +} + +static int iwl_pcie_load_given_ucode(struct iwl_trans *trans, + const struct fw_img *image) +{ + int ret = 0; + int first_ucode_section; + + IWL_DEBUG_FW(trans, "working with %s CPU\n", + image->is_dual_cpus ? "Dual" : "Single"); + + /* load to FW the binary non secured sections of CPU1 */ + ret = iwl_pcie_load_cpu_sections(trans, image, 1, &first_ucode_section); + if (ret) + return ret; + + if (image->is_dual_cpus) { + /* set CPU2 header address */ + iwl_write_prph(trans, + LMPM_SECURE_UCODE_LOAD_CPU2_HDR_ADDR, + LMPM_SECURE_CPU2_HDR_MEM_SPACE); + + /* load to FW the binary sections of CPU2 */ + ret = iwl_pcie_load_cpu_sections(trans, image, 2, + &first_ucode_section); + if (ret) + return ret; + } + + if (iwl_pcie_dbg_on(trans)) + iwl_pcie_apply_destination(trans); + + iwl_enable_interrupts(trans); + + /* release CPU reset */ + iwl_write32(trans, CSR_RESET, 0); + + return 0; +} + +static int iwl_pcie_load_given_ucode_8000(struct iwl_trans *trans, + const struct fw_img *image) +{ + int ret = 0; + int first_ucode_section; + + IWL_DEBUG_FW(trans, "working with %s CPU\n", + image->is_dual_cpus ? "Dual" : "Single"); + + if (iwl_pcie_dbg_on(trans)) + iwl_pcie_apply_destination(trans); + + IWL_DEBUG_POWER(trans, "Original WFPM value = 0x%08X\n", + iwl_read_prph(trans, WFPM_GP2)); + + /* + * Set default value. On resume reading the values that were + * zeored can provide debug data on the resume flow. + * This is for debugging only and has no functional impact. + */ + iwl_write_prph(trans, WFPM_GP2, 0x01010101); + + /* configure the ucode to be ready to get the secured image */ + /* release CPU reset */ + iwl_write_prph(trans, RELEASE_CPU_RESET, RELEASE_CPU_RESET_BIT); + + /* load to FW the binary Secured sections of CPU1 */ + ret = iwl_pcie_load_cpu_sections_8000(trans, image, 1, + &first_ucode_section); + if (ret) + return ret; + + /* load to FW the binary sections of CPU2 */ + return iwl_pcie_load_cpu_sections_8000(trans, image, 2, + &first_ucode_section); +} + +bool iwl_pcie_check_hw_rf_kill(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool hw_rfkill = iwl_is_rfkill_set(trans); + bool prev = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + bool report; + + if (hw_rfkill) { + set_bit(STATUS_RFKILL_HW, &trans->status); + set_bit(STATUS_RFKILL_OPMODE, &trans->status); + } else { + clear_bit(STATUS_RFKILL_HW, &trans->status); + if (trans_pcie->opmode_down) + clear_bit(STATUS_RFKILL_OPMODE, &trans->status); + } + + report = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + + if (prev != report) + iwl_trans_pcie_rf_kill(trans, report); + + return hw_rfkill; +} + +struct iwl_causes_list { + u32 cause_num; + u32 mask_reg; + u8 addr; +}; + +static const struct iwl_causes_list causes_list_common[] = { + {MSIX_FH_INT_CAUSES_D2S_CH0_NUM, CSR_MSIX_FH_INT_MASK_AD, 0}, + {MSIX_FH_INT_CAUSES_D2S_CH1_NUM, CSR_MSIX_FH_INT_MASK_AD, 0x1}, + {MSIX_FH_INT_CAUSES_S2D, CSR_MSIX_FH_INT_MASK_AD, 0x3}, + {MSIX_FH_INT_CAUSES_FH_ERR, CSR_MSIX_FH_INT_MASK_AD, 0x5}, + {MSIX_HW_INT_CAUSES_REG_ALIVE, CSR_MSIX_HW_INT_MASK_AD, 0x10}, + {MSIX_HW_INT_CAUSES_REG_WAKEUP, CSR_MSIX_HW_INT_MASK_AD, 0x11}, + {MSIX_HW_INT_CAUSES_REG_RESET_DONE, CSR_MSIX_HW_INT_MASK_AD, 0x12}, + {MSIX_HW_INT_CAUSES_REG_CT_KILL, CSR_MSIX_HW_INT_MASK_AD, 0x16}, + {MSIX_HW_INT_CAUSES_REG_RF_KILL, CSR_MSIX_HW_INT_MASK_AD, 0x17}, + {MSIX_HW_INT_CAUSES_REG_PERIODIC, CSR_MSIX_HW_INT_MASK_AD, 0x18}, + {MSIX_HW_INT_CAUSES_REG_SCD, CSR_MSIX_HW_INT_MASK_AD, 0x2A}, + {MSIX_HW_INT_CAUSES_REG_FH_TX, CSR_MSIX_HW_INT_MASK_AD, 0x2B}, + {MSIX_HW_INT_CAUSES_REG_HW_ERR, CSR_MSIX_HW_INT_MASK_AD, 0x2D}, + {MSIX_HW_INT_CAUSES_REG_HAP, CSR_MSIX_HW_INT_MASK_AD, 0x2E}, +}; + +static const struct iwl_causes_list causes_list_pre_bz[] = { + {MSIX_HW_INT_CAUSES_REG_SW_ERR, CSR_MSIX_HW_INT_MASK_AD, 0x29}, +}; + +static const struct iwl_causes_list causes_list_bz[] = { + {MSIX_HW_INT_CAUSES_REG_SW_ERR_BZ, CSR_MSIX_HW_INT_MASK_AD, 0x29}, +}; + +static void iwl_pcie_map_list(struct iwl_trans *trans, + const struct iwl_causes_list *causes, + int arr_size, int val) +{ + int i; + + for (i = 0; i < arr_size; i++) { + iwl_write8(trans, CSR_MSIX_IVAR(causes[i].addr), val); + iwl_clear_bit(trans, causes[i].mask_reg, + causes[i].cause_num); + } +} + +static void iwl_pcie_map_non_rx_causes(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int val = trans_pcie->def_irq | MSIX_NON_AUTO_CLEAR_CAUSE; + /* + * Access all non RX causes and map them to the default irq. + * In case we are missing at least one interrupt vector, + * the first interrupt vector will serve non-RX and FBQ causes. + */ + iwl_pcie_map_list(trans, causes_list_common, + ARRAY_SIZE(causes_list_common), val); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_pcie_map_list(trans, causes_list_bz, + ARRAY_SIZE(causes_list_bz), val); + else + iwl_pcie_map_list(trans, causes_list_pre_bz, + ARRAY_SIZE(causes_list_pre_bz), val); +} + +static void iwl_pcie_map_rx_causes(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 offset = + trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS ? 1 : 0; + u32 val, idx; + + /* + * The first RX queue - fallback queue, which is designated for + * management frame, command responses etc, is always mapped to the + * first interrupt vector. The other RX queues are mapped to + * the other (N - 2) interrupt vectors. + */ + val = BIT(MSIX_FH_INT_CAUSES_Q(0)); + for (idx = 1; idx < trans->num_rx_queues; idx++) { + iwl_write8(trans, CSR_MSIX_RX_IVAR(idx), + MSIX_FH_INT_CAUSES_Q(idx - offset)); + val |= BIT(MSIX_FH_INT_CAUSES_Q(idx)); + } + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, ~val); + + val = MSIX_FH_INT_CAUSES_Q(0); + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_NON_RX) + val |= MSIX_NON_AUTO_CLEAR_CAUSE; + iwl_write8(trans, CSR_MSIX_RX_IVAR(0), val); + + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS) + iwl_write8(trans, CSR_MSIX_RX_IVAR(1), val); +} + +void iwl_pcie_conf_msix_hw(struct iwl_trans_pcie *trans_pcie) +{ + struct iwl_trans *trans = trans_pcie->trans; + + if (!trans_pcie->msix_enabled) { + if (trans->trans_cfg->mq_rx_supported && + test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + iwl_write_umac_prph(trans, UREG_CHICK, + UREG_CHICK_MSI_ENABLE); + return; + } + /* + * The IVAR table needs to be configured again after reset, + * but if the device is disabled, we can't write to + * prph. + */ + if (test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + iwl_write_umac_prph(trans, UREG_CHICK, UREG_CHICK_MSIX_ENABLE); + + /* + * Each cause from the causes list above and the RX causes is + * represented as a byte in the IVAR table. The first nibble + * represents the bound interrupt vector of the cause, the second + * represents no auto clear for this cause. This will be set if its + * interrupt vector is bound to serve other causes. + */ + iwl_pcie_map_rx_causes(trans); + + iwl_pcie_map_non_rx_causes(trans); +} + +static void iwl_pcie_init_msix(struct iwl_trans_pcie *trans_pcie) +{ + struct iwl_trans *trans = trans_pcie->trans; + + iwl_pcie_conf_msix_hw(trans_pcie); + + if (!trans_pcie->msix_enabled) + return; + + trans_pcie->fh_init_mask = ~iwl_read32(trans, CSR_MSIX_FH_INT_MASK_AD); + trans_pcie->fh_mask = trans_pcie->fh_init_mask; + trans_pcie->hw_init_mask = ~iwl_read32(trans, CSR_MSIX_HW_INT_MASK_AD); + trans_pcie->hw_mask = trans_pcie->hw_init_mask; +} + +static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->mutex); + + if (trans_pcie->is_down) + return; + + trans_pcie->is_down = true; + + /* tell the device to stop sending interrupts */ + iwl_disable_interrupts(trans); + + /* device going down, Stop using ICT table */ + iwl_pcie_disable_ict(trans); + + /* + * If a HW restart happens during firmware loading, + * then the firmware loading might call this function + * and later it might be called again due to the + * restart. So don't process again if the device is + * already dead. + */ + if (test_and_clear_bit(STATUS_DEVICE_ENABLED, &trans->status)) { + IWL_DEBUG_INFO(trans, + "DEVICE_ENABLED bit was set and is now cleared\n"); + iwl_pcie_tx_stop(trans); + iwl_pcie_rx_stop(trans); + + /* Power-down device's busmaster DMA clocks */ + if (!trans->cfg->apmg_not_supported) { + iwl_write_prph(trans, APMG_CLK_DIS_REG, + APMG_CLK_VAL_DMA_CLK_RQT); + udelay(5); + } + } + + /* Make sure (redundant) we've released our request to stay awake */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ); + else + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + + /* Stop the device, and put it in low power state */ + iwl_pcie_apm_stop(trans, false); + + iwl_trans_pcie_sw_reset(trans); + + /* + * Upon stop, the IVAR table gets erased, so msi-x won't + * work. This causes a bug in RF-KILL flows, since the interrupt + * that enables radio won't fire on the correct irq, and the + * driver won't be able to handle the interrupt. + * Configure the IVAR table again after reset. + */ + iwl_pcie_conf_msix_hw(trans_pcie); + + /* + * Upon stop, the APM issues an interrupt if HW RF kill is set. + * This is a bug in certain verions of the hardware. + * Certain devices also keep sending HW RF kill interrupt all + * the time, unless the interrupt is ACKed even if the interrupt + * should be masked. Re-ACK all the interrupts here. + */ + iwl_disable_interrupts(trans); + + /* clear all status bits */ + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + clear_bit(STATUS_INT_ENABLED, &trans->status); + clear_bit(STATUS_TPOWER_PMI, &trans->status); + + /* + * Even if we stop the HW, we still want the RF kill + * interrupt + */ + iwl_enable_rfkill_int(trans); + + /* re-take ownership to prevent other users from stealing the device */ + iwl_pcie_prepare_card_hw(trans); +} + +void iwl_pcie_synchronize_irqs(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (trans_pcie->msix_enabled) { + int i; + + for (i = 0; i < trans_pcie->alloc_vecs; i++) + synchronize_irq(trans_pcie->msix_entries[i].vector); + } else { + synchronize_irq(trans_pcie->pci_dev->irq); + } +} + +static int iwl_trans_pcie_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, bool run_in_rfkill) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool hw_rfkill; + int ret; + + /* This may fail if AMT took ownership of the device */ + if (iwl_pcie_prepare_card_hw(trans)) { + IWL_WARN(trans, "Exit HW not ready\n"); + ret = -EIO; + goto out; + } + + iwl_enable_rfkill_int(trans); + + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + + /* + * We enabled the RF-Kill interrupt and the handler may very + * well be running. Disable the interrupts to make sure no other + * interrupt can be fired. + */ + iwl_disable_interrupts(trans); + + /* Make sure it finished running */ + iwl_pcie_synchronize_irqs(trans); + + mutex_lock(&trans_pcie->mutex); + + /* If platform's RF_KILL switch is NOT set to KILL */ + hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); + if (hw_rfkill && !run_in_rfkill) { + ret = -ERFKILL; + goto out; + } + + /* Someone called stop_device, don't try to start_fw */ + if (trans_pcie->is_down) { + IWL_WARN(trans, + "Can't start_fw since the HW hasn't been started\n"); + ret = -EIO; + goto out; + } + + /* make sure rfkill handshake bits are cleared */ + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + /* clear (again), then enable host interrupts */ + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + + ret = iwl_pcie_nic_init(trans); + if (ret) { + IWL_ERR(trans, "Unable to init nic\n"); + goto out; + } + + /* + * Now, we load the firmware and don't want to be interrupted, even + * by the RF-Kill interrupt (hence mask all the interrupt besides the + * FH_TX interrupt which is needed to load the firmware). If the + * RF-Kill switch is toggled, we will find out after having loaded + * the firmware and return the proper value to the caller. + */ + iwl_enable_fw_load_int(trans); + + /* really make sure rfkill handshake bits are cleared */ + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + + /* Load the given image to the HW */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_8000) + ret = iwl_pcie_load_given_ucode_8000(trans, fw); + else + ret = iwl_pcie_load_given_ucode(trans, fw); + + /* re-check RF-Kill state since we may have missed the interrupt */ + hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); + if (hw_rfkill && !run_in_rfkill) + ret = -ERFKILL; + +out: + mutex_unlock(&trans_pcie->mutex); + return ret; +} + +static void iwl_trans_pcie_fw_alive(struct iwl_trans *trans, u32 scd_addr) +{ + iwl_pcie_reset_ict(trans); + iwl_pcie_tx_start(trans, scd_addr); +} + +void iwl_trans_pcie_handle_stop_rfkill(struct iwl_trans *trans, + bool was_in_rfkill) +{ + bool hw_rfkill; + + /* + * Check again since the RF kill state may have changed while + * all the interrupts were disabled, in this case we couldn't + * receive the RF kill interrupt and update the state in the + * op_mode. + * Don't call the op_mode if the rkfill state hasn't changed. + * This allows the op_mode to call stop_device from the rfkill + * notification without endless recursion. Under very rare + * circumstances, we might have a small recursion if the rfkill + * state changed exactly now while we were called from stop_device. + * This is very unlikely but can happen and is supported. + */ + hw_rfkill = iwl_is_rfkill_set(trans); + if (hw_rfkill) { + set_bit(STATUS_RFKILL_HW, &trans->status); + set_bit(STATUS_RFKILL_OPMODE, &trans->status); + } else { + clear_bit(STATUS_RFKILL_HW, &trans->status); + clear_bit(STATUS_RFKILL_OPMODE, &trans->status); + } + if (hw_rfkill != was_in_rfkill) + iwl_trans_pcie_rf_kill(trans, hw_rfkill); +} + +static void iwl_trans_pcie_stop_device(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool was_in_rfkill; + + iwl_op_mode_time_point(trans->op_mode, + IWL_FW_INI_TIME_POINT_HOST_DEVICE_DISABLE, + NULL); + + mutex_lock(&trans_pcie->mutex); + trans_pcie->opmode_down = true; + was_in_rfkill = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + _iwl_trans_pcie_stop_device(trans); + iwl_trans_pcie_handle_stop_rfkill(trans, was_in_rfkill); + mutex_unlock(&trans_pcie->mutex); +} + +void iwl_trans_pcie_rf_kill(struct iwl_trans *trans, bool state) +{ + struct iwl_trans_pcie __maybe_unused *trans_pcie = + IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->mutex); + + IWL_WARN(trans, "reporting RF_KILL (radio %s)\n", + state ? "disabled" : "enabled"); + if (iwl_op_mode_hw_rf_kill(trans->op_mode, state)) { + if (trans->trans_cfg->gen2) + _iwl_trans_pcie_gen2_stop_device(trans); + else + _iwl_trans_pcie_stop_device(trans); + } +} + +void iwl_pcie_d3_complete_suspend(struct iwl_trans *trans, + bool test, bool reset) +{ + iwl_disable_interrupts(trans); + + /* + * in testing mode, the host stays awake and the + * hardware won't be reset (not even partially) + */ + if (test) + return; + + iwl_pcie_disable_ict(trans); + + iwl_pcie_synchronize_irqs(trans); + + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + if (reset) { + /* + * reset TX queues -- some of their registers reset during S3 + * so if we don't reset everything here the D3 image would try + * to execute some invalid memory upon resume + */ + iwl_trans_pcie_tx_reset(trans); + } + + iwl_pcie_set_pwr(trans, true); +} + +static int iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test, + bool reset) +{ + int ret; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!reset) + /* Enable persistence mode to avoid reset */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PERSIST_MODE); + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6, + UREG_DOORBELL_TO_ISR6_SUSPEND); + + ret = wait_event_timeout(trans_pcie->sx_waitq, + trans_pcie->sx_complete, 2 * HZ); + /* + * Invalidate it toward resume. + */ + trans_pcie->sx_complete = false; + + if (!ret) { + IWL_ERR(trans, "Timeout entering D3\n"); + return -ETIMEDOUT; + } + } + iwl_pcie_d3_complete_suspend(trans, test, reset); + + return 0; +} + +static int iwl_trans_pcie_d3_resume(struct iwl_trans *trans, + enum iwl_d3_status *status, + bool test, bool reset) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 val; + int ret; + + if (test) { + iwl_enable_interrupts(trans); + *status = IWL_D3_STATUS_ALIVE; + goto out; + } + + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + + ret = iwl_finish_nic_init(trans); + if (ret) + return ret; + + /* + * Reconfigure IVAR table in case of MSIX or reset ict table in + * MSI mode since HW reset erased it. + * Also enables interrupts - none will happen as + * the device doesn't know we're waking it up, only when + * the opmode actually tells it after this call. + */ + iwl_pcie_conf_msix_hw(trans_pcie); + if (!trans_pcie->msix_enabled) + iwl_pcie_reset_ict(trans); + iwl_enable_interrupts(trans); + + iwl_pcie_set_pwr(trans, false); + + if (!reset) { + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + } else { + iwl_trans_pcie_tx_reset(trans); + + ret = iwl_pcie_rx_init(trans); + if (ret) { + IWL_ERR(trans, + "Failed to resume the device (RX reset)\n"); + return ret; + } + } + + IWL_DEBUG_POWER(trans, "WFPM value upon resume = 0x%08X\n", + iwl_read_umac_prph(trans, WFPM_GP2)); + + val = iwl_read32(trans, CSR_RESET); + if (val & CSR_RESET_REG_FLAG_NEVO_RESET) + *status = IWL_D3_STATUS_RESET; + else + *status = IWL_D3_STATUS_ALIVE; + +out: + if (*status == IWL_D3_STATUS_ALIVE && + trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + trans_pcie->sx_complete = false; + iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6, + UREG_DOORBELL_TO_ISR6_RESUME); + + ret = wait_event_timeout(trans_pcie->sx_waitq, + trans_pcie->sx_complete, 2 * HZ); + /* + * Invalidate it toward next suspend. + */ + trans_pcie->sx_complete = false; + + if (!ret) { + IWL_ERR(trans, "Timeout exiting D3\n"); + return -ETIMEDOUT; + } + } + return 0; +} + +static void +iwl_pcie_set_interrupt_capa(struct pci_dev *pdev, + struct iwl_trans *trans, + const struct iwl_cfg_trans_params *cfg_trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int max_irqs, num_irqs, i, ret; + u16 pci_cmd; + u32 max_rx_queues = IWL_MAX_RX_HW_QUEUES; + + if (!cfg_trans->mq_rx_supported) + goto enable_msi; + + if (cfg_trans->device_family <= IWL_DEVICE_FAMILY_9000) + max_rx_queues = IWL_9000_MAX_RX_HW_QUEUES; + + max_irqs = min_t(u32, num_online_cpus() + 2, max_rx_queues); + for (i = 0; i < max_irqs; i++) + trans_pcie->msix_entries[i].entry = i; + + num_irqs = pci_enable_msix_range(pdev, trans_pcie->msix_entries, + MSIX_MIN_INTERRUPT_VECTORS, + max_irqs); + if (num_irqs < 0) { + IWL_DEBUG_INFO(trans, + "Failed to enable msi-x mode (ret %d). Moving to msi mode.\n", + num_irqs); + goto enable_msi; + } + trans_pcie->def_irq = (num_irqs == max_irqs) ? num_irqs - 1 : 0; + + IWL_DEBUG_INFO(trans, + "MSI-X enabled. %d interrupt vectors were allocated\n", + num_irqs); + + /* + * In case the OS provides fewer interrupts than requested, different + * causes will share the same interrupt vector as follows: + * One interrupt less: non rx causes shared with FBQ. + * Two interrupts less: non rx causes shared with FBQ and RSS. + * More than two interrupts: we will use fewer RSS queues. + */ + if (num_irqs <= max_irqs - 2) { + trans_pcie->trans->num_rx_queues = num_irqs + 1; + trans_pcie->shared_vec_mask = IWL_SHARED_IRQ_NON_RX | + IWL_SHARED_IRQ_FIRST_RSS; + } else if (num_irqs == max_irqs - 1) { + trans_pcie->trans->num_rx_queues = num_irqs; + trans_pcie->shared_vec_mask = IWL_SHARED_IRQ_NON_RX; + } else { + trans_pcie->trans->num_rx_queues = num_irqs - 1; + } + + IWL_DEBUG_INFO(trans, + "MSI-X enabled with rx queues %d, vec mask 0x%x\n", + trans_pcie->trans->num_rx_queues, trans_pcie->shared_vec_mask); + + WARN_ON(trans_pcie->trans->num_rx_queues > IWL_MAX_RX_HW_QUEUES); + + trans_pcie->alloc_vecs = num_irqs; + trans_pcie->msix_enabled = true; + return; + +enable_msi: + ret = pci_enable_msi(pdev); + if (ret) { + dev_err(&pdev->dev, "pci_enable_msi failed - %d\n", ret); + /* enable rfkill interrupt: hw bug w/a */ + pci_read_config_word(pdev, PCI_COMMAND, &pci_cmd); + if (pci_cmd & PCI_COMMAND_INTX_DISABLE) { + pci_cmd &= ~PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(pdev, PCI_COMMAND, pci_cmd); + } + } +} + +static void iwl_pcie_irq_set_affinity(struct iwl_trans *trans) +{ + int iter_rx_q, i, ret, cpu, offset; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + i = trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS ? 0 : 1; + iter_rx_q = trans_pcie->trans->num_rx_queues - 1 + i; + offset = 1 + i; + for (; i < iter_rx_q ; i++) { + /* + * Get the cpu prior to the place to search + * (i.e. return will be > i - 1). + */ + cpu = cpumask_next(i - offset, cpu_online_mask); + cpumask_set_cpu(cpu, &trans_pcie->affinity_mask[i]); + ret = irq_set_affinity_hint(trans_pcie->msix_entries[i].vector, + &trans_pcie->affinity_mask[i]); + if (ret) + IWL_ERR(trans_pcie->trans, + "Failed to set affinity mask for IRQ %d\n", + trans_pcie->msix_entries[i].vector); + } +} + +static int iwl_pcie_init_msix_handler(struct pci_dev *pdev, + struct iwl_trans_pcie *trans_pcie) +{ + int i; + + for (i = 0; i < trans_pcie->alloc_vecs; i++) { + int ret; + struct msix_entry *msix_entry; + const char *qname = queue_name(&pdev->dev, trans_pcie, i); + + if (!qname) + return -ENOMEM; + + msix_entry = &trans_pcie->msix_entries[i]; + ret = devm_request_threaded_irq(&pdev->dev, + msix_entry->vector, + iwl_pcie_msix_isr, + (i == trans_pcie->def_irq) ? + iwl_pcie_irq_msix_handler : + iwl_pcie_irq_rx_msix_handler, + IRQF_SHARED, + qname, + msix_entry); + if (ret) { + IWL_ERR(trans_pcie->trans, + "Error allocating IRQ %d\n", i); + + return ret; + } + } + iwl_pcie_irq_set_affinity(trans_pcie->trans); + + return 0; +} + +static int iwl_trans_pcie_clear_persistence_bit(struct iwl_trans *trans) +{ + u32 hpm, wprot; + + switch (trans->trans_cfg->device_family) { + case IWL_DEVICE_FAMILY_9000: + wprot = PREG_PRPH_WPROT_9000; + break; + case IWL_DEVICE_FAMILY_22000: + wprot = PREG_PRPH_WPROT_22000; + break; + default: + return 0; + } + + hpm = iwl_read_umac_prph_no_grab(trans, HPM_DEBUG); + if (hpm != 0xa5a5a5a0 && (hpm & PERSISTENCE_BIT)) { + u32 wprot_val = iwl_read_umac_prph_no_grab(trans, wprot); + + if (wprot_val & PREG_WFPM_ACCESS) { + IWL_ERR(trans, + "Error, can not clear persistence bit\n"); + return -EPERM; + } + iwl_write_umac_prph_no_grab(trans, HPM_DEBUG, + hpm & ~PERSISTENCE_BIT); + } + + return 0; +} + +static int iwl_pcie_gen2_force_power_gating(struct iwl_trans *trans) +{ + int ret; + + ret = iwl_finish_nic_init(trans); + if (ret < 0) + return ret; + + iwl_set_bits_prph(trans, HPM_HIPM_GEN_CFG, + HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE); + udelay(20); + iwl_set_bits_prph(trans, HPM_HIPM_GEN_CFG, + HPM_HIPM_GEN_CFG_CR_PG_EN | + HPM_HIPM_GEN_CFG_CR_SLP_EN); + udelay(20); + iwl_clear_bits_prph(trans, HPM_HIPM_GEN_CFG, + HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE); + + iwl_trans_pcie_sw_reset(trans); + + return 0; +} + +static int _iwl_trans_pcie_start_hw(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int err; + + lockdep_assert_held(&trans_pcie->mutex); + + err = iwl_pcie_prepare_card_hw(trans); + if (err) { + IWL_ERR(trans, "Error while preparing HW: %d\n", err); + return err; + } + + err = iwl_trans_pcie_clear_persistence_bit(trans); + if (err) + return err; + + iwl_trans_pcie_sw_reset(trans); + + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000 && + trans->trans_cfg->integrated) { + err = iwl_pcie_gen2_force_power_gating(trans); + if (err) + return err; + } + + err = iwl_pcie_apm_init(trans); + if (err) + return err; + + iwl_pcie_init_msix(trans_pcie); + + /* From now on, the op_mode will be kept updated about RF kill state */ + iwl_enable_rfkill_int(trans); + + trans_pcie->opmode_down = false; + + /* Set is_down to false here so that...*/ + trans_pcie->is_down = false; + + /* ...rfkill can call stop_device and set it false if needed */ + iwl_pcie_check_hw_rf_kill(trans); + + return 0; +} + +static int iwl_trans_pcie_start_hw(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + mutex_lock(&trans_pcie->mutex); + ret = _iwl_trans_pcie_start_hw(trans); + mutex_unlock(&trans_pcie->mutex); + + return ret; +} + +static void iwl_trans_pcie_op_mode_leave(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + mutex_lock(&trans_pcie->mutex); + + /* disable interrupts - don't enable HW RF kill interrupt */ + iwl_disable_interrupts(trans); + + iwl_pcie_apm_stop(trans, true); + + iwl_disable_interrupts(trans); + + iwl_pcie_disable_ict(trans); + + mutex_unlock(&trans_pcie->mutex); + + iwl_pcie_synchronize_irqs(trans); +} + +#if defined(__linux__) +static void iwl_trans_pcie_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + writeb(val, IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs); +} + +static void iwl_trans_pcie_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + writel(val, IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs); +} + +static u32 iwl_trans_pcie_read32(struct iwl_trans *trans, u32 ofs) +{ + return readl(IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs); +} +#elif defined(__FreeBSD__) +static void iwl_trans_pcie_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + + IWL_DEBUG_PCI_RW(trans, "W1 %#010x %#04x\n", ofs, val); + bus_write_1((struct resource *)IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base, ofs, val); +} + +static void iwl_trans_pcie_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + + IWL_DEBUG_PCI_RW(trans, "W4 %#010x %#010x\n", ofs, val); + bus_write_4((struct resource *)IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base, ofs, val); +} + +static u32 iwl_trans_pcie_read32(struct iwl_trans *trans, u32 ofs) +{ + u32 v; + + v = bus_read_4((struct resource *)IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base, ofs); + IWL_DEBUG_PCI_RW(trans, "R4 %#010x %#010x\n", ofs, v); + return (v); +} +#endif + +static u32 iwl_trans_pcie_prph_msk(struct iwl_trans *trans) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + return 0x00FFFFFF; + else + return 0x000FFFFF; +} + +static u32 iwl_trans_pcie_read_prph(struct iwl_trans *trans, u32 reg) +{ + u32 mask = iwl_trans_pcie_prph_msk(trans); + + iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_RADDR, + ((reg & mask) | (3 << 24))); + return iwl_trans_pcie_read32(trans, HBUS_TARG_PRPH_RDAT); +} + +static void iwl_trans_pcie_write_prph(struct iwl_trans *trans, u32 addr, + u32 val) +{ + u32 mask = iwl_trans_pcie_prph_msk(trans); + + iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_WADDR, + ((addr & mask) | (3 << 24))); + iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_WDAT, val); +} + +static void iwl_trans_pcie_configure(struct iwl_trans *trans, + const struct iwl_trans_config *trans_cfg) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + /* free all first - we might be reconfigured for a different size */ + iwl_pcie_free_rbs_pool(trans); + + trans->txqs.cmd.q_id = trans_cfg->cmd_queue; + trans->txqs.cmd.fifo = trans_cfg->cmd_fifo; + trans->txqs.cmd.wdg_timeout = trans_cfg->cmd_q_wdg_timeout; + trans->txqs.page_offs = trans_cfg->cb_data_offs; + trans->txqs.dev_cmd_offs = trans_cfg->cb_data_offs + sizeof(void *); + + if (WARN_ON(trans_cfg->n_no_reclaim_cmds > MAX_NO_RECLAIM_CMDS)) + trans_pcie->n_no_reclaim_cmds = 0; + else + trans_pcie->n_no_reclaim_cmds = trans_cfg->n_no_reclaim_cmds; + if (trans_pcie->n_no_reclaim_cmds) + memcpy(trans_pcie->no_reclaim_cmds, trans_cfg->no_reclaim_cmds, + trans_pcie->n_no_reclaim_cmds * sizeof(u8)); + + trans_pcie->rx_buf_size = trans_cfg->rx_buf_size; + trans_pcie->rx_page_order = + iwl_trans_get_rb_size_order(trans_pcie->rx_buf_size); + trans_pcie->rx_buf_bytes = + iwl_trans_get_rb_size(trans_pcie->rx_buf_size); + trans_pcie->supported_dma_mask = DMA_BIT_MASK(12); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + trans_pcie->supported_dma_mask = DMA_BIT_MASK(11); + + trans->txqs.bc_table_dword = trans_cfg->bc_table_dword; + trans_pcie->scd_set_active = trans_cfg->scd_set_active; + + trans->command_groups = trans_cfg->command_groups; + trans->command_groups_size = trans_cfg->command_groups_size; + + /* Initialize NAPI here - it should be before registering to mac80211 + * in the opmode but after the HW struct is allocated. + * As this function may be called again in some corner cases don't + * do anything if NAPI was already initialized. + */ + if (trans_pcie->napi_dev.reg_state != NETREG_DUMMY) + init_dummy_netdev(&trans_pcie->napi_dev); + + trans_pcie->fw_reset_handshake = trans_cfg->fw_reset_handshake; +} + +void iwl_trans_pcie_free(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int i; + + iwl_pcie_synchronize_irqs(trans); + + if (trans->trans_cfg->gen2) + iwl_txq_gen2_tx_free(trans); + else + iwl_pcie_tx_free(trans); + iwl_pcie_rx_free(trans); + + if (trans_pcie->rba.alloc_wq) { + destroy_workqueue(trans_pcie->rba.alloc_wq); + trans_pcie->rba.alloc_wq = NULL; + } + + if (trans_pcie->msix_enabled) { + for (i = 0; i < trans_pcie->alloc_vecs; i++) { + irq_set_affinity_hint( + trans_pcie->msix_entries[i].vector, + NULL); + } + + trans_pcie->msix_enabled = false; + } else { + iwl_pcie_free_ict(trans); + } + + iwl_pcie_free_fw_monitor(trans); + + if (trans_pcie->pnvm_dram.size) + dma_free_coherent(trans->dev, trans_pcie->pnvm_dram.size, + trans_pcie->pnvm_dram.block, + trans_pcie->pnvm_dram.physical); + + if (trans_pcie->reduce_power_dram.size) + dma_free_coherent(trans->dev, + trans_pcie->reduce_power_dram.size, + trans_pcie->reduce_power_dram.block, + trans_pcie->reduce_power_dram.physical); + + mutex_destroy(&trans_pcie->mutex); + iwl_trans_free(trans); +} + +static void iwl_trans_pcie_set_pmi(struct iwl_trans *trans, bool state) +{ + if (state) + set_bit(STATUS_TPOWER_PMI, &trans->status); + else + clear_bit(STATUS_TPOWER_PMI, &trans->status); +} + +struct iwl_trans_pcie_removal { + struct pci_dev *pdev; + struct work_struct work; +}; + +static void iwl_trans_pcie_removal_wk(struct work_struct *wk) +{ + struct iwl_trans_pcie_removal *removal = + container_of(wk, struct iwl_trans_pcie_removal, work); + struct pci_dev *pdev = removal->pdev; + static char *prop[] = {"EVENT=INACCESSIBLE", NULL}; + + dev_err(&pdev->dev, "Device gone - attempting removal\n"); + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, prop); + pci_lock_rescan_remove(); + pci_dev_put(pdev); + pci_stop_and_remove_bus_device(pdev); + pci_unlock_rescan_remove(); + + kfree(removal); + module_put(THIS_MODULE); +} + +/* + * This version doesn't disable BHs but rather assumes they're + * already disabled. + */ +bool __iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans) +{ + int ret; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 write = CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ; + u32 mask = CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY | + CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP; + u32 poll = CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN; + + spin_lock(&trans_pcie->reg_lock); + + if (trans_pcie->cmd_hold_nic_awake) + goto out; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + write = CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ; + mask = CSR_GP_CNTRL_REG_FLAG_MAC_STATUS; + poll = CSR_GP_CNTRL_REG_FLAG_MAC_STATUS; + } + + /* this bit wakes up the NIC */ + __iwl_trans_pcie_set_bit(trans, CSR_GP_CNTRL, write); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_8000) + udelay(2); + + /* + * These bits say the device is running, and should keep running for + * at least a short while (at least as long as MAC_ACCESS_REQ stays 1), + * but they do not indicate that embedded SRAM is restored yet; + * HW with volatile SRAM must save/restore contents to/from + * host DRAM when sleeping/waking for power-saving. + * Each direction takes approximately 1/4 millisecond; with this + * overhead, it's a good idea to grab and hold MAC_ACCESS_REQUEST if a + * series of register accesses are expected (e.g. reading Event Log), + * to keep device from sleeping. + * + * CSR_UCODE_DRV_GP1 register bit MAC_SLEEP == 0 indicates that + * SRAM is okay/restored. We don't check that here because this call + * is just for hardware register access; but GP1 MAC_SLEEP + * check is a good idea before accessing the SRAM of HW with + * volatile SRAM (e.g. reading Event Log). + * + * 5000 series and later (including 1000 series) have non-volatile SRAM, + * and do not save/restore SRAM when power cycling. + */ + ret = iwl_poll_bit(trans, CSR_GP_CNTRL, poll, mask, 15000); + if (unlikely(ret < 0)) { + u32 cntrl = iwl_read32(trans, CSR_GP_CNTRL); + + WARN_ONCE(1, + "Timeout waiting for hardware access (CSR_GP_CNTRL 0x%08x)\n", + cntrl); + + iwl_trans_pcie_dump_regs(trans); + + if (iwlwifi_mod_params.remove_when_gone && cntrl == ~0U) { + struct iwl_trans_pcie_removal *removal; + + if (test_bit(STATUS_TRANS_DEAD, &trans->status)) + goto err; + + IWL_ERR(trans, "Device gone - scheduling removal!\n"); + + /* + * get a module reference to avoid doing this + * while unloading anyway and to avoid + * scheduling a work with code that's being + * removed. + */ + if (!try_module_get(THIS_MODULE)) { + IWL_ERR(trans, + "Module is being unloaded - abort\n"); + goto err; + } + + removal = kzalloc(sizeof(*removal), GFP_ATOMIC); + if (!removal) { + module_put(THIS_MODULE); + goto err; + } + /* + * we don't need to clear this flag, because + * the trans will be freed and reallocated. + */ + set_bit(STATUS_TRANS_DEAD, &trans->status); + + removal->pdev = to_pci_dev(trans->dev); + INIT_WORK(&removal->work, iwl_trans_pcie_removal_wk); + pci_dev_get(removal->pdev); + schedule_work(&removal->work); + } else { + iwl_write32(trans, CSR_RESET, + CSR_RESET_REG_FLAG_FORCE_NMI); + } + +err: + spin_unlock(&trans_pcie->reg_lock); + return false; + } + +out: + /* + * Fool sparse by faking we release the lock - sparse will + * track nic_access anyway. + */ + __release(&trans_pcie->reg_lock); + return true; +} + +static bool iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans) +{ + bool ret; + + local_bh_disable(); + ret = __iwl_trans_pcie_grab_nic_access(trans); + if (ret) { + /* keep BHs disabled until iwl_trans_pcie_release_nic_access */ + return ret; + } + local_bh_enable(); + return false; +} + +static void iwl_trans_pcie_release_nic_access(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->reg_lock); + + /* + * Fool sparse by faking we acquiring the lock - sparse will + * track nic_access anyway. + */ + __acquire(&trans_pcie->reg_lock); + + if (trans_pcie->cmd_hold_nic_awake) + goto out; + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ); + else + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + /* + * Above we read the CSR_GP_CNTRL register, which will flush + * any previous writes, but we need the write that clears the + * MAC_ACCESS_REQ bit to be performed before any other writes + * scheduled on different CPUs (after we drop reg_lock). + */ +out: + spin_unlock_bh(&trans_pcie->reg_lock); +} + +static int iwl_trans_pcie_read_mem(struct iwl_trans *trans, u32 addr, + void *buf, int dwords) +{ + int offs = 0; + u32 *vals = buf; + + while (offs < dwords) { + /* limit the time we spin here under lock to 1/2s */ + unsigned long end = jiffies + HZ / 2; + bool resched = false; + + if (iwl_trans_grab_nic_access(trans)) { + iwl_write32(trans, HBUS_TARG_MEM_RADDR, + addr + 4 * offs); + + while (offs < dwords) { + vals[offs] = iwl_read32(trans, + HBUS_TARG_MEM_RDAT); + offs++; + + if (time_after(jiffies, end)) { + resched = true; + break; + } + } + iwl_trans_release_nic_access(trans); + + if (resched) + cond_resched(); + } else { + return -EBUSY; + } + } + + return 0; +} + +static int iwl_trans_pcie_write_mem(struct iwl_trans *trans, u32 addr, + const void *buf, int dwords) +{ + int offs, ret = 0; + const u32 *vals = buf; + + if (iwl_trans_grab_nic_access(trans)) { + iwl_write32(trans, HBUS_TARG_MEM_WADDR, addr); + for (offs = 0; offs < dwords; offs++) + iwl_write32(trans, HBUS_TARG_MEM_WDAT, + vals ? vals[offs] : 0); + iwl_trans_release_nic_access(trans); + } else { + ret = -EBUSY; + } + return ret; +} + +static int iwl_trans_pcie_read_config32(struct iwl_trans *trans, u32 ofs, + u32 *val) +{ + return pci_read_config_dword(IWL_TRANS_GET_PCIE_TRANS(trans)->pci_dev, + ofs, val); +} + +static void iwl_trans_pcie_block_txq_ptrs(struct iwl_trans *trans, bool block) +{ + int i; + + for (i = 0; i < trans->trans_cfg->base_params->num_of_queues; i++) { + struct iwl_txq *txq = trans->txqs.txq[i]; + + if (i == trans->txqs.cmd.q_id) + continue; + + spin_lock_bh(&txq->lock); + + if (!block && !(WARN_ON_ONCE(!txq->block))) { + txq->block--; + if (!txq->block) { + iwl_write32(trans, HBUS_TARG_WRPTR, + txq->write_ptr | (i << 8)); + } + } else if (block) { + txq->block++; + } + + spin_unlock_bh(&txq->lock); + } +} + +#define IWL_FLUSH_WAIT_MS 2000 + +static int iwl_trans_pcie_rxq_dma_data(struct iwl_trans *trans, int queue, + struct iwl_trans_rxq_dma_data *data) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (queue >= trans->num_rx_queues || !trans_pcie->rxq) + return -EINVAL; + + data->fr_bd_cb = trans_pcie->rxq[queue].bd_dma; + data->urbd_stts_wrptr = trans_pcie->rxq[queue].rb_stts_dma; + data->ur_bd_cb = trans_pcie->rxq[queue].used_bd_dma; + data->fr_bd_wid = 0; + + return 0; +} + +static int iwl_trans_pcie_wait_txq_empty(struct iwl_trans *trans, int txq_idx) +{ + struct iwl_txq *txq; + unsigned long now = jiffies; + bool overflow_tx; + u8 wr_ptr; + + /* Make sure the NIC is still alive in the bus */ + if (test_bit(STATUS_TRANS_DEAD, &trans->status)) + return -ENODEV; + + if (!test_bit(txq_idx, trans->txqs.queue_used)) + return -EINVAL; + + IWL_DEBUG_TX_QUEUES(trans, "Emptying queue %d...\n", txq_idx); + txq = trans->txqs.txq[txq_idx]; + + spin_lock_bh(&txq->lock); + overflow_tx = txq->overflow_tx || + !skb_queue_empty(&txq->overflow_q); + spin_unlock_bh(&txq->lock); + + wr_ptr = READ_ONCE(txq->write_ptr); + + while ((txq->read_ptr != READ_ONCE(txq->write_ptr) || + overflow_tx) && + !time_after(jiffies, + now + msecs_to_jiffies(IWL_FLUSH_WAIT_MS))) { + u8 write_ptr = READ_ONCE(txq->write_ptr); + + /* + * If write pointer moved during the wait, warn only + * if the TX came from op mode. In case TX came from + * trans layer (overflow TX) don't warn. + */ + if (WARN_ONCE(wr_ptr != write_ptr && !overflow_tx, + "WR pointer moved while flushing %d -> %d\n", + wr_ptr, write_ptr)) + return -ETIMEDOUT; + wr_ptr = write_ptr; + + usleep_range(1000, 2000); + + spin_lock_bh(&txq->lock); + overflow_tx = txq->overflow_tx || + !skb_queue_empty(&txq->overflow_q); + spin_unlock_bh(&txq->lock); + } + + if (txq->read_ptr != txq->write_ptr) { + IWL_ERR(trans, + "fail to flush all tx fifo queues Q %d\n", txq_idx); + iwl_txq_log_scd_error(trans, txq); + return -ETIMEDOUT; + } + + IWL_DEBUG_TX_QUEUES(trans, "Queue %d is now empty.\n", txq_idx); + + return 0; +} + +static int iwl_trans_pcie_wait_txqs_empty(struct iwl_trans *trans, u32 txq_bm) +{ + int cnt; + int ret = 0; + + /* waiting for all the tx frames complete might take a while */ + for (cnt = 0; + cnt < trans->trans_cfg->base_params->num_of_queues; + cnt++) { + + if (cnt == trans->txqs.cmd.q_id) + continue; + if (!test_bit(cnt, trans->txqs.queue_used)) + continue; + if (!(BIT(cnt) & txq_bm)) + continue; + + ret = iwl_trans_pcie_wait_txq_empty(trans, cnt); + if (ret) + break; + } + + return ret; +} + +static void iwl_trans_pcie_set_bits_mask(struct iwl_trans *trans, u32 reg, + u32 mask, u32 value) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock_bh(&trans_pcie->reg_lock); + __iwl_trans_pcie_set_bits_mask(trans, reg, mask, value); + spin_unlock_bh(&trans_pcie->reg_lock); +} + +static const char *get_csr_string(int cmd) +{ +#define IWL_CMD(x) case x: return #x + switch (cmd) { + IWL_CMD(CSR_HW_IF_CONFIG_REG); + IWL_CMD(CSR_INT_COALESCING); + IWL_CMD(CSR_INT); + IWL_CMD(CSR_INT_MASK); + IWL_CMD(CSR_FH_INT_STATUS); + IWL_CMD(CSR_GPIO_IN); + IWL_CMD(CSR_RESET); + IWL_CMD(CSR_GP_CNTRL); + IWL_CMD(CSR_HW_REV); + IWL_CMD(CSR_EEPROM_REG); + IWL_CMD(CSR_EEPROM_GP); + IWL_CMD(CSR_OTP_GP_REG); + IWL_CMD(CSR_GIO_REG); + IWL_CMD(CSR_GP_UCODE_REG); + IWL_CMD(CSR_GP_DRIVER_REG); + IWL_CMD(CSR_UCODE_DRV_GP1); + IWL_CMD(CSR_UCODE_DRV_GP2); + IWL_CMD(CSR_LED_REG); + IWL_CMD(CSR_DRAM_INT_TBL_REG); + IWL_CMD(CSR_GIO_CHICKEN_BITS); + IWL_CMD(CSR_ANA_PLL_CFG); + IWL_CMD(CSR_HW_REV_WA_REG); + IWL_CMD(CSR_MONITOR_STATUS_REG); + IWL_CMD(CSR_DBG_HPET_MEM_REG); + default: + return "UNKNOWN"; + } +#undef IWL_CMD +} + +void iwl_pcie_dump_csr(struct iwl_trans *trans) +{ + int i; + static const u32 csr_tbl[] = { + CSR_HW_IF_CONFIG_REG, + CSR_INT_COALESCING, + CSR_INT, + CSR_INT_MASK, + CSR_FH_INT_STATUS, + CSR_GPIO_IN, + CSR_RESET, + CSR_GP_CNTRL, + CSR_HW_REV, + CSR_EEPROM_REG, + CSR_EEPROM_GP, + CSR_OTP_GP_REG, + CSR_GIO_REG, + CSR_GP_UCODE_REG, + CSR_GP_DRIVER_REG, + CSR_UCODE_DRV_GP1, + CSR_UCODE_DRV_GP2, + CSR_LED_REG, + CSR_DRAM_INT_TBL_REG, + CSR_GIO_CHICKEN_BITS, + CSR_ANA_PLL_CFG, + CSR_MONITOR_STATUS_REG, + CSR_HW_REV_WA_REG, + CSR_DBG_HPET_MEM_REG + }; + IWL_ERR(trans, "CSR values:\n"); + IWL_ERR(trans, "(2nd byte of CSR_INT_COALESCING is " + "CSR_INT_PERIODIC_REG)\n"); + for (i = 0; i < ARRAY_SIZE(csr_tbl); i++) { + IWL_ERR(trans, " %25s: 0X%08x\n", + get_csr_string(csr_tbl[i]), + iwl_read32(trans, csr_tbl[i])); + } +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +/* create and remove of files */ +#define DEBUGFS_ADD_FILE(name, parent, mode) do { \ + debugfs_create_file(#name, mode, parent, trans, \ + &iwl_dbgfs_##name##_ops); \ +} while (0) + +/* file operation */ +#define DEBUGFS_READ_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_WRITE_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_READ_WRITE_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +struct iwl_dbgfs_tx_queue_priv { + struct iwl_trans *trans; +}; + +struct iwl_dbgfs_tx_queue_state { + loff_t pos; +}; + +static void *iwl_dbgfs_tx_queue_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct iwl_dbgfs_tx_queue_priv *priv = seq->private; + struct iwl_dbgfs_tx_queue_state *state; + + if (*pos >= priv->trans->trans_cfg->base_params->num_of_queues) + return NULL; + + state = kmalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + state->pos = *pos; + return state; +} + +static void *iwl_dbgfs_tx_queue_seq_next(struct seq_file *seq, + void *v, loff_t *pos) +{ + struct iwl_dbgfs_tx_queue_priv *priv = seq->private; + struct iwl_dbgfs_tx_queue_state *state = v; + + *pos = ++state->pos; + + if (*pos >= priv->trans->trans_cfg->base_params->num_of_queues) + return NULL; + + return state; +} + +static void iwl_dbgfs_tx_queue_seq_stop(struct seq_file *seq, void *v) +{ + kfree(v); +} + +static int iwl_dbgfs_tx_queue_seq_show(struct seq_file *seq, void *v) +{ + struct iwl_dbgfs_tx_queue_priv *priv = seq->private; + struct iwl_dbgfs_tx_queue_state *state = v; + struct iwl_trans *trans = priv->trans; + struct iwl_txq *txq = trans->txqs.txq[state->pos]; + + seq_printf(seq, "hwq %.3u: used=%d stopped=%d ", + (unsigned int)state->pos, + !!test_bit(state->pos, trans->txqs.queue_used), + !!test_bit(state->pos, trans->txqs.queue_stopped)); + if (txq) + seq_printf(seq, + "read=%u write=%u need_update=%d frozen=%d n_window=%d ampdu=%d", + txq->read_ptr, txq->write_ptr, + txq->need_update, txq->frozen, + txq->n_window, txq->ampdu); + else + seq_puts(seq, "(unallocated)"); + + if (state->pos == trans->txqs.cmd.q_id) + seq_puts(seq, " (HCMD)"); + seq_puts(seq, "\n"); + + return 0; +} + +static const struct seq_operations iwl_dbgfs_tx_queue_seq_ops = { + .start = iwl_dbgfs_tx_queue_seq_start, + .next = iwl_dbgfs_tx_queue_seq_next, + .stop = iwl_dbgfs_tx_queue_seq_stop, + .show = iwl_dbgfs_tx_queue_seq_show, +}; + +static int iwl_dbgfs_tx_queue_open(struct inode *inode, struct file *filp) +{ + struct iwl_dbgfs_tx_queue_priv *priv; + + priv = __seq_open_private(filp, &iwl_dbgfs_tx_queue_seq_ops, + sizeof(*priv)); + + if (!priv) + return -ENOMEM; + + priv->trans = inode->i_private; + return 0; +} + +static ssize_t iwl_dbgfs_rx_queue_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + char *buf; + int pos = 0, i, ret; + size_t bufsz; + + bufsz = sizeof(char) * 121 * trans->num_rx_queues; + + if (!trans_pcie->rxq) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < trans->num_rx_queues && pos < bufsz; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + pos += scnprintf(buf + pos, bufsz - pos, "queue#: %2d\n", + i); + pos += scnprintf(buf + pos, bufsz - pos, "\tread: %u\n", + rxq->read); + pos += scnprintf(buf + pos, bufsz - pos, "\twrite: %u\n", + rxq->write); + pos += scnprintf(buf + pos, bufsz - pos, "\twrite_actual: %u\n", + rxq->write_actual); + pos += scnprintf(buf + pos, bufsz - pos, "\tneed_update: %2d\n", + rxq->need_update); + pos += scnprintf(buf + pos, bufsz - pos, "\tfree_count: %u\n", + rxq->free_count); + if (rxq->rb_stts) { + u32 r = __le16_to_cpu(iwl_get_closed_rb_stts(trans, + rxq)); + pos += scnprintf(buf + pos, bufsz - pos, + "\tclosed_rb_num: %u\n", + r & 0x0FFF); + } else { + pos += scnprintf(buf + pos, bufsz - pos, + "\tclosed_rb_num: Not Allocated\n"); + } + } + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + + return ret; +} + +static ssize_t iwl_dbgfs_interrupt_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + + int pos = 0; + char *buf; + int bufsz = 24 * 64; /* 24 items * 64 char per item */ + ssize_t ret; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, + "Interrupt Statistics Report:\n"); + + pos += scnprintf(buf + pos, bufsz - pos, "HW Error:\t\t\t %u\n", + isr_stats->hw); + pos += scnprintf(buf + pos, bufsz - pos, "SW Error:\t\t\t %u\n", + isr_stats->sw); + if (isr_stats->sw || isr_stats->hw) { + pos += scnprintf(buf + pos, bufsz - pos, + "\tLast Restarting Code: 0x%X\n", + isr_stats->err_code); + } +#ifdef CONFIG_IWLWIFI_DEBUG + pos += scnprintf(buf + pos, bufsz - pos, "Frame transmitted:\t\t %u\n", + isr_stats->sch); + pos += scnprintf(buf + pos, bufsz - pos, "Alive interrupt:\t\t %u\n", + isr_stats->alive); +#endif + pos += scnprintf(buf + pos, bufsz - pos, + "HW RF KILL switch toggled:\t %u\n", isr_stats->rfkill); + + pos += scnprintf(buf + pos, bufsz - pos, "CT KILL:\t\t\t %u\n", + isr_stats->ctkill); + + pos += scnprintf(buf + pos, bufsz - pos, "Wakeup Interrupt:\t\t %u\n", + isr_stats->wakeup); + + pos += scnprintf(buf + pos, bufsz - pos, + "Rx command responses:\t\t %u\n", isr_stats->rx); + + pos += scnprintf(buf + pos, bufsz - pos, "Tx/FH interrupt:\t\t %u\n", + isr_stats->tx); + + pos += scnprintf(buf + pos, bufsz - pos, "Unexpected INTA:\t\t %u\n", + isr_stats->unhandled); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_interrupt_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + u32 reset_flag; + int ret; + + ret = kstrtou32_from_user(user_buf, count, 16, &reset_flag); + if (ret) + return ret; + if (reset_flag == 0) + memset(isr_stats, 0, sizeof(*isr_stats)); + + return count; +} + +static ssize_t iwl_dbgfs_csr_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + + iwl_pcie_dump_csr(trans); + + return count; +} + +static ssize_t iwl_dbgfs_fh_reg_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + char *buf = NULL; + ssize_t ret; + + ret = iwl_dump_fh(trans, &buf); + if (ret < 0) + return ret; + if (!buf) + return -EINVAL; + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_rfkill_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + char buf[100]; + int pos; + + pos = scnprintf(buf, sizeof(buf), "debug: %d\nhw: %d\n", + trans_pcie->debug_rfkill, + !(iwl_read32(trans, CSR_GP_CNTRL) & + CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_rfkill_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool new_value; + int ret; + + ret = kstrtobool_from_user(user_buf, count, &new_value); + if (ret) + return ret; + if (new_value == trans_pcie->debug_rfkill) + return count; + IWL_WARN(trans, "changing debug rfkill %d->%d\n", + trans_pcie->debug_rfkill, new_value); + trans_pcie->debug_rfkill = new_value; + iwl_pcie_handle_rfkill_irq(trans); + + return count; +} + +static int iwl_dbgfs_monitor_data_open(struct inode *inode, + struct file *file) +{ + struct iwl_trans *trans = inode->i_private; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!trans->dbg.dest_tlv || + trans->dbg.dest_tlv->monitor_mode != EXTERNAL_MODE) { + IWL_ERR(trans, "Debug destination is not set to DRAM\n"); + return -ENOENT; + } + + if (trans_pcie->fw_mon_data.state != IWL_FW_MON_DBGFS_STATE_CLOSED) + return -EBUSY; + + trans_pcie->fw_mon_data.state = IWL_FW_MON_DBGFS_STATE_OPEN; + return simple_open(inode, file); +} + +static int iwl_dbgfs_monitor_data_release(struct inode *inode, + struct file *file) +{ + struct iwl_trans_pcie *trans_pcie = + IWL_TRANS_GET_PCIE_TRANS(inode->i_private); + + if (trans_pcie->fw_mon_data.state == IWL_FW_MON_DBGFS_STATE_OPEN) + trans_pcie->fw_mon_data.state = IWL_FW_MON_DBGFS_STATE_CLOSED; + return 0; +} + +static bool iwl_write_to_user_buf(char __user *user_buf, ssize_t count, + void *buf, ssize_t *size, + ssize_t *bytes_copied) +{ + int buf_size_left = count - *bytes_copied; + + buf_size_left = buf_size_left - (buf_size_left % sizeof(u32)); + if (*size > buf_size_left) + *size = buf_size_left; + + *size -= copy_to_user(user_buf, buf, *size); + *bytes_copied += *size; + + if (buf_size_left == *size) + return true; + return false; +} + +static ssize_t iwl_dbgfs_monitor_data_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + void *cpu_addr = (void *)trans->dbg.fw_mon.block, *curr_buf; + struct cont_rec *data = &trans_pcie->fw_mon_data; + u32 write_ptr_addr, wrap_cnt_addr, write_ptr, wrap_cnt; + ssize_t size, bytes_copied = 0; + bool b_full; + + if (trans->dbg.dest_tlv) { + write_ptr_addr = + le32_to_cpu(trans->dbg.dest_tlv->write_ptr_reg); + wrap_cnt_addr = le32_to_cpu(trans->dbg.dest_tlv->wrap_count); + } else { + write_ptr_addr = MON_BUFF_WRPTR; + wrap_cnt_addr = MON_BUFF_CYCLE_CNT; + } + + if (unlikely(!trans->dbg.rec_on)) + return 0; + + mutex_lock(&data->mutex); + if (data->state == + IWL_FW_MON_DBGFS_STATE_DISABLED) { + mutex_unlock(&data->mutex); + return 0; + } + + /* write_ptr position in bytes rather then DW */ + write_ptr = iwl_read_prph(trans, write_ptr_addr) * sizeof(u32); + wrap_cnt = iwl_read_prph(trans, wrap_cnt_addr); + + if (data->prev_wrap_cnt == wrap_cnt) { + size = write_ptr - data->prev_wr_ptr; + curr_buf = cpu_addr + data->prev_wr_ptr; + b_full = iwl_write_to_user_buf(user_buf, count, + curr_buf, &size, + &bytes_copied); + data->prev_wr_ptr += size; + + } else if (data->prev_wrap_cnt == wrap_cnt - 1 && + write_ptr < data->prev_wr_ptr) { + size = trans->dbg.fw_mon.size - data->prev_wr_ptr; + curr_buf = cpu_addr + data->prev_wr_ptr; + b_full = iwl_write_to_user_buf(user_buf, count, + curr_buf, &size, + &bytes_copied); + data->prev_wr_ptr += size; + + if (!b_full) { + size = write_ptr; + b_full = iwl_write_to_user_buf(user_buf, count, + cpu_addr, &size, + &bytes_copied); + data->prev_wr_ptr = size; + data->prev_wrap_cnt++; + } + } else { + if (data->prev_wrap_cnt == wrap_cnt - 1 && + write_ptr > data->prev_wr_ptr) + IWL_WARN(trans, + "write pointer passed previous write pointer, start copying from the beginning\n"); + else if (!unlikely(data->prev_wrap_cnt == 0 && + data->prev_wr_ptr == 0)) + IWL_WARN(trans, + "monitor data is out of sync, start copying from the beginning\n"); + + size = write_ptr; + b_full = iwl_write_to_user_buf(user_buf, count, + cpu_addr, &size, + &bytes_copied); + data->prev_wr_ptr = size; + data->prev_wrap_cnt = wrap_cnt; + } + + mutex_unlock(&data->mutex); + + return bytes_copied; +} + +static ssize_t iwl_dbgfs_rf_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!trans_pcie->rf_name[0]) + return -ENODEV; + + return simple_read_from_buffer(user_buf, count, ppos, + trans_pcie->rf_name, + strlen(trans_pcie->rf_name)); +} + +DEBUGFS_READ_WRITE_FILE_OPS(interrupt); +DEBUGFS_READ_FILE_OPS(fh_reg); +DEBUGFS_READ_FILE_OPS(rx_queue); +DEBUGFS_WRITE_FILE_OPS(csr); +DEBUGFS_READ_WRITE_FILE_OPS(rfkill); +DEBUGFS_READ_FILE_OPS(rf); + +static const struct file_operations iwl_dbgfs_tx_queue_ops = { + .owner = THIS_MODULE, + .open = iwl_dbgfs_tx_queue_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; + +static const struct file_operations iwl_dbgfs_monitor_data_ops = { + .read = iwl_dbgfs_monitor_data_read, + .open = iwl_dbgfs_monitor_data_open, + .release = iwl_dbgfs_monitor_data_release, +}; + +/* Create the debugfs files and directories */ +void iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans) +{ + struct dentry *dir = trans->dbgfs_dir; + + DEBUGFS_ADD_FILE(rx_queue, dir, 0400); + DEBUGFS_ADD_FILE(tx_queue, dir, 0400); + DEBUGFS_ADD_FILE(interrupt, dir, 0600); + DEBUGFS_ADD_FILE(csr, dir, 0200); + DEBUGFS_ADD_FILE(fh_reg, dir, 0400); + DEBUGFS_ADD_FILE(rfkill, dir, 0600); + DEBUGFS_ADD_FILE(monitor_data, dir, 0400); + DEBUGFS_ADD_FILE(rf, dir, 0400); +} + +static void iwl_trans_pcie_debugfs_cleanup(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct cont_rec *data = &trans_pcie->fw_mon_data; + + mutex_lock(&data->mutex); + data->state = IWL_FW_MON_DBGFS_STATE_DISABLED; + mutex_unlock(&data->mutex); +} +#endif /*CONFIG_IWLWIFI_DEBUGFS */ + +static u32 iwl_trans_pcie_get_cmdlen(struct iwl_trans *trans, void *tfd) +{ + u32 cmdlen = 0; + int i; + + for (i = 0; i < trans->txqs.tfd.max_tbs; i++) + cmdlen += iwl_txq_gen1_tfd_tb_get_len(trans, tfd, i); + + return cmdlen; +} + +static u32 iwl_trans_pcie_dump_rbs(struct iwl_trans *trans, + struct iwl_fw_error_dump_data **data, + int allocated_rb_nums) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int max_len = trans_pcie->rx_buf_bytes; + /* Dump RBs is supported only for pre-9000 devices (1 queue) */ + struct iwl_rxq *rxq = &trans_pcie->rxq[0]; + u32 i, r, j, rb_len = 0; + + spin_lock(&rxq->lock); + + r = le16_to_cpu(iwl_get_closed_rb_stts(trans, rxq)) & 0x0FFF; + + for (i = rxq->read, j = 0; + i != r && j < allocated_rb_nums; + i = (i + 1) & RX_QUEUE_MASK, j++) { + struct iwl_rx_mem_buffer *rxb = rxq->queue[i]; + struct iwl_fw_error_dump_rb *rb; + + dma_sync_single_for_cpu(trans->dev, rxb->page_dma, + max_len, DMA_FROM_DEVICE); + + rb_len += sizeof(**data) + sizeof(*rb) + max_len; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RB); + (*data)->len = cpu_to_le32(sizeof(*rb) + max_len); + rb = (void *)(*data)->data; + rb->index = cpu_to_le32(i); + memcpy(rb->data, page_address(rxb->page), max_len); + + *data = iwl_fw_error_next_data(*data); + } + + spin_unlock(&rxq->lock); + + return rb_len; +} +#define IWL_CSR_TO_DUMP (0x250) + +static u32 iwl_trans_pcie_dump_csr(struct iwl_trans *trans, + struct iwl_fw_error_dump_data **data) +{ + u32 csr_len = sizeof(**data) + IWL_CSR_TO_DUMP; + __le32 *val; + int i; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_CSR); + (*data)->len = cpu_to_le32(IWL_CSR_TO_DUMP); + val = (void *)(*data)->data; + + for (i = 0; i < IWL_CSR_TO_DUMP; i += 4) + *val++ = cpu_to_le32(iwl_trans_pcie_read32(trans, i)); + + *data = iwl_fw_error_next_data(*data); + + return csr_len; +} + +static u32 iwl_trans_pcie_fh_regs_dump(struct iwl_trans *trans, + struct iwl_fw_error_dump_data **data) +{ + u32 fh_regs_len = FH_MEM_UPPER_BOUND - FH_MEM_LOWER_BOUND; + __le32 *val; + int i; + + if (!iwl_trans_grab_nic_access(trans)) + return 0; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_FH_REGS); + (*data)->len = cpu_to_le32(fh_regs_len); + val = (void *)(*data)->data; + + if (!trans->trans_cfg->gen2) + for (i = FH_MEM_LOWER_BOUND; i < FH_MEM_UPPER_BOUND; + i += sizeof(u32)) + *val++ = cpu_to_le32(iwl_trans_pcie_read32(trans, i)); + else + for (i = iwl_umac_prph(trans, FH_MEM_LOWER_BOUND_GEN2); + i < iwl_umac_prph(trans, FH_MEM_UPPER_BOUND_GEN2); + i += sizeof(u32)) + *val++ = cpu_to_le32(iwl_trans_pcie_read_prph(trans, + i)); + + iwl_trans_release_nic_access(trans); + + *data = iwl_fw_error_next_data(*data); + + return sizeof(**data) + fh_regs_len; +} + +static u32 +iwl_trans_pci_dump_marbh_monitor(struct iwl_trans *trans, + struct iwl_fw_error_dump_fw_mon *fw_mon_data, + u32 monitor_len) +{ + u32 buf_size_in_dwords = (monitor_len >> 2); + u32 *buffer = (u32 *)fw_mon_data->data; + u32 i; + + if (!iwl_trans_grab_nic_access(trans)) + return 0; + + iwl_write_umac_prph_no_grab(trans, MON_DMARB_RD_CTL_ADDR, 0x1); + for (i = 0; i < buf_size_in_dwords; i++) + buffer[i] = iwl_read_umac_prph_no_grab(trans, + MON_DMARB_RD_DATA_ADDR); + iwl_write_umac_prph_no_grab(trans, MON_DMARB_RD_CTL_ADDR, 0x0); + + iwl_trans_release_nic_access(trans); + + return monitor_len; +} + +static void +iwl_trans_pcie_dump_pointers(struct iwl_trans *trans, + struct iwl_fw_error_dump_fw_mon *fw_mon_data) +{ + u32 base, base_high, write_ptr, write_ptr_val, wrap_cnt; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + base = DBGC_CUR_DBGBUF_BASE_ADDR_LSB; + base_high = DBGC_CUR_DBGBUF_BASE_ADDR_MSB; + write_ptr = DBGC_CUR_DBGBUF_STATUS; + wrap_cnt = DBGC_DBGBUF_WRAP_AROUND; + } else if (trans->dbg.dest_tlv) { + write_ptr = le32_to_cpu(trans->dbg.dest_tlv->write_ptr_reg); + wrap_cnt = le32_to_cpu(trans->dbg.dest_tlv->wrap_count); + base = le32_to_cpu(trans->dbg.dest_tlv->base_reg); + } else { + base = MON_BUFF_BASE_ADDR; + write_ptr = MON_BUFF_WRPTR; + wrap_cnt = MON_BUFF_CYCLE_CNT; + } + + write_ptr_val = iwl_read_prph(trans, write_ptr); + fw_mon_data->fw_mon_cycle_cnt = + cpu_to_le32(iwl_read_prph(trans, wrap_cnt)); + fw_mon_data->fw_mon_base_ptr = + cpu_to_le32(iwl_read_prph(trans, base)); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + fw_mon_data->fw_mon_base_high_ptr = + cpu_to_le32(iwl_read_prph(trans, base_high)); + write_ptr_val &= DBGC_CUR_DBGBUF_STATUS_OFFSET_MSK; + /* convert wrtPtr to DWs, to align with all HWs */ + write_ptr_val >>= 2; + } + fw_mon_data->fw_mon_wr_ptr = cpu_to_le32(write_ptr_val); +} + +static u32 +iwl_trans_pcie_dump_monitor(struct iwl_trans *trans, + struct iwl_fw_error_dump_data **data, + u32 monitor_len) +{ + struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + u32 len = 0; + + if (trans->dbg.dest_tlv || + (fw_mon->size && + (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000 || + trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210))) { + struct iwl_fw_error_dump_fw_mon *fw_mon_data; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_FW_MONITOR); + fw_mon_data = (void *)(*data)->data; + + iwl_trans_pcie_dump_pointers(trans, fw_mon_data); + + len += sizeof(**data) + sizeof(*fw_mon_data); + if (fw_mon->size) { + memcpy(fw_mon_data->data, fw_mon->block, fw_mon->size); + monitor_len = fw_mon->size; + } else if (trans->dbg.dest_tlv->monitor_mode == SMEM_MODE) { + u32 base = le32_to_cpu(fw_mon_data->fw_mon_base_ptr); + /* + * Update pointers to reflect actual values after + * shifting + */ + if (trans->dbg.dest_tlv->version) { + base = (iwl_read_prph(trans, base) & + IWL_LDBG_M2S_BUF_BA_MSK) << + trans->dbg.dest_tlv->base_shift; + base *= IWL_M2S_UNIT_SIZE; + base += trans->cfg->smem_offset; + } else { + base = iwl_read_prph(trans, base) << + trans->dbg.dest_tlv->base_shift; + } + + iwl_trans_read_mem(trans, base, fw_mon_data->data, + monitor_len / sizeof(u32)); + } else if (trans->dbg.dest_tlv->monitor_mode == MARBH_MODE) { + monitor_len = + iwl_trans_pci_dump_marbh_monitor(trans, + fw_mon_data, + monitor_len); + } else { + /* Didn't match anything - output no monitor data */ + monitor_len = 0; + } + + len += monitor_len; + (*data)->len = cpu_to_le32(monitor_len + sizeof(*fw_mon_data)); + } + + return len; +} + +static int iwl_trans_get_fw_monitor_len(struct iwl_trans *trans, u32 *len) +{ + if (trans->dbg.fw_mon.size) { + *len += sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fw_mon) + + trans->dbg.fw_mon.size; + return trans->dbg.fw_mon.size; + } else if (trans->dbg.dest_tlv) { + u32 base, end, cfg_reg, monitor_len; + + if (trans->dbg.dest_tlv->version == 1) { + cfg_reg = le32_to_cpu(trans->dbg.dest_tlv->base_reg); + cfg_reg = iwl_read_prph(trans, cfg_reg); + base = (cfg_reg & IWL_LDBG_M2S_BUF_BA_MSK) << + trans->dbg.dest_tlv->base_shift; + base *= IWL_M2S_UNIT_SIZE; + base += trans->cfg->smem_offset; + + monitor_len = + (cfg_reg & IWL_LDBG_M2S_BUF_SIZE_MSK) >> + trans->dbg.dest_tlv->end_shift; + monitor_len *= IWL_M2S_UNIT_SIZE; + } else { + base = le32_to_cpu(trans->dbg.dest_tlv->base_reg); + end = le32_to_cpu(trans->dbg.dest_tlv->end_reg); + + base = iwl_read_prph(trans, base) << + trans->dbg.dest_tlv->base_shift; + end = iwl_read_prph(trans, end) << + trans->dbg.dest_tlv->end_shift; + + /* Make "end" point to the actual end */ + if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_8000 || + trans->dbg.dest_tlv->monitor_mode == MARBH_MODE) + end += (1 << trans->dbg.dest_tlv->end_shift); + monitor_len = end - base; + } + *len += sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fw_mon) + + monitor_len; + return monitor_len; + } + return 0; +} + +static struct iwl_trans_dump_data * +iwl_trans_pcie_dump_data(struct iwl_trans *trans, + u32 dump_mask, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_fw_error_dump_data *data; + struct iwl_txq *cmdq = trans->txqs.txq[trans->txqs.cmd.q_id]; + struct iwl_fw_error_dump_txcmd *txcmd; + struct iwl_trans_dump_data *dump_data; + u32 len, num_rbs = 0, monitor_len = 0; + int i, ptr; + bool dump_rbs = test_bit(STATUS_FW_ERROR, &trans->status) && + !trans->trans_cfg->mq_rx_supported && + dump_mask & BIT(IWL_FW_ERROR_DUMP_RB); + + if (!dump_mask) + return NULL; + + /* transport dump header */ + len = sizeof(*dump_data); + + /* host commands */ + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_TXCMD) && cmdq) + len += sizeof(*data) + + cmdq->n_window * (sizeof(*txcmd) + + TFD_MAX_PAYLOAD_SIZE); + + /* FW monitor */ + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_FW_MONITOR)) + monitor_len = iwl_trans_get_fw_monitor_len(trans, &len); + + /* CSR registers */ + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_CSR)) + len += sizeof(*data) + IWL_CSR_TO_DUMP; + + /* FH registers */ + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_FH_REGS)) { + if (trans->trans_cfg->gen2) + len += sizeof(*data) + + (iwl_umac_prph(trans, FH_MEM_UPPER_BOUND_GEN2) - + iwl_umac_prph(trans, FH_MEM_LOWER_BOUND_GEN2)); + else + len += sizeof(*data) + + (FH_MEM_UPPER_BOUND - + FH_MEM_LOWER_BOUND); + } + + if (dump_rbs) { + /* Dump RBs is supported only for pre-9000 devices (1 queue) */ + struct iwl_rxq *rxq = &trans_pcie->rxq[0]; + /* RBs */ + num_rbs = + le16_to_cpu(iwl_get_closed_rb_stts(trans, rxq)) + & 0x0FFF; + num_rbs = (num_rbs - rxq->read) & RX_QUEUE_MASK; + len += num_rbs * (sizeof(*data) + + sizeof(struct iwl_fw_error_dump_rb) + + (PAGE_SIZE << trans_pcie->rx_page_order)); + } + + /* Paged memory for gen2 HW */ + if (trans->trans_cfg->gen2 && dump_mask & BIT(IWL_FW_ERROR_DUMP_PAGING)) + for (i = 0; i < trans->init_dram.paging_cnt; i++) + len += sizeof(*data) + + sizeof(struct iwl_fw_error_dump_paging) + + trans->init_dram.paging[i].size; + + dump_data = vzalloc(len); + if (!dump_data) + return NULL; + + len = 0; + data = (void *)dump_data->data; + + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_TXCMD) && cmdq) { + u16 tfd_size = trans->txqs.tfd.size; + + data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXCMD); + txcmd = (void *)data->data; + spin_lock_bh(&cmdq->lock); + ptr = cmdq->write_ptr; + for (i = 0; i < cmdq->n_window; i++) { + u8 idx = iwl_txq_get_cmd_index(cmdq, ptr); + u8 tfdidx; + u32 caplen, cmdlen; + + if (trans->trans_cfg->use_tfh) + tfdidx = idx; + else + tfdidx = ptr; + + cmdlen = iwl_trans_pcie_get_cmdlen(trans, + (u8 *)cmdq->tfds + + tfd_size * tfdidx); + caplen = min_t(u32, TFD_MAX_PAYLOAD_SIZE, cmdlen); + + if (cmdlen) { + len += sizeof(*txcmd) + caplen; + txcmd->cmdlen = cpu_to_le32(cmdlen); + txcmd->caplen = cpu_to_le32(caplen); + memcpy(txcmd->data, cmdq->entries[idx].cmd, + caplen); + if (sanitize_ops && sanitize_ops->frob_hcmd) + sanitize_ops->frob_hcmd(sanitize_ctx, + txcmd->data, + caplen); + txcmd = (void *)((u8 *)txcmd->data + caplen); + } + + ptr = iwl_txq_dec_wrap(trans, ptr); + } + spin_unlock_bh(&cmdq->lock); + + data->len = cpu_to_le32(len); + len += sizeof(*data); + data = iwl_fw_error_next_data(data); + } + + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_CSR)) + len += iwl_trans_pcie_dump_csr(trans, &data); + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_FH_REGS)) + len += iwl_trans_pcie_fh_regs_dump(trans, &data); + if (dump_rbs) + len += iwl_trans_pcie_dump_rbs(trans, &data, num_rbs); + + /* Paged memory for gen2 HW */ + if (trans->trans_cfg->gen2 && + dump_mask & BIT(IWL_FW_ERROR_DUMP_PAGING)) { + for (i = 0; i < trans->init_dram.paging_cnt; i++) { + struct iwl_fw_error_dump_paging *paging; + u32 page_len = trans->init_dram.paging[i].size; + + data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PAGING); + data->len = cpu_to_le32(sizeof(*paging) + page_len); + paging = (void *)data->data; + paging->index = cpu_to_le32(i); + memcpy(paging->data, + trans->init_dram.paging[i].block, page_len); + data = iwl_fw_error_next_data(data); + + len += sizeof(*data) + sizeof(*paging) + page_len; + } + } + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_FW_MONITOR)) + len += iwl_trans_pcie_dump_monitor(trans, &data, monitor_len); + + dump_data->len = len; + + return dump_data; +} + +static void iwl_trans_pci_interrupts(struct iwl_trans *trans, bool enable) +{ + if (enable) + iwl_enable_interrupts(trans); + else + iwl_disable_interrupts(trans); +} + +static void iwl_trans_pcie_sync_nmi(struct iwl_trans *trans) +{ + u32 inta_addr, sw_err_bit; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (trans_pcie->msix_enabled) { + inta_addr = CSR_MSIX_HW_INT_CAUSES_AD; + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + sw_err_bit = MSIX_HW_INT_CAUSES_REG_SW_ERR_BZ; + else + sw_err_bit = MSIX_HW_INT_CAUSES_REG_SW_ERR; + } else { + inta_addr = CSR_INT; + sw_err_bit = CSR_INT_BIT_SW_ERR; + } + + iwl_trans_sync_nmi_with_addr(trans, inta_addr, sw_err_bit); +} + +#define IWL_TRANS_COMMON_OPS \ + .op_mode_leave = iwl_trans_pcie_op_mode_leave, \ + .write8 = iwl_trans_pcie_write8, \ + .write32 = iwl_trans_pcie_write32, \ + .read32 = iwl_trans_pcie_read32, \ + .read_prph = iwl_trans_pcie_read_prph, \ + .write_prph = iwl_trans_pcie_write_prph, \ + .read_mem = iwl_trans_pcie_read_mem, \ + .write_mem = iwl_trans_pcie_write_mem, \ + .read_config32 = iwl_trans_pcie_read_config32, \ + .configure = iwl_trans_pcie_configure, \ + .set_pmi = iwl_trans_pcie_set_pmi, \ + .sw_reset = iwl_trans_pcie_sw_reset, \ + .grab_nic_access = iwl_trans_pcie_grab_nic_access, \ + .release_nic_access = iwl_trans_pcie_release_nic_access, \ + .set_bits_mask = iwl_trans_pcie_set_bits_mask, \ + .dump_data = iwl_trans_pcie_dump_data, \ + .d3_suspend = iwl_trans_pcie_d3_suspend, \ + .d3_resume = iwl_trans_pcie_d3_resume, \ + .interrupts = iwl_trans_pci_interrupts, \ + .sync_nmi = iwl_trans_pcie_sync_nmi \ + +static const struct iwl_trans_ops trans_ops_pcie = { + IWL_TRANS_COMMON_OPS, + .start_hw = iwl_trans_pcie_start_hw, + .fw_alive = iwl_trans_pcie_fw_alive, + .start_fw = iwl_trans_pcie_start_fw, + .stop_device = iwl_trans_pcie_stop_device, + + .send_cmd = iwl_pcie_enqueue_hcmd, + + .tx = iwl_trans_pcie_tx, + .reclaim = iwl_txq_reclaim, + + .txq_disable = iwl_trans_pcie_txq_disable, + .txq_enable = iwl_trans_pcie_txq_enable, + + .txq_set_shared_mode = iwl_trans_pcie_txq_set_shared_mode, + + .wait_tx_queues_empty = iwl_trans_pcie_wait_txqs_empty, + + .freeze_txq_timer = iwl_trans_txq_freeze_timer, + .block_txq_ptrs = iwl_trans_pcie_block_txq_ptrs, +#ifdef CONFIG_IWLWIFI_DEBUGFS + .debugfs_cleanup = iwl_trans_pcie_debugfs_cleanup, +#endif +}; + +static const struct iwl_trans_ops trans_ops_pcie_gen2 = { + IWL_TRANS_COMMON_OPS, + .start_hw = iwl_trans_pcie_start_hw, + .fw_alive = iwl_trans_pcie_gen2_fw_alive, + .start_fw = iwl_trans_pcie_gen2_start_fw, + .stop_device = iwl_trans_pcie_gen2_stop_device, + + .send_cmd = iwl_pcie_gen2_enqueue_hcmd, + + .tx = iwl_txq_gen2_tx, + .reclaim = iwl_txq_reclaim, + + .set_q_ptrs = iwl_txq_set_q_ptrs, + + .txq_alloc = iwl_txq_dyn_alloc, + .txq_free = iwl_txq_dyn_free, + .wait_txq_empty = iwl_trans_pcie_wait_txq_empty, + .rxq_dma_data = iwl_trans_pcie_rxq_dma_data, + .set_pnvm = iwl_trans_pcie_ctx_info_gen3_set_pnvm, + .set_reduce_power = iwl_trans_pcie_ctx_info_gen3_set_reduce_power, +#ifdef CONFIG_IWLWIFI_DEBUGFS + .debugfs_cleanup = iwl_trans_pcie_debugfs_cleanup, +#endif +}; + +struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev, + const struct pci_device_id *ent, + const struct iwl_cfg_trans_params *cfg_trans) +{ + struct iwl_trans_pcie *trans_pcie; + struct iwl_trans *trans; + int ret, addr_size; + const struct iwl_trans_ops *ops = &trans_ops_pcie_gen2; + void __iomem * const *table; + + if (!cfg_trans->gen2) + ops = &trans_ops_pcie; + + ret = pcim_enable_device(pdev); + if (ret) + return ERR_PTR(ret); + + trans = iwl_trans_alloc(sizeof(struct iwl_trans_pcie), &pdev->dev, ops, + cfg_trans); + if (!trans) + return ERR_PTR(-ENOMEM); + + trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + trans_pcie->trans = trans; + trans_pcie->opmode_down = true; + spin_lock_init(&trans_pcie->irq_lock); + spin_lock_init(&trans_pcie->reg_lock); + spin_lock_init(&trans_pcie->alloc_page_lock); + mutex_init(&trans_pcie->mutex); + init_waitqueue_head(&trans_pcie->ucode_write_waitq); + init_waitqueue_head(&trans_pcie->fw_reset_waitq); + + trans_pcie->rba.alloc_wq = alloc_workqueue("rb_allocator", + WQ_HIGHPRI | WQ_UNBOUND, 1); + if (!trans_pcie->rba.alloc_wq) { + ret = -ENOMEM; + goto out_free_trans; + } + INIT_WORK(&trans_pcie->rba.rx_alloc, iwl_pcie_rx_allocator_work); + + trans_pcie->debug_rfkill = -1; + + if (!cfg_trans->base_params->pcie_l1_allowed) { + /* + * W/A - seems to solve weird behavior. We need to remove this + * if we don't want to stay in L1 all the time. This wastes a + * lot of power. + */ + pci_disable_link_state(pdev, PCIE_LINK_STATE_L0S | + PCIE_LINK_STATE_L1 | + PCIE_LINK_STATE_CLKPM); + } + + trans_pcie->def_rx_queue = 0; + + pci_set_master(pdev); + + addr_size = trans->txqs.tfd.addr_size; + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(addr_size)); + if (ret) { + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + /* both attempts failed: */ + if (ret) { + dev_err(&pdev->dev, "No suitable DMA available\n"); + goto out_no_pci; + } + } + + ret = pcim_iomap_regions_request_all(pdev, BIT(0), DRV_NAME); + if (ret) { + dev_err(&pdev->dev, "pcim_iomap_regions_request_all failed\n"); + goto out_no_pci; + } + +#if defined(__FreeBSD__) + linuxkpi_pcim_want_to_use_bus_functions(pdev); +#endif + table = pcim_iomap_table(pdev); + if (!table) { + dev_err(&pdev->dev, "pcim_iomap_table failed\n"); + ret = -ENOMEM; + goto out_no_pci; + } + + trans_pcie->hw_base = table[0]; + if (!trans_pcie->hw_base) { + dev_err(&pdev->dev, "couldn't find IO mem in first BAR\n"); + ret = -ENODEV; + goto out_no_pci; + } + + /* We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state */ + pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00); + + trans_pcie->pci_dev = pdev; + iwl_disable_interrupts(trans); + + trans->hw_rev = iwl_read32(trans, CSR_HW_REV); + if (trans->hw_rev == 0xffffffff) { + dev_err(&pdev->dev, "HW_REV=0xFFFFFFFF, PCI issues?\n"); + ret = -EIO; + goto out_no_pci; + } + + /* + * In the 8000 HW family the format of the 4 bytes of CSR_HW_REV have + * changed, and now the revision step also includes bit 0-1 (no more + * "dash" value). To keep hw_rev backwards compatible - we'll store it + * in the old format. + */ + if (cfg_trans->device_family >= IWL_DEVICE_FAMILY_8000) + trans->hw_rev = (trans->hw_rev & 0xfff0) | + (CSR_HW_REV_STEP(trans->hw_rev << 2) << 2); + + IWL_DEBUG_INFO(trans, "HW REV: 0x%0x\n", trans->hw_rev); + + iwl_pcie_set_interrupt_capa(pdev, trans, cfg_trans); + trans->hw_id = (pdev->device << 16) + pdev->subsystem_device; + snprintf(trans->hw_id_str, sizeof(trans->hw_id_str), + "PCI ID: 0x%04X:0x%04X", pdev->device, pdev->subsystem_device); + + init_waitqueue_head(&trans_pcie->sx_waitq); + + + if (trans_pcie->msix_enabled) { + ret = iwl_pcie_init_msix_handler(pdev, trans_pcie); + if (ret) + goto out_no_pci; + } else { + ret = iwl_pcie_alloc_ict(trans); + if (ret) + goto out_no_pci; + + ret = devm_request_threaded_irq(&pdev->dev, pdev->irq, + iwl_pcie_isr, + iwl_pcie_irq_handler, + IRQF_SHARED, DRV_NAME, trans); + if (ret) { + IWL_ERR(trans, "Error allocating IRQ %d\n", pdev->irq); + goto out_free_ict; + } + } + +#ifdef CONFIG_IWLWIFI_DEBUGFS + trans_pcie->fw_mon_data.state = IWL_FW_MON_DBGFS_STATE_CLOSED; + mutex_init(&trans_pcie->fw_mon_data.mutex); +#endif + + iwl_dbg_tlv_init(trans); + + return trans; + +out_free_ict: + iwl_pcie_free_ict(trans); +out_no_pci: + destroy_workqueue(trans_pcie->rba.alloc_wq); +out_free_trans: + iwl_trans_free(trans); + return ERR_PTR(ret); +} diff --git a/sys/contrib/dev/iwlwifi/pcie/tx-gen2.c b/sys/contrib/dev/iwlwifi/pcie/tx-gen2.c new file mode 100644 index 00000000000..f6cb28e90eb --- /dev/null +++ b/sys/contrib/dev/iwlwifi/pcie/tx-gen2.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2020 Intel Corporation + */ +#ifdef CONFIG_INET +#include +#endif +#include + +#include "iwl-debug.h" +#include "iwl-csr.h" +#include "iwl-io.h" +#include "internal.h" +#include "fw/api/tx.h" +#include "queue/tx.h" + +/*************** HOST COMMAND QUEUE FUNCTIONS *****/ + +/* + * iwl_pcie_gen2_enqueue_hcmd - enqueue a uCode command + * @priv: device private data point + * @cmd: a pointer to the ucode command structure + * + * The function returns < 0 values to indicate the operation + * failed. On success, it returns the index (>= 0) of command in the + * command queue. + */ +int iwl_pcie_gen2_enqueue_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + struct iwl_device_cmd *out_cmd; + struct iwl_cmd_meta *out_meta; + void *dup_buf = NULL; + dma_addr_t phys_addr; + int i, cmd_pos, idx; + u16 copy_size, cmd_size, tb0_size; + bool had_nocopy = false; + u8 group_id = iwl_cmd_groupid(cmd->id); + const u8 *cmddata[IWL_MAX_CMD_TBS_PER_TFD]; + u16 cmdlen[IWL_MAX_CMD_TBS_PER_TFD]; + struct iwl_tfh_tfd *tfd; + unsigned long flags; + + copy_size = sizeof(struct iwl_cmd_header_wide); + cmd_size = sizeof(struct iwl_cmd_header_wide); + + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + cmddata[i] = cmd->data[i]; + cmdlen[i] = cmd->len[i]; + + if (!cmd->len[i]) + continue; + + /* need at least IWL_FIRST_TB_SIZE copied */ + if (copy_size < IWL_FIRST_TB_SIZE) { + int copy = IWL_FIRST_TB_SIZE - copy_size; + + if (copy > cmdlen[i]) + copy = cmdlen[i]; + cmdlen[i] -= copy; + cmddata[i] += copy; + copy_size += copy; + } + + if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) { + had_nocopy = true; + if (WARN_ON(cmd->dataflags[i] & IWL_HCMD_DFL_DUP)) { + idx = -EINVAL; + goto free_dup_buf; + } + } else if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) { + /* + * This is also a chunk that isn't copied + * to the static buffer so set had_nocopy. + */ + had_nocopy = true; + + /* only allowed once */ + if (WARN_ON(dup_buf)) { + idx = -EINVAL; + goto free_dup_buf; + } + + dup_buf = kmemdup(cmddata[i], cmdlen[i], + GFP_ATOMIC); + if (!dup_buf) + return -ENOMEM; + } else { + /* NOCOPY must not be followed by normal! */ + if (WARN_ON(had_nocopy)) { + idx = -EINVAL; + goto free_dup_buf; + } + copy_size += cmdlen[i]; + } + cmd_size += cmd->len[i]; + } + + /* + * If any of the command structures end up being larger than the + * TFD_MAX_PAYLOAD_SIZE and they aren't dynamically allocated into + * separate TFDs, then we will need to increase the size of the buffers + */ + if (WARN(copy_size > TFD_MAX_PAYLOAD_SIZE, + "Command %s (%#x) is too large (%d bytes)\n", + iwl_get_cmd_string(trans, cmd->id), cmd->id, copy_size)) { + idx = -EINVAL; + goto free_dup_buf; + } + + spin_lock_irqsave(&txq->lock, flags); + + idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + tfd = iwl_txq_get_tfd(trans, txq, txq->write_ptr); + memset(tfd, 0, sizeof(*tfd)); + + if (iwl_txq_space(trans, txq) < ((cmd->flags & CMD_ASYNC) ? 2 : 1)) { + spin_unlock_irqrestore(&txq->lock, flags); + + IWL_ERR(trans, "No space in command queue\n"); + iwl_op_mode_cmd_queue_full(trans->op_mode); + idx = -ENOSPC; + goto free_dup_buf; + } + + out_cmd = txq->entries[idx].cmd; + out_meta = &txq->entries[idx].meta; + + /* re-initialize to NULL */ + memset(out_meta, 0, sizeof(*out_meta)); + if (cmd->flags & CMD_WANT_SKB) + out_meta->source = cmd; + + /* set up the header */ + out_cmd->hdr_wide.cmd = iwl_cmd_opcode(cmd->id); + out_cmd->hdr_wide.group_id = group_id; + out_cmd->hdr_wide.version = iwl_cmd_version(cmd->id); + out_cmd->hdr_wide.length = + cpu_to_le16(cmd_size - sizeof(struct iwl_cmd_header_wide)); + out_cmd->hdr_wide.reserved = 0; + out_cmd->hdr_wide.sequence = + cpu_to_le16(QUEUE_TO_SEQ(trans->txqs.cmd.q_id) | + INDEX_TO_SEQ(txq->write_ptr)); + + cmd_pos = sizeof(struct iwl_cmd_header_wide); + copy_size = sizeof(struct iwl_cmd_header_wide); + + /* and copy the data that needs to be copied */ + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + int copy; + + if (!cmd->len[i]) + continue; + + /* copy everything if not nocopy/dup */ + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) { + copy = cmd->len[i]; + + memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy); + cmd_pos += copy; + copy_size += copy; + continue; + } + + /* + * Otherwise we need at least IWL_FIRST_TB_SIZE copied + * in total (for bi-directional DMA), but copy up to what + * we can fit into the payload for debug dump purposes. + */ + copy = min_t(int, TFD_MAX_PAYLOAD_SIZE - cmd_pos, cmd->len[i]); + + memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy); + cmd_pos += copy; + + /* However, treat copy_size the proper way, we need it below */ + if (copy_size < IWL_FIRST_TB_SIZE) { + copy = IWL_FIRST_TB_SIZE - copy_size; + + if (copy > cmd->len[i]) + copy = cmd->len[i]; + copy_size += copy; + } + } + + IWL_DEBUG_HC(trans, + "Sending command %s (%.2x.%.2x), seq: 0x%04X, %d bytes at %d[%d]:%d\n", + iwl_get_cmd_string(trans, cmd->id), group_id, + out_cmd->hdr.cmd, le16_to_cpu(out_cmd->hdr.sequence), + cmd_size, txq->write_ptr, idx, trans->txqs.cmd.q_id); + + /* start the TFD with the minimum copy bytes */ + tb0_size = min_t(int, copy_size, IWL_FIRST_TB_SIZE); + memcpy(&txq->first_tb_bufs[idx], out_cmd, tb0_size); + iwl_txq_gen2_set_tb(trans, tfd, iwl_txq_get_first_tb_dma(txq, idx), + tb0_size); + + /* map first command fragment, if any remains */ + if (copy_size > tb0_size) { + phys_addr = dma_map_single(trans->dev, + (u8 *)out_cmd + tb0_size, + copy_size - tb0_size, + DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + idx = -ENOMEM; + iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd); + goto out; + } + iwl_txq_gen2_set_tb(trans, tfd, phys_addr, + copy_size - tb0_size); + } + + /* map the remaining (adjusted) nocopy/dup fragments */ + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + void *data = (void *)(uintptr_t)cmddata[i]; + + if (!cmdlen[i]) + continue; + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) + continue; + if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) + data = dup_buf; + phys_addr = dma_map_single(trans->dev, data, + cmdlen[i], DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + idx = -ENOMEM; + iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd); + goto out; + } + iwl_txq_gen2_set_tb(trans, tfd, phys_addr, cmdlen[i]); + } + + BUILD_BUG_ON(IWL_TFH_NUM_TBS > sizeof(out_meta->tbs) * BITS_PER_BYTE); + out_meta->flags = cmd->flags; + if (WARN_ON_ONCE(txq->entries[idx].free_buf)) + kfree_sensitive(txq->entries[idx].free_buf); + txq->entries[idx].free_buf = dup_buf; + + trace_iwlwifi_dev_hcmd(trans->dev, cmd, cmd_size, &out_cmd->hdr_wide); + + /* start timer if queue currently empty */ + if (txq->read_ptr == txq->write_ptr && txq->wd_timeout) + mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout); + + spin_lock(&trans_pcie->reg_lock); + /* Increment and update queue's write index */ + txq->write_ptr = iwl_txq_inc_wrap(trans, txq->write_ptr); + iwl_txq_inc_wr_ptr(trans, txq); + spin_unlock(&trans_pcie->reg_lock); + +out: + spin_unlock_irqrestore(&txq->lock, flags); +free_dup_buf: + if (idx < 0) + kfree(dup_buf); + return idx; +} diff --git a/sys/contrib/dev/iwlwifi/pcie/tx.c b/sys/contrib/dev/iwlwifi/pcie/tx.c new file mode 100644 index 00000000000..142231465fa --- /dev/null +++ b/sys/contrib/dev/iwlwifi/pcie/tx.c @@ -0,0 +1,1639 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2003-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include +#include +#include +#include +#ifdef CONFIG_INET +#include +#include +#endif +#if defined(__FreeBSD__) +#include +#endif + +#include "iwl-debug.h" +#include "iwl-csr.h" +#include "iwl-prph.h" +#include "iwl-io.h" +#include "iwl-scd.h" +#include "iwl-op-mode.h" +#include "internal.h" +#include "fw/api/tx.h" + +/*************** DMA-QUEUE-GENERAL-FUNCTIONS ***** + * DMA services + * + * Theory of operation + * + * A Tx or Rx queue resides in host DRAM, and is comprised of a circular buffer + * of buffer descriptors, each of which points to one or more data buffers for + * the device to read from or fill. Driver and device exchange status of each + * queue via "read" and "write" pointers. Driver keeps minimum of 2 empty + * entries in each circular buffer, to protect against confusing empty and full + * queue states. + * + * The device reads or writes the data in the queues via the device's several + * DMA/FIFO channels. Each queue is mapped to a single DMA channel. + * + * For Tx queue, there are low mark and high mark limits. If, after queuing + * the packet for Tx, free space become < low mark, Tx queue stopped. When + * reclaiming packets (on 'tx done IRQ), if free space become > high mark, + * Tx queue resumed. + * + ***************************************************/ + + +int iwl_pcie_alloc_dma_ptr(struct iwl_trans *trans, + struct iwl_dma_ptr *ptr, size_t size) +{ + if (WARN_ON(ptr->addr)) + return -EINVAL; + + ptr->addr = dma_alloc_coherent(trans->dev, size, + &ptr->dma, GFP_KERNEL); + if (!ptr->addr) + return -ENOMEM; + ptr->size = size; + return 0; +} + +void iwl_pcie_free_dma_ptr(struct iwl_trans *trans, struct iwl_dma_ptr *ptr) +{ + if (unlikely(!ptr->addr)) + return; + + dma_free_coherent(trans->dev, ptr->size, ptr->addr, ptr->dma); + memset(ptr, 0, sizeof(*ptr)); +} + +/* + * iwl_pcie_txq_inc_wr_ptr - Send new write index to hardware + */ +static void iwl_pcie_txq_inc_wr_ptr(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + u32 reg = 0; + int txq_id = txq->id; + + lockdep_assert_held(&txq->lock); + + /* + * explicitly wake up the NIC if: + * 1. shadow registers aren't enabled + * 2. NIC is woken up for CMD regardless of shadow outside this function + * 3. there is a chance that the NIC is asleep + */ + if (!trans->trans_cfg->base_params->shadow_reg_enable && + txq_id != trans->txqs.cmd.q_id && + test_bit(STATUS_TPOWER_PMI, &trans->status)) { + /* + * wake up nic if it's powered down ... + * uCode will wake up, and interrupt us again, so next + * time we'll skip this part. + */ + reg = iwl_read32(trans, CSR_UCODE_DRV_GP1); + + if (reg & CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP) { + IWL_DEBUG_INFO(trans, "Tx queue %d requesting wakeup, GP1 = 0x%x\n", + txq_id, reg); + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + txq->need_update = true; + return; + } + } + + /* + * if not in power-save mode, uCode will never sleep when we're + * trying to tx (during RFKILL, we're not trying to tx). + */ + IWL_DEBUG_TX(trans, "Q:%d WR: 0x%x\n", txq_id, txq->write_ptr); + if (!txq->block) + iwl_write32(trans, HBUS_TARG_WRPTR, + txq->write_ptr | (txq_id << 8)); +} + +void iwl_pcie_txq_check_wrptrs(struct iwl_trans *trans) +{ + int i; + + for (i = 0; i < trans->trans_cfg->base_params->num_of_queues; i++) { + struct iwl_txq *txq = trans->txqs.txq[i]; + + if (!test_bit(i, trans->txqs.queue_used)) + continue; + + spin_lock_bh(&txq->lock); + if (txq->need_update) { + iwl_pcie_txq_inc_wr_ptr(trans, txq); + txq->need_update = false; + } + spin_unlock_bh(&txq->lock); + } +} + +static inline void iwl_pcie_tfd_set_tb(struct iwl_trans *trans, void *tfd, + u8 idx, dma_addr_t addr, u16 len) +{ + struct iwl_tfd *tfd_fh = (void *)tfd; + struct iwl_tfd_tb *tb = &tfd_fh->tbs[idx]; + + u16 hi_n_len = len << 4; + + put_unaligned_le32(addr, &tb->lo); + hi_n_len |= iwl_get_dma_hi_addr(addr); + + tb->hi_n_len = cpu_to_le16(hi_n_len); + + tfd_fh->num_tbs = idx + 1; +} + +static int iwl_pcie_txq_build_tfd(struct iwl_trans *trans, struct iwl_txq *txq, + dma_addr_t addr, u16 len, bool reset) +{ + void *tfd; + u32 num_tbs; + + tfd = (u8 *)txq->tfds + trans->txqs.tfd.size * txq->write_ptr; + + if (reset) + memset(tfd, 0, trans->txqs.tfd.size); + + num_tbs = iwl_txq_gen1_tfd_get_num_tbs(trans, tfd); + + /* Each TFD can point to a maximum max_tbs Tx buffers */ + if (num_tbs >= trans->txqs.tfd.max_tbs) { + IWL_ERR(trans, "Error can not send more than %d chunks\n", + trans->txqs.tfd.max_tbs); + return -EINVAL; + } + + if (WARN(addr & ~IWL_TX_DMA_MASK, + "Unaligned address = %llx\n", (unsigned long long)addr)) + return -EINVAL; + + iwl_pcie_tfd_set_tb(trans, tfd, num_tbs, addr, len); + + return num_tbs; +} + +static void iwl_pcie_clear_cmd_in_flight(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!trans->trans_cfg->base_params->apmg_wake_up_wa) + return; + + spin_lock(&trans_pcie->reg_lock); + + if (WARN_ON(!trans_pcie->cmd_hold_nic_awake)) { + spin_unlock(&trans_pcie->reg_lock); + return; + } + + trans_pcie->cmd_hold_nic_awake = false; + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + spin_unlock(&trans_pcie->reg_lock); +} + +/* + * iwl_pcie_txq_unmap - Unmap any remaining DMA mappings and free skb's + */ +static void iwl_pcie_txq_unmap(struct iwl_trans *trans, int txq_id) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + + if (!txq) { + IWL_ERR(trans, "Trying to free a queue that wasn't allocated?\n"); + return; + } + + spin_lock_bh(&txq->lock); + while (txq->write_ptr != txq->read_ptr) { + IWL_DEBUG_TX_REPLY(trans, "Q %d Free %d\n", + txq_id, txq->read_ptr); + + if (txq_id != trans->txqs.cmd.q_id) { + struct sk_buff *skb = txq->entries[txq->read_ptr].skb; + + if (WARN_ON_ONCE(!skb)) + continue; + + iwl_txq_free_tso_page(trans, skb); + } + iwl_txq_free_tfd(trans, txq); + txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr); + + if (txq->read_ptr == txq->write_ptr && + txq_id == trans->txqs.cmd.q_id) + iwl_pcie_clear_cmd_in_flight(trans); + } + + while (!skb_queue_empty(&txq->overflow_q)) { + struct sk_buff *skb = __skb_dequeue(&txq->overflow_q); + + iwl_op_mode_free_skb(trans->op_mode, skb); + } + + spin_unlock_bh(&txq->lock); + + /* just in case - this queue may have been stopped */ + iwl_wake_queue(trans, txq); +} + +/* + * iwl_pcie_txq_free - Deallocate DMA queue. + * @txq: Transmit queue to deallocate. + * + * Empty queue by removing and destroying all BD's. + * Free all buffers. + * 0-fill, but do not free "txq" descriptor structure. + */ +static void iwl_pcie_txq_free(struct iwl_trans *trans, int txq_id) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + struct device *dev = trans->dev; + int i; + + if (WARN_ON(!txq)) + return; + + iwl_pcie_txq_unmap(trans, txq_id); + + /* De-alloc array of command/tx buffers */ + if (txq_id == trans->txqs.cmd.q_id) + for (i = 0; i < txq->n_window; i++) { + kfree_sensitive(txq->entries[i].cmd); + kfree_sensitive(txq->entries[i].free_buf); + } + + /* De-alloc circular buffer of TFDs */ + if (txq->tfds) { + dma_free_coherent(dev, + trans->txqs.tfd.size * + trans->trans_cfg->base_params->max_tfd_queue_size, + txq->tfds, txq->dma_addr); + txq->dma_addr = 0; + txq->tfds = NULL; + + dma_free_coherent(dev, + sizeof(*txq->first_tb_bufs) * txq->n_window, + txq->first_tb_bufs, txq->first_tb_dma); + } + + kfree(txq->entries); + txq->entries = NULL; + + del_timer_sync(&txq->stuck_timer); + + /* 0-fill queue descriptor structure */ + memset(txq, 0, sizeof(*txq)); +} + +void iwl_pcie_tx_start(struct iwl_trans *trans, u32 scd_base_addr) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int nq = trans->trans_cfg->base_params->num_of_queues; + int chan; + u32 reg_val; + int clear_dwords = (SCD_TRANS_TBL_OFFSET_QUEUE(nq) - + SCD_CONTEXT_MEM_LOWER_BOUND) / sizeof(u32); + + /* make sure all queue are not stopped/used */ + memset(trans->txqs.queue_stopped, 0, + sizeof(trans->txqs.queue_stopped)); + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + trans_pcie->scd_base_addr = + iwl_read_prph(trans, SCD_SRAM_BASE_ADDR); + + WARN_ON(scd_base_addr != 0 && + scd_base_addr != trans_pcie->scd_base_addr); + + /* reset context data, TX status and translation data */ + iwl_trans_write_mem(trans, trans_pcie->scd_base_addr + + SCD_CONTEXT_MEM_LOWER_BOUND, + NULL, clear_dwords); + + iwl_write_prph(trans, SCD_DRAM_BASE_ADDR, + trans->txqs.scd_bc_tbls.dma >> 10); + + /* The chain extension of the SCD doesn't work well. This feature is + * enabled by default by the HW, so we need to disable it manually. + */ + if (trans->trans_cfg->base_params->scd_chain_ext_wa) + iwl_write_prph(trans, SCD_CHAINEXT_EN, 0); + + iwl_trans_ac_txq_enable(trans, trans->txqs.cmd.q_id, + trans->txqs.cmd.fifo, + trans->txqs.cmd.wdg_timeout); + + /* Activate all Tx DMA/FIFO channels */ + iwl_scd_activate_fifos(trans); + + /* Enable DMA channel */ + for (chan = 0; chan < FH_TCSR_CHNL_NUM; chan++) + iwl_write_direct32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(chan), + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE); + + /* Update FH chicken bits */ + reg_val = iwl_read_direct32(trans, FH_TX_CHICKEN_BITS_REG); + iwl_write_direct32(trans, FH_TX_CHICKEN_BITS_REG, + reg_val | FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN); + + /* Enable L1-Active */ + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) + iwl_clear_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_L1_ACT_DIS); +} + +void iwl_trans_pcie_tx_reset(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int txq_id; + + /* + * we should never get here in gen2 trans mode return early to avoid + * having invalid accesses + */ + if (WARN_ON_ONCE(trans->trans_cfg->gen2)) + return; + + for (txq_id = 0; txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) { + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + if (trans->trans_cfg->use_tfh) + iwl_write_direct64(trans, + FH_MEM_CBBC_QUEUE(trans, txq_id), + txq->dma_addr); + else + iwl_write_direct32(trans, + FH_MEM_CBBC_QUEUE(trans, txq_id), + txq->dma_addr >> 8); + iwl_pcie_txq_unmap(trans, txq_id); + txq->read_ptr = 0; + txq->write_ptr = 0; + } + + /* Tell NIC where to find the "keep warm" buffer */ + iwl_write_direct32(trans, FH_KW_MEM_ADDR_REG, + trans_pcie->kw.dma >> 4); + + /* + * Send 0 as the scd_base_addr since the device may have be reset + * while we were in WoWLAN in which case SCD_SRAM_BASE_ADDR will + * contain garbage. + */ + iwl_pcie_tx_start(trans, 0); +} + +static void iwl_pcie_tx_stop_fh(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ch, ret; + u32 mask = 0; + + spin_lock_bh(&trans_pcie->irq_lock); + + if (!iwl_trans_grab_nic_access(trans)) + goto out; + + /* Stop each Tx DMA channel */ + for (ch = 0; ch < FH_TCSR_CHNL_NUM; ch++) { + iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(ch), 0x0); + mask |= FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(ch); + } + + /* Wait for DMA channels to be idle */ + ret = iwl_poll_bit(trans, FH_TSSR_TX_STATUS_REG, mask, mask, 5000); + if (ret < 0) + IWL_ERR(trans, + "Failing on timeout while stopping DMA channel %d [0x%08x]\n", + ch, iwl_read32(trans, FH_TSSR_TX_STATUS_REG)); + + iwl_trans_release_nic_access(trans); + +out: + spin_unlock_bh(&trans_pcie->irq_lock); +} + +/* + * iwl_pcie_tx_stop - Stop all Tx DMA channels + */ +int iwl_pcie_tx_stop(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int txq_id; + + /* Turn off all Tx DMA fifos */ + iwl_scd_deactivate_fifos(trans); + + /* Turn off all Tx DMA channels */ + iwl_pcie_tx_stop_fh(trans); + + /* + * This function can be called before the op_mode disabled the + * queues. This happens when we have an rfkill interrupt. + * Since we stop Tx altogether - mark the queues as stopped. + */ + memset(trans->txqs.queue_stopped, 0, + sizeof(trans->txqs.queue_stopped)); + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + /* This can happen: start_hw, stop_device */ + if (!trans_pcie->txq_memory) + return 0; + + /* Unmap DMA from host system and free skb's */ + for (txq_id = 0; txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) + iwl_pcie_txq_unmap(trans, txq_id); + + return 0; +} + +/* + * iwl_trans_tx_free - Free TXQ Context + * + * Destroy all TX DMA queues and structures + */ +void iwl_pcie_tx_free(struct iwl_trans *trans) +{ + int txq_id; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + /* Tx queues */ + if (trans_pcie->txq_memory) { + for (txq_id = 0; + txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) { + iwl_pcie_txq_free(trans, txq_id); + trans->txqs.txq[txq_id] = NULL; + } + } + + kfree(trans_pcie->txq_memory); + trans_pcie->txq_memory = NULL; + + iwl_pcie_free_dma_ptr(trans, &trans_pcie->kw); + + iwl_pcie_free_dma_ptr(trans, &trans->txqs.scd_bc_tbls); +} + +/* + * iwl_pcie_tx_alloc - allocate TX context + * Allocate all Tx DMA structures and initialize them + */ +static int iwl_pcie_tx_alloc(struct iwl_trans *trans) +{ + int ret; + int txq_id, slots_num; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u16 bc_tbls_size = trans->trans_cfg->base_params->num_of_queues; + + if (WARN_ON(trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210)) + return -EINVAL; + + bc_tbls_size *= sizeof(struct iwlagn_scd_bc_tbl); + + /*It is not allowed to alloc twice, so warn when this happens. + * We cannot rely on the previous allocation, so free and fail */ + if (WARN_ON(trans_pcie->txq_memory)) { + ret = -EINVAL; + goto error; + } + + ret = iwl_pcie_alloc_dma_ptr(trans, &trans->txqs.scd_bc_tbls, + bc_tbls_size); + if (ret) { + IWL_ERR(trans, "Scheduler BC Table allocation failed\n"); + goto error; + } + + /* Alloc keep-warm buffer */ + ret = iwl_pcie_alloc_dma_ptr(trans, &trans_pcie->kw, IWL_KW_SIZE); + if (ret) { + IWL_ERR(trans, "Keep Warm allocation failed\n"); + goto error; + } + + trans_pcie->txq_memory = + kcalloc(trans->trans_cfg->base_params->num_of_queues, + sizeof(struct iwl_txq), GFP_KERNEL); + if (!trans_pcie->txq_memory) { + IWL_ERR(trans, "Not enough memory for txq\n"); + ret = -ENOMEM; + goto error; + } + + /* Alloc and init all Tx queues, including the command queue (#4/#9) */ + for (txq_id = 0; txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) { + bool cmd_queue = (txq_id == trans->txqs.cmd.q_id); + + if (cmd_queue) + slots_num = max_t(u32, IWL_CMD_QUEUE_SIZE, + trans->cfg->min_txq_size); + else + slots_num = max_t(u32, IWL_DEFAULT_QUEUE_SIZE, + trans->cfg->min_256_ba_txq_size); + trans->txqs.txq[txq_id] = &trans_pcie->txq_memory[txq_id]; + ret = iwl_txq_alloc(trans, trans->txqs.txq[txq_id], slots_num, + cmd_queue); + if (ret) { + IWL_ERR(trans, "Tx %d queue alloc failed\n", txq_id); + goto error; + } + trans->txqs.txq[txq_id]->id = txq_id; + } + + return 0; + +error: + iwl_pcie_tx_free(trans); + + return ret; +} + +int iwl_pcie_tx_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + int txq_id, slots_num; + bool alloc = false; + + if (!trans_pcie->txq_memory) { + ret = iwl_pcie_tx_alloc(trans); + if (ret) + goto error; + alloc = true; + } + + spin_lock_bh(&trans_pcie->irq_lock); + + /* Turn off all Tx DMA fifos */ + iwl_scd_deactivate_fifos(trans); + + /* Tell NIC where to find the "keep warm" buffer */ + iwl_write_direct32(trans, FH_KW_MEM_ADDR_REG, + trans_pcie->kw.dma >> 4); + + spin_unlock_bh(&trans_pcie->irq_lock); + + /* Alloc and init all Tx queues, including the command queue (#4/#9) */ + for (txq_id = 0; txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) { + bool cmd_queue = (txq_id == trans->txqs.cmd.q_id); + + if (cmd_queue) + slots_num = max_t(u32, IWL_CMD_QUEUE_SIZE, + trans->cfg->min_txq_size); + else + slots_num = max_t(u32, IWL_DEFAULT_QUEUE_SIZE, + trans->cfg->min_256_ba_txq_size); + ret = iwl_txq_init(trans, trans->txqs.txq[txq_id], slots_num, + cmd_queue); + if (ret) { + IWL_ERR(trans, "Tx %d queue init failed\n", txq_id); + goto error; + } + + /* + * Tell nic where to find circular buffer of TFDs for a + * given Tx queue, and enable the DMA channel used for that + * queue. + * Circular buffer (TFD queue in DRAM) physical base address + */ + iwl_write_direct32(trans, FH_MEM_CBBC_QUEUE(trans, txq_id), + trans->txqs.txq[txq_id]->dma_addr >> 8); + } + + iwl_set_bits_prph(trans, SCD_GP_CTRL, SCD_GP_CTRL_AUTO_ACTIVE_MODE); + if (trans->trans_cfg->base_params->num_of_queues > 20) + iwl_set_bits_prph(trans, SCD_GP_CTRL, + SCD_GP_CTRL_ENABLE_31_QUEUES); + + return 0; +error: + /*Upon error, free only if we allocated something */ + if (alloc) + iwl_pcie_tx_free(trans); + return ret; +} + +static int iwl_pcie_set_cmd_in_flight(struct iwl_trans *trans, + const struct iwl_host_cmd *cmd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + /* Make sure the NIC is still alive in the bus */ + if (test_bit(STATUS_TRANS_DEAD, &trans->status)) + return -ENODEV; + + if (!trans->trans_cfg->base_params->apmg_wake_up_wa) + return 0; + + /* + * wake up the NIC to make sure that the firmware will see the host + * command - we will let the NIC sleep once all the host commands + * returned. This needs to be done only on NICs that have + * apmg_wake_up_wa set (see above.) + */ + if (!_iwl_trans_pcie_grab_nic_access(trans)) + return -EIO; + + /* + * In iwl_trans_grab_nic_access(), we've acquired the reg_lock. + * There, we also returned immediately if cmd_hold_nic_awake is + * already true, so it's OK to unconditionally set it to true. + */ + trans_pcie->cmd_hold_nic_awake = true; + spin_unlock(&trans_pcie->reg_lock); + + return 0; +} + +/* + * iwl_pcie_cmdq_reclaim - Reclaim TX command queue entries already Tx'd + * + * When FW advances 'R' index, all entries between old and new 'R' index + * need to be reclaimed. As result, some free space forms. If there is + * enough free space (> low mark), wake the stack that feeds us. + */ +static void iwl_pcie_cmdq_reclaim(struct iwl_trans *trans, int txq_id, int idx) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + int nfreed = 0; + u16 r; + + lockdep_assert_held(&txq->lock); + + idx = iwl_txq_get_cmd_index(txq, idx); + r = iwl_txq_get_cmd_index(txq, txq->read_ptr); + + if (idx >= trans->trans_cfg->base_params->max_tfd_queue_size || + (!iwl_txq_used(txq, idx))) { + WARN_ONCE(test_bit(txq_id, trans->txqs.queue_used), + "%s: Read index for DMA queue txq id (%d), index %d is out of range [0-%d] %d %d.\n", + __func__, txq_id, idx, + trans->trans_cfg->base_params->max_tfd_queue_size, + txq->write_ptr, txq->read_ptr); + return; + } + + for (idx = iwl_txq_inc_wrap(trans, idx); r != idx; + r = iwl_txq_inc_wrap(trans, r)) { + txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr); + + if (nfreed++ > 0) { + IWL_ERR(trans, "HCMD skipped: index (%d) %d %d\n", + idx, txq->write_ptr, r); + iwl_force_nmi(trans); + } + } + + if (txq->read_ptr == txq->write_ptr) + iwl_pcie_clear_cmd_in_flight(trans); + + iwl_txq_progress(txq); +} + +static int iwl_pcie_txq_set_ratid_map(struct iwl_trans *trans, u16 ra_tid, + u16 txq_id) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 tbl_dw_addr; + u32 tbl_dw; + u16 scd_q2ratid; + + scd_q2ratid = ra_tid & SCD_QUEUE_RA_TID_MAP_RATID_MSK; + + tbl_dw_addr = trans_pcie->scd_base_addr + + SCD_TRANS_TBL_OFFSET_QUEUE(txq_id); + + tbl_dw = iwl_trans_read_mem32(trans, tbl_dw_addr); + + if (txq_id & 0x1) + tbl_dw = (scd_q2ratid << 16) | (tbl_dw & 0x0000FFFF); + else + tbl_dw = scd_q2ratid | (tbl_dw & 0xFFFF0000); + + iwl_trans_write_mem32(trans, tbl_dw_addr, tbl_dw); + + return 0; +} + +/* Receiver address (actually, Rx station's index into station table), + * combined with Traffic ID (QOS priority), in format used by Tx Scheduler */ +#define BUILD_RAxTID(sta_id, tid) (((sta_id) << 4) + (tid)) + +bool iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int txq_id, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int wdg_timeout) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + int fifo = -1; + bool scd_bug = false; + + if (test_and_set_bit(txq_id, trans->txqs.queue_used)) + WARN_ONCE(1, "queue %d already used - expect issues", txq_id); + + txq->wd_timeout = msecs_to_jiffies(wdg_timeout); + + if (cfg) { + fifo = cfg->fifo; + + /* Disable the scheduler prior configuring the cmd queue */ + if (txq_id == trans->txqs.cmd.q_id && + trans_pcie->scd_set_active) + iwl_scd_enable_set_active(trans, 0); + + /* Stop this Tx queue before configuring it */ + iwl_scd_txq_set_inactive(trans, txq_id); + + /* Set this queue as a chain-building queue unless it is CMD */ + if (txq_id != trans->txqs.cmd.q_id) + iwl_scd_txq_set_chain(trans, txq_id); + + if (cfg->aggregate) { + u16 ra_tid = BUILD_RAxTID(cfg->sta_id, cfg->tid); + + /* Map receiver-address / traffic-ID to this queue */ + iwl_pcie_txq_set_ratid_map(trans, ra_tid, txq_id); + + /* enable aggregations for the queue */ + iwl_scd_txq_enable_agg(trans, txq_id); + txq->ampdu = true; + } else { + /* + * disable aggregations for the queue, this will also + * make the ra_tid mapping configuration irrelevant + * since it is now a non-AGG queue. + */ + iwl_scd_txq_disable_agg(trans, txq_id); + + ssn = txq->read_ptr; + } + } else { + /* + * If we need to move the SCD write pointer by steps of + * 0x40, 0x80 or 0xc0, it gets stuck. Avoids this and let + * the op_mode know by returning true later. + * Do this only in case cfg is NULL since this trick can + * be done only if we have DQA enabled which is true for mvm + * only. And mvm never sets a cfg pointer. + * This is really ugly, but this is the easiest way out for + * this sad hardware issue. + * This bug has been fixed on devices 9000 and up. + */ + scd_bug = !trans->trans_cfg->mq_rx_supported && + !((ssn - txq->write_ptr) & 0x3f) && + (ssn != txq->write_ptr); + if (scd_bug) + ssn++; + } + + /* Place first TFD at index corresponding to start sequence number. + * Assumes that ssn_idx is valid (!= 0xFFF) */ + txq->read_ptr = (ssn & 0xff); + txq->write_ptr = (ssn & 0xff); + iwl_write_direct32(trans, HBUS_TARG_WRPTR, + (ssn & 0xff) | (txq_id << 8)); + + if (cfg) { + u8 frame_limit = cfg->frame_limit; + + iwl_write_prph(trans, SCD_QUEUE_RDPTR(txq_id), ssn); + + /* Set up Tx window size and frame limit for this queue */ + iwl_trans_write_mem32(trans, trans_pcie->scd_base_addr + + SCD_CONTEXT_QUEUE_OFFSET(txq_id), 0); + iwl_trans_write_mem32(trans, + trans_pcie->scd_base_addr + + SCD_CONTEXT_QUEUE_OFFSET(txq_id) + sizeof(u32), + SCD_QUEUE_CTX_REG2_VAL(WIN_SIZE, frame_limit) | + SCD_QUEUE_CTX_REG2_VAL(FRAME_LIMIT, frame_limit)); + + /* Set up status area in SRAM, map to Tx DMA/FIFO, activate */ + iwl_write_prph(trans, SCD_QUEUE_STATUS_BITS(txq_id), + (1 << SCD_QUEUE_STTS_REG_POS_ACTIVE) | + (cfg->fifo << SCD_QUEUE_STTS_REG_POS_TXF) | + (1 << SCD_QUEUE_STTS_REG_POS_WSL) | + SCD_QUEUE_STTS_REG_MSK); + + /* enable the scheduler for this queue (only) */ + if (txq_id == trans->txqs.cmd.q_id && + trans_pcie->scd_set_active) + iwl_scd_enable_set_active(trans, BIT(txq_id)); + + IWL_DEBUG_TX_QUEUES(trans, + "Activate queue %d on FIFO %d WrPtr: %d\n", + txq_id, fifo, ssn & 0xff); + } else { + IWL_DEBUG_TX_QUEUES(trans, + "Activate queue %d WrPtr: %d\n", + txq_id, ssn & 0xff); + } + + return scd_bug; +} + +void iwl_trans_pcie_txq_set_shared_mode(struct iwl_trans *trans, u32 txq_id, + bool shared_mode) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + + txq->ampdu = !shared_mode; +} + +void iwl_trans_pcie_txq_disable(struct iwl_trans *trans, int txq_id, + bool configure_scd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 stts_addr = trans_pcie->scd_base_addr + + SCD_TX_STTS_QUEUE_OFFSET(txq_id); + static const u32 zero_val[4] = {}; + + trans->txqs.txq[txq_id]->frozen_expiry_remainder = 0; + trans->txqs.txq[txq_id]->frozen = false; + + /* + * Upon HW Rfkill - we stop the device, and then stop the queues + * in the op_mode. Just for the sake of the simplicity of the op_mode, + * allow the op_mode to call txq_disable after it already called + * stop_device. + */ + if (!test_and_clear_bit(txq_id, trans->txqs.queue_used)) { + WARN_ONCE(test_bit(STATUS_DEVICE_ENABLED, &trans->status), + "queue %d not used", txq_id); + return; + } + + if (configure_scd) { + iwl_scd_txq_set_inactive(trans, txq_id); + + iwl_trans_write_mem(trans, stts_addr, zero_val, + ARRAY_SIZE(zero_val)); + } + + iwl_pcie_txq_unmap(trans, txq_id); + trans->txqs.txq[txq_id]->ampdu = false; + + IWL_DEBUG_TX_QUEUES(trans, "Deactivate queue %d\n", txq_id); +} + +/*************** HOST COMMAND QUEUE FUNCTIONS *****/ + +/* + * iwl_pcie_enqueue_hcmd - enqueue a uCode command + * @priv: device private data point + * @cmd: a pointer to the ucode command structure + * + * The function returns < 0 values to indicate the operation + * failed. On success, it returns the index (>= 0) of command in the + * command queue. + */ +int iwl_pcie_enqueue_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + struct iwl_device_cmd *out_cmd; + struct iwl_cmd_meta *out_meta; + void *dup_buf = NULL; + dma_addr_t phys_addr; + int idx; + u16 copy_size, cmd_size, tb0_size; + bool had_nocopy = false; + u8 group_id = iwl_cmd_groupid(cmd->id); + int i, ret; + u32 cmd_pos; + const u8 *cmddata[IWL_MAX_CMD_TBS_PER_TFD]; + u16 cmdlen[IWL_MAX_CMD_TBS_PER_TFD]; + unsigned long flags; + + if (WARN(!trans->wide_cmd_header && + group_id > IWL_ALWAYS_LONG_GROUP, + "unsupported wide command %#x\n", cmd->id)) + return -EINVAL; + + if (group_id != 0) { + copy_size = sizeof(struct iwl_cmd_header_wide); + cmd_size = sizeof(struct iwl_cmd_header_wide); + } else { + copy_size = sizeof(struct iwl_cmd_header); + cmd_size = sizeof(struct iwl_cmd_header); + } + + /* need one for the header if the first is NOCOPY */ + BUILD_BUG_ON(IWL_MAX_CMD_TBS_PER_TFD > IWL_NUM_OF_TBS - 1); + + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + cmddata[i] = cmd->data[i]; + cmdlen[i] = cmd->len[i]; + + if (!cmd->len[i]) + continue; + + /* need at least IWL_FIRST_TB_SIZE copied */ + if (copy_size < IWL_FIRST_TB_SIZE) { + int copy = IWL_FIRST_TB_SIZE - copy_size; + + if (copy > cmdlen[i]) + copy = cmdlen[i]; + cmdlen[i] -= copy; + cmddata[i] += copy; + copy_size += copy; + } + + if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) { + had_nocopy = true; + if (WARN_ON(cmd->dataflags[i] & IWL_HCMD_DFL_DUP)) { + idx = -EINVAL; + goto free_dup_buf; + } + } else if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) { + /* + * This is also a chunk that isn't copied + * to the static buffer so set had_nocopy. + */ + had_nocopy = true; + + /* only allowed once */ + if (WARN_ON(dup_buf)) { + idx = -EINVAL; + goto free_dup_buf; + } + + dup_buf = kmemdup(cmddata[i], cmdlen[i], + GFP_ATOMIC); + if (!dup_buf) + return -ENOMEM; + } else { + /* NOCOPY must not be followed by normal! */ + if (WARN_ON(had_nocopy)) { + idx = -EINVAL; + goto free_dup_buf; + } + copy_size += cmdlen[i]; + } + cmd_size += cmd->len[i]; + } + + /* + * If any of the command structures end up being larger than + * the TFD_MAX_PAYLOAD_SIZE and they aren't dynamically + * allocated into separate TFDs, then we will need to + * increase the size of the buffers. + */ + if (WARN(copy_size > TFD_MAX_PAYLOAD_SIZE, + "Command %s (%#x) is too large (%d bytes)\n", + iwl_get_cmd_string(trans, cmd->id), + cmd->id, copy_size)) { + idx = -EINVAL; + goto free_dup_buf; + } + + spin_lock_irqsave(&txq->lock, flags); + + if (iwl_txq_space(trans, txq) < ((cmd->flags & CMD_ASYNC) ? 2 : 1)) { + spin_unlock_irqrestore(&txq->lock, flags); + + IWL_ERR(trans, "No space in command queue\n"); + iwl_op_mode_cmd_queue_full(trans->op_mode); + idx = -ENOSPC; + goto free_dup_buf; + } + + idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + out_cmd = txq->entries[idx].cmd; + out_meta = &txq->entries[idx].meta; + + memset(out_meta, 0, sizeof(*out_meta)); /* re-initialize to NULL */ + if (cmd->flags & CMD_WANT_SKB) + out_meta->source = cmd; + + /* set up the header */ + if (group_id != 0) { + out_cmd->hdr_wide.cmd = iwl_cmd_opcode(cmd->id); + out_cmd->hdr_wide.group_id = group_id; + out_cmd->hdr_wide.version = iwl_cmd_version(cmd->id); + out_cmd->hdr_wide.length = + cpu_to_le16(cmd_size - + sizeof(struct iwl_cmd_header_wide)); + out_cmd->hdr_wide.reserved = 0; + out_cmd->hdr_wide.sequence = + cpu_to_le16(QUEUE_TO_SEQ(trans->txqs.cmd.q_id) | + INDEX_TO_SEQ(txq->write_ptr)); + + cmd_pos = sizeof(struct iwl_cmd_header_wide); + copy_size = sizeof(struct iwl_cmd_header_wide); + } else { + out_cmd->hdr.cmd = iwl_cmd_opcode(cmd->id); + out_cmd->hdr.sequence = + cpu_to_le16(QUEUE_TO_SEQ(trans->txqs.cmd.q_id) | + INDEX_TO_SEQ(txq->write_ptr)); + out_cmd->hdr.group_id = 0; + + cmd_pos = sizeof(struct iwl_cmd_header); + copy_size = sizeof(struct iwl_cmd_header); + } + + /* and copy the data that needs to be copied */ + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + int copy; + + if (!cmd->len[i]) + continue; + + /* copy everything if not nocopy/dup */ + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) { + copy = cmd->len[i]; + + memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy); + cmd_pos += copy; + copy_size += copy; + continue; + } + + /* + * Otherwise we need at least IWL_FIRST_TB_SIZE copied + * in total (for bi-directional DMA), but copy up to what + * we can fit into the payload for debug dump purposes. + */ + copy = min_t(int, TFD_MAX_PAYLOAD_SIZE - cmd_pos, cmd->len[i]); + + memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy); + cmd_pos += copy; + + /* However, treat copy_size the proper way, we need it below */ + if (copy_size < IWL_FIRST_TB_SIZE) { + copy = IWL_FIRST_TB_SIZE - copy_size; + + if (copy > cmd->len[i]) + copy = cmd->len[i]; + copy_size += copy; + } + } + + IWL_DEBUG_HC(trans, + "Sending command %s (%.2x.%.2x), seq: 0x%04X, %d bytes at %d[%d]:%d\n", + iwl_get_cmd_string(trans, cmd->id), + group_id, out_cmd->hdr.cmd, + le16_to_cpu(out_cmd->hdr.sequence), + cmd_size, txq->write_ptr, idx, trans->txqs.cmd.q_id); + + /* start the TFD with the minimum copy bytes */ + tb0_size = min_t(int, copy_size, IWL_FIRST_TB_SIZE); + memcpy(&txq->first_tb_bufs[idx], &out_cmd->hdr, tb0_size); + iwl_pcie_txq_build_tfd(trans, txq, + iwl_txq_get_first_tb_dma(txq, idx), + tb0_size, true); + + /* map first command fragment, if any remains */ + if (copy_size > tb0_size) { + phys_addr = dma_map_single(trans->dev, + ((u8 *)&out_cmd->hdr) + tb0_size, + copy_size - tb0_size, + DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + iwl_txq_gen1_tfd_unmap(trans, out_meta, txq, + txq->write_ptr); + idx = -ENOMEM; + goto out; + } + + iwl_pcie_txq_build_tfd(trans, txq, phys_addr, + copy_size - tb0_size, false); + } + + /* map the remaining (adjusted) nocopy/dup fragments */ + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + void *data = (void *)(uintptr_t)cmddata[i]; + + if (!cmdlen[i]) + continue; + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) + continue; + if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) + data = dup_buf; + phys_addr = dma_map_single(trans->dev, data, + cmdlen[i], DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + iwl_txq_gen1_tfd_unmap(trans, out_meta, txq, + txq->write_ptr); + idx = -ENOMEM; + goto out; + } + + iwl_pcie_txq_build_tfd(trans, txq, phys_addr, cmdlen[i], false); + } + + BUILD_BUG_ON(IWL_TFH_NUM_TBS > sizeof(out_meta->tbs) * BITS_PER_BYTE); + out_meta->flags = cmd->flags; + if (WARN_ON_ONCE(txq->entries[idx].free_buf)) + kfree_sensitive(txq->entries[idx].free_buf); + txq->entries[idx].free_buf = dup_buf; + + trace_iwlwifi_dev_hcmd(trans->dev, cmd, cmd_size, &out_cmd->hdr_wide); + + /* start timer if queue currently empty */ + if (txq->read_ptr == txq->write_ptr && txq->wd_timeout) + mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout); + + ret = iwl_pcie_set_cmd_in_flight(trans, cmd); + if (ret < 0) { + idx = ret; + goto out; + } + + /* Increment and update queue's write index */ + txq->write_ptr = iwl_txq_inc_wrap(trans, txq->write_ptr); + iwl_pcie_txq_inc_wr_ptr(trans, txq); + + out: + spin_unlock_irqrestore(&txq->lock, flags); + free_dup_buf: + if (idx < 0) + kfree(dup_buf); + return idx; +} + +/* + * iwl_pcie_hcmd_complete - Pull unused buffers off the queue and reclaim them + * @rxb: Rx buffer to reclaim + */ +void iwl_pcie_hcmd_complete(struct iwl_trans *trans, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + u8 group_id; + u32 cmd_id; + int txq_id = SEQ_TO_QUEUE(sequence); + int index = SEQ_TO_INDEX(sequence); + int cmd_index; + struct iwl_device_cmd *cmd; + struct iwl_cmd_meta *meta; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + + /* If a Tx command is being handled and it isn't in the actual + * command queue then there a command routing bug has been introduced + * in the queue management code. */ + if (WARN(txq_id != trans->txqs.cmd.q_id, + "wrong command queue %d (should be %d), sequence 0x%X readp=%d writep=%d\n", + txq_id, trans->txqs.cmd.q_id, sequence, txq->read_ptr, + txq->write_ptr)) { + iwl_print_hex_error(trans, pkt, 32); + return; + } + + spin_lock_bh(&txq->lock); + + cmd_index = iwl_txq_get_cmd_index(txq, index); + cmd = txq->entries[cmd_index].cmd; + meta = &txq->entries[cmd_index].meta; + group_id = cmd->hdr.group_id; + cmd_id = iwl_cmd_id(cmd->hdr.cmd, group_id, 0); + + iwl_txq_gen1_tfd_unmap(trans, meta, txq, index); + + /* Input error checking is done when commands are added to queue. */ + if (meta->flags & CMD_WANT_SKB) { + struct page *p = rxb_steal_page(rxb); + + meta->source->resp_pkt = pkt; +#if defined(__linux__) + meta->source->_rx_page_addr = (unsigned long)page_address(p); +#elif defined(__FreeBSD__) + meta->source->_page = p; +#endif + meta->source->_rx_page_order = trans_pcie->rx_page_order; + } + + if (meta->flags & CMD_WANT_ASYNC_CALLBACK) + iwl_op_mode_async_cb(trans->op_mode, cmd); + + iwl_pcie_cmdq_reclaim(trans, txq_id, index); + + if (!(meta->flags & CMD_ASYNC)) { + if (!test_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status)) { + IWL_WARN(trans, + "HCMD_ACTIVE already clear for command %s\n", + iwl_get_cmd_string(trans, cmd_id)); + } + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + IWL_DEBUG_INFO(trans, "Clearing HCMD_ACTIVE for command %s\n", + iwl_get_cmd_string(trans, cmd_id)); + wake_up(&trans->wait_command_queue); + } + + meta->flags = 0; + + spin_unlock_bh(&txq->lock); +} + +static int iwl_fill_data_tbs(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_txq *txq, u8 hdr_len, + struct iwl_cmd_meta *out_meta) +{ + u16 head_tb_len; + int i; + + /* + * Set up TFD's third entry to point directly to remainder + * of skb's head, if any + */ + head_tb_len = skb_headlen(skb) - hdr_len; + + if (head_tb_len > 0) { + dma_addr_t tb_phys = dma_map_single(trans->dev, + skb->data + hdr_len, + head_tb_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + return -EINVAL; + trace_iwlwifi_dev_tx_tb(trans->dev, skb, skb->data + hdr_len, + tb_phys, head_tb_len); + iwl_pcie_txq_build_tfd(trans, txq, tb_phys, head_tb_len, false); + } + + /* set up the remaining entries to point to the data */ + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + dma_addr_t tb_phys; + int tb_idx; + + if (!skb_frag_size(frag)) + continue; + + tb_phys = skb_frag_dma_map(trans->dev, frag, 0, + skb_frag_size(frag), DMA_TO_DEVICE); + + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + return -EINVAL; + trace_iwlwifi_dev_tx_tb(trans->dev, skb, skb_frag_address(frag), + tb_phys, skb_frag_size(frag)); + tb_idx = iwl_pcie_txq_build_tfd(trans, txq, tb_phys, + skb_frag_size(frag), false); + if (tb_idx < 0) + return tb_idx; + + out_meta->tbs |= BIT(tb_idx); + } + + return 0; +} + +#ifdef CONFIG_INET +static int iwl_fill_data_tbs_amsdu(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_txq *txq, u8 hdr_len, + struct iwl_cmd_meta *out_meta, + struct iwl_device_tx_cmd *dev_cmd, + u16 tb1_len) +{ + struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload; + struct ieee80211_hdr *hdr = (void *)skb->data; + unsigned int snap_ip_tcp_hdrlen, ip_hdrlen, total_len, hdr_room; + unsigned int mss = skb_shinfo(skb)->gso_size; + u16 length, iv_len, amsdu_pad; + u8 *start_hdr; + struct iwl_tso_hdr_page *hdr_page; + struct tso_t tso; + + /* if the packet is protected, then it must be CCMP or GCMP */ + BUILD_BUG_ON(IEEE80211_CCMP_HDR_LEN != IEEE80211_GCMP_HDR_LEN); + iv_len = ieee80211_has_protected(hdr->frame_control) ? + IEEE80211_CCMP_HDR_LEN : 0; + + trace_iwlwifi_dev_tx(trans->dev, skb, + iwl_txq_get_tfd(trans, txq, txq->write_ptr), + trans->txqs.tfd.size, + &dev_cmd->hdr, IWL_FIRST_TB_SIZE + tb1_len, 0); + + ip_hdrlen = skb_transport_header(skb) - skb_network_header(skb); + snap_ip_tcp_hdrlen = 8 + ip_hdrlen + tcp_hdrlen(skb); + total_len = skb->len - snap_ip_tcp_hdrlen - hdr_len - iv_len; + amsdu_pad = 0; + + /* total amount of header we may need for this A-MSDU */ + hdr_room = DIV_ROUND_UP(total_len, mss) * + (3 + snap_ip_tcp_hdrlen + sizeof(struct ethhdr)) + iv_len; + + /* Our device supports 9 segments at most, it will fit in 1 page */ + hdr_page = get_page_hdr(trans, hdr_room, skb); + if (!hdr_page) + return -ENOMEM; + + start_hdr = hdr_page->pos; + memcpy(hdr_page->pos, skb->data + hdr_len, iv_len); + hdr_page->pos += iv_len; + + /* + * Pull the ieee80211 header + IV to be able to use TSO core, + * we will restore it for the tx_status flow. + */ + skb_pull(skb, hdr_len + iv_len); + + /* + * Remove the length of all the headers that we don't actually + * have in the MPDU by themselves, but that we duplicate into + * all the different MSDUs inside the A-MSDU. + */ + le16_add_cpu(&tx_cmd->len, -snap_ip_tcp_hdrlen); + + tso_start(skb, &tso); + + while (total_len) { + /* this is the data left for this subframe */ + unsigned int data_left = + min_t(unsigned int, mss, total_len); + unsigned int hdr_tb_len; + dma_addr_t hdr_tb_phys; + u8 *subf_hdrs_start = hdr_page->pos; + + total_len -= data_left; + + memset(hdr_page->pos, 0, amsdu_pad); + hdr_page->pos += amsdu_pad; + amsdu_pad = (4 - (sizeof(struct ethhdr) + snap_ip_tcp_hdrlen + + data_left)) & 0x3; + ether_addr_copy(hdr_page->pos, ieee80211_get_DA(hdr)); + hdr_page->pos += ETH_ALEN; + ether_addr_copy(hdr_page->pos, ieee80211_get_SA(hdr)); + hdr_page->pos += ETH_ALEN; + + length = snap_ip_tcp_hdrlen + data_left; + *((__be16 *)hdr_page->pos) = cpu_to_be16(length); + hdr_page->pos += sizeof(length); + + /* + * This will copy the SNAP as well which will be considered + * as MAC header. + */ + tso_build_hdr(skb, hdr_page->pos, &tso, data_left, !total_len); + + hdr_page->pos += snap_ip_tcp_hdrlen; + + hdr_tb_len = hdr_page->pos - start_hdr; + hdr_tb_phys = dma_map_single(trans->dev, start_hdr, + hdr_tb_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, hdr_tb_phys))) + return -EINVAL; + iwl_pcie_txq_build_tfd(trans, txq, hdr_tb_phys, + hdr_tb_len, false); + trace_iwlwifi_dev_tx_tb(trans->dev, skb, start_hdr, + hdr_tb_phys, hdr_tb_len); + /* add this subframe's headers' length to the tx_cmd */ + le16_add_cpu(&tx_cmd->len, hdr_page->pos - subf_hdrs_start); + + /* prepare the start_hdr for the next subframe */ + start_hdr = hdr_page->pos; + + /* put the payload */ + while (data_left) { + unsigned int size = min_t(unsigned int, tso.size, + data_left); + dma_addr_t tb_phys; + + tb_phys = dma_map_single(trans->dev, tso.data, + size, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + return -EINVAL; + + iwl_pcie_txq_build_tfd(trans, txq, tb_phys, + size, false); + trace_iwlwifi_dev_tx_tb(trans->dev, skb, tso.data, + tb_phys, size); + + data_left -= size; + tso_build_data(skb, &tso, size); + } + } + + /* re -add the WiFi header and IV */ + skb_push(skb, hdr_len + iv_len); + + return 0; +} +#else /* CONFIG_INET */ +static int iwl_fill_data_tbs_amsdu(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_txq *txq, u8 hdr_len, + struct iwl_cmd_meta *out_meta, + struct iwl_device_tx_cmd *dev_cmd, + u16 tb1_len) +{ + /* No A-MSDU without CONFIG_INET */ + WARN_ON(1); + + return -1; +} +#endif /* CONFIG_INET */ + +int iwl_trans_pcie_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int txq_id) +{ + struct ieee80211_hdr *hdr; + struct iwl_tx_cmd *tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + struct iwl_cmd_meta *out_meta; + struct iwl_txq *txq; + dma_addr_t tb0_phys, tb1_phys, scratch_phys; + void *tb1_addr; + void *tfd; + u16 len, tb1_len; + bool wait_write_ptr; + __le16 fc; + u8 hdr_len; + u16 wifi_seq; + bool amsdu; + + txq = trans->txqs.txq[txq_id]; + + if (WARN_ONCE(!test_bit(txq_id, trans->txqs.queue_used), + "TX on unused queue %d\n", txq_id)) + return -EINVAL; + + if (skb_is_nonlinear(skb) && + skb_shinfo(skb)->nr_frags > IWL_TRANS_MAX_FRAGS(trans) && + __skb_linearize(skb)) + return -ENOMEM; + + /* mac80211 always puts the full header into the SKB's head, + * so there's no need to check if it's readable there + */ + hdr = (struct ieee80211_hdr *)skb->data; + fc = hdr->frame_control; + hdr_len = ieee80211_hdrlen(fc); + + spin_lock(&txq->lock); + + if (iwl_txq_space(trans, txq) < txq->high_mark) { + iwl_txq_stop(trans, txq); + + /* don't put the packet on the ring, if there is no room */ + if (unlikely(iwl_txq_space(trans, txq) < 3)) { + struct iwl_device_tx_cmd **dev_cmd_ptr; + + dev_cmd_ptr = (void *)((u8 *)skb->cb + + trans->txqs.dev_cmd_offs); + + *dev_cmd_ptr = dev_cmd; + __skb_queue_tail(&txq->overflow_q, skb); + + spin_unlock(&txq->lock); + return 0; + } + } + + /* In AGG mode, the index in the ring must correspond to the WiFi + * sequence number. This is a HW requirements to help the SCD to parse + * the BA. + * Check here that the packets are in the right place on the ring. + */ + wifi_seq = IEEE80211_SEQ_TO_SN(le16_to_cpu(hdr->seq_ctrl)); + WARN_ONCE(txq->ampdu && + (wifi_seq & 0xff) != txq->write_ptr, + "Q: %d WiFi Seq %d tfdNum %d", + txq_id, wifi_seq, txq->write_ptr); + + /* Set up driver data for this TFD */ + txq->entries[txq->write_ptr].skb = skb; + txq->entries[txq->write_ptr].cmd = dev_cmd; + + dev_cmd->hdr.sequence = + cpu_to_le16((u16)(QUEUE_TO_SEQ(txq_id) | + INDEX_TO_SEQ(txq->write_ptr))); + + tb0_phys = iwl_txq_get_first_tb_dma(txq, txq->write_ptr); + scratch_phys = tb0_phys + sizeof(struct iwl_cmd_header) + + offsetof(struct iwl_tx_cmd, scratch); + + tx_cmd->dram_lsb_ptr = cpu_to_le32(scratch_phys); + tx_cmd->dram_msb_ptr = iwl_get_dma_hi_addr(scratch_phys); + + /* Set up first empty entry in queue's array of Tx/cmd buffers */ + out_meta = &txq->entries[txq->write_ptr].meta; + out_meta->flags = 0; + + /* + * The second TB (tb1) points to the remainder of the TX command + * and the 802.11 header - dword aligned size + * (This calculation modifies the TX command, so do it before the + * setup of the first TB) + */ + len = sizeof(struct iwl_tx_cmd) + sizeof(struct iwl_cmd_header) + + hdr_len - IWL_FIRST_TB_SIZE; + /* do not align A-MSDU to dword as the subframe header aligns it */ + amsdu = ieee80211_is_data_qos(fc) && + (*ieee80211_get_qos_ctl(hdr) & + IEEE80211_QOS_CTL_A_MSDU_PRESENT); + if (!amsdu) { + tb1_len = ALIGN(len, 4); + /* Tell NIC about any 2-byte padding after MAC header */ + if (tb1_len != len) + tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_MH_PAD); + } else { + tb1_len = len; + } + + /* + * The first TB points to bi-directional DMA data, we'll + * memcpy the data into it later. + */ + iwl_pcie_txq_build_tfd(trans, txq, tb0_phys, + IWL_FIRST_TB_SIZE, true); + + /* there must be data left over for TB1 or this code must be changed */ + BUILD_BUG_ON(sizeof(struct iwl_tx_cmd) < IWL_FIRST_TB_SIZE); + + /* map the data for TB1 */ + tb1_addr = ((u8 *)&dev_cmd->hdr) + IWL_FIRST_TB_SIZE; + tb1_phys = dma_map_single(trans->dev, tb1_addr, tb1_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb1_phys))) + goto out_err; + iwl_pcie_txq_build_tfd(trans, txq, tb1_phys, tb1_len, false); + + trace_iwlwifi_dev_tx(trans->dev, skb, + iwl_txq_get_tfd(trans, txq, txq->write_ptr), + trans->txqs.tfd.size, + &dev_cmd->hdr, IWL_FIRST_TB_SIZE + tb1_len, + hdr_len); + + /* + * If gso_size wasn't set, don't give the frame "amsdu treatment" + * (adding subframes, etc.). + * This can happen in some testing flows when the amsdu was already + * pre-built, and we just need to send the resulting skb. + */ + if (amsdu && skb_shinfo(skb)->gso_size) { + if (unlikely(iwl_fill_data_tbs_amsdu(trans, skb, txq, hdr_len, + out_meta, dev_cmd, + tb1_len))) + goto out_err; + } else { + struct sk_buff *frag; + + if (unlikely(iwl_fill_data_tbs(trans, skb, txq, hdr_len, + out_meta))) + goto out_err; + + skb_walk_frags(skb, frag) { + if (unlikely(iwl_fill_data_tbs(trans, frag, txq, 0, + out_meta))) + goto out_err; + } + } + + /* building the A-MSDU might have changed this data, so memcpy it now */ + memcpy(&txq->first_tb_bufs[txq->write_ptr], dev_cmd, IWL_FIRST_TB_SIZE); + + tfd = iwl_txq_get_tfd(trans, txq, txq->write_ptr); + /* Set up entry for this TFD in Tx byte-count array */ + iwl_txq_gen1_update_byte_cnt_tbl(trans, txq, le16_to_cpu(tx_cmd->len), + iwl_txq_gen1_tfd_get_num_tbs(trans, + tfd)); + + wait_write_ptr = ieee80211_has_morefrags(fc); + + /* start timer if queue currently empty */ + if (txq->read_ptr == txq->write_ptr && txq->wd_timeout) { + /* + * If the TXQ is active, then set the timer, if not, + * set the timer in remainder so that the timer will + * be armed with the right value when the station will + * wake up. + */ + if (!txq->frozen) + mod_timer(&txq->stuck_timer, + jiffies + txq->wd_timeout); + else + txq->frozen_expiry_remainder = txq->wd_timeout; + } + + /* Tell device the write index *just past* this latest filled TFD */ + txq->write_ptr = iwl_txq_inc_wrap(trans, txq->write_ptr); + if (!wait_write_ptr) + iwl_pcie_txq_inc_wr_ptr(trans, txq); + + /* + * At this point the frame is "transmitted" successfully + * and we will get a TX status notification eventually. + */ + spin_unlock(&txq->lock); + return 0; +out_err: + iwl_txq_gen1_tfd_unmap(trans, out_meta, txq, txq->write_ptr); + spin_unlock(&txq->lock); + return -1; +} diff --git a/sys/contrib/dev/iwlwifi/queue/tx.c b/sys/contrib/dev/iwlwifi/queue/tx.c new file mode 100644 index 00000000000..efb68b45f02 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/queue/tx.c @@ -0,0 +1,1843 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2020-2021 Intel Corporation + */ +#ifdef CONFIG_INET +#include +#endif +#include + +#include "iwl-debug.h" +#include "iwl-io.h" +#include "fw/api/tx.h" +#include "queue/tx.h" +#include "iwl-fh.h" +#include "iwl-scd.h" +#include +#if defined(__FreeBSD__) +#include +#endif + +/* + * iwl_txq_update_byte_tbl - Set up entry in Tx byte-count array + */ +static void iwl_pcie_gen2_update_byte_tbl(struct iwl_trans *trans, + struct iwl_txq *txq, u16 byte_cnt, + int num_tbs) +{ + int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + u8 filled_tfd_size, num_fetch_chunks; + u16 len = byte_cnt; + __le16 bc_ent; + + if (WARN(idx >= txq->n_window, "%d >= %d\n", idx, txq->n_window)) + return; + + filled_tfd_size = offsetof(struct iwl_tfh_tfd, tbs) + + num_tbs * sizeof(struct iwl_tfh_tb); + /* + * filled_tfd_size contains the number of filled bytes in the TFD. + * Dividing it by 64 will give the number of chunks to fetch + * to SRAM- 0 for one chunk, 1 for 2 and so on. + * If, for example, TFD contains only 3 TBs then 32 bytes + * of the TFD are used, and only one chunk of 64 bytes should + * be fetched + */ + num_fetch_chunks = DIV_ROUND_UP(filled_tfd_size, 64) - 1; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + struct iwl_gen3_bc_tbl *scd_bc_tbl_gen3 = txq->bc_tbl.addr; + + /* Starting from AX210, the HW expects bytes */ + WARN_ON(trans->txqs.bc_table_dword); + WARN_ON(len > 0x3FFF); + bc_ent = cpu_to_le16(len | (num_fetch_chunks << 14)); + scd_bc_tbl_gen3->tfd_offset[idx] = bc_ent; + } else { + struct iwlagn_scd_bc_tbl *scd_bc_tbl = txq->bc_tbl.addr; + + /* Before AX210, the HW expects DW */ + WARN_ON(!trans->txqs.bc_table_dword); + len = DIV_ROUND_UP(len, 4); + WARN_ON(len > 0xFFF); + bc_ent = cpu_to_le16(len | (num_fetch_chunks << 12)); + scd_bc_tbl->tfd_offset[idx] = bc_ent; + } +} + +/* + * iwl_txq_inc_wr_ptr - Send new write index to hardware + */ +void iwl_txq_inc_wr_ptr(struct iwl_trans *trans, struct iwl_txq *txq) +{ + lockdep_assert_held(&txq->lock); + + IWL_DEBUG_TX(trans, "Q:%d WR: 0x%x\n", txq->id, txq->write_ptr); + + /* + * if not in power-save mode, uCode will never sleep when we're + * trying to tx (during RFKILL, we're not trying to tx). + */ + iwl_write32(trans, HBUS_TARG_WRPTR, txq->write_ptr | (txq->id << 16)); +} + +static u8 iwl_txq_gen2_get_num_tbs(struct iwl_trans *trans, + struct iwl_tfh_tfd *tfd) +{ + return le16_to_cpu(tfd->num_tbs) & 0x1f; +} + +void iwl_txq_gen2_tfd_unmap(struct iwl_trans *trans, struct iwl_cmd_meta *meta, + struct iwl_tfh_tfd *tfd) +{ + int i, num_tbs; + + /* Sanity check on number of chunks */ + num_tbs = iwl_txq_gen2_get_num_tbs(trans, tfd); + + if (num_tbs > trans->txqs.tfd.max_tbs) { + IWL_ERR(trans, "Too many chunks: %i\n", num_tbs); + return; + } + + /* first TB is never freed - it's the bidirectional DMA data */ + for (i = 1; i < num_tbs; i++) { + if (meta->tbs & BIT(i)) + dma_unmap_page(trans->dev, + le64_to_cpu(tfd->tbs[i].addr), + le16_to_cpu(tfd->tbs[i].tb_len), + DMA_TO_DEVICE); + else + dma_unmap_single(trans->dev, + le64_to_cpu(tfd->tbs[i].addr), + le16_to_cpu(tfd->tbs[i].tb_len), + DMA_TO_DEVICE); + } + + tfd->num_tbs = 0; +} + +void iwl_txq_gen2_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq) +{ + /* rd_ptr is bounded by TFD_QUEUE_SIZE_MAX and + * idx is bounded by n_window + */ + int idx = iwl_txq_get_cmd_index(txq, txq->read_ptr); + struct sk_buff *skb; + + lockdep_assert_held(&txq->lock); + + if (!txq->entries) + return; + + iwl_txq_gen2_tfd_unmap(trans, &txq->entries[idx].meta, + iwl_txq_get_tfd(trans, txq, idx)); + + skb = txq->entries[idx].skb; + + /* Can be called from irqs-disabled context + * If skb is not NULL, it means that the whole queue is being + * freed and that the queue is not empty - free the skb + */ + if (skb) { + iwl_op_mode_free_skb(trans->op_mode, skb); + txq->entries[idx].skb = NULL; + } +} + +int iwl_txq_gen2_set_tb(struct iwl_trans *trans, struct iwl_tfh_tfd *tfd, + dma_addr_t addr, u16 len) +{ + int idx = iwl_txq_gen2_get_num_tbs(trans, tfd); + struct iwl_tfh_tb *tb; + + /* + * Only WARN here so we know about the issue, but we mess up our + * unmap path because not every place currently checks for errors + * returned from this function - it can only return an error if + * there's no more space, and so when we know there is enough we + * don't always check ... + */ + WARN(iwl_txq_crosses_4g_boundary(addr, len), + "possible DMA problem with iova:0x%llx, len:%d\n", + (unsigned long long)addr, len); + + if (WARN_ON(idx >= IWL_TFH_NUM_TBS)) + return -EINVAL; + tb = &tfd->tbs[idx]; + + /* Each TFD can point to a maximum max_tbs Tx buffers */ + if (le16_to_cpu(tfd->num_tbs) >= trans->txqs.tfd.max_tbs) { + IWL_ERR(trans, "Error can not send more than %d chunks\n", + trans->txqs.tfd.max_tbs); + return -EINVAL; + } + + put_unaligned_le64(addr, &tb->addr); + tb->tb_len = cpu_to_le16(len); + + tfd->num_tbs = cpu_to_le16(idx + 1); + + return idx; +} + +static struct page *get_workaround_page(struct iwl_trans *trans, + struct sk_buff *skb) +{ + struct page **page_ptr; + struct page *ret; + + page_ptr = (void *)((u8 *)skb->cb + trans->txqs.page_offs); + + ret = alloc_page(GFP_ATOMIC); + if (!ret) + return NULL; + + /* set the chaining pointer to the previous page if there */ + *(void **)((u8 *)page_address(ret) + PAGE_SIZE - sizeof(void *)) = + *page_ptr; + *page_ptr = ret; + + return ret; +} + +/* + * Add a TB and if needed apply the FH HW bug workaround; + * meta != NULL indicates that it's a page mapping and we + * need to dma_unmap_page() and set the meta->tbs bit in + * this case. + */ +static int iwl_txq_gen2_set_tb_with_wa(struct iwl_trans *trans, + struct sk_buff *skb, + struct iwl_tfh_tfd *tfd, + dma_addr_t phys, void *virt, + u16 len, struct iwl_cmd_meta *meta) +{ + dma_addr_t oldphys = phys; + struct page *page; + int ret; + + if (unlikely(dma_mapping_error(trans->dev, phys))) + return -ENOMEM; + + if (likely(!iwl_txq_crosses_4g_boundary(phys, len))) { + ret = iwl_txq_gen2_set_tb(trans, tfd, phys, len); + + if (ret < 0) + goto unmap; + + if (meta) + meta->tbs |= BIT(ret); + + ret = 0; + goto trace; + } + + /* + * Work around a hardware bug. If (as expressed in the + * condition above) the TB ends on a 32-bit boundary, + * then the next TB may be accessed with the wrong + * address. + * To work around it, copy the data elsewhere and make + * a new mapping for it so the device will not fail. + */ + + if (WARN_ON(len > PAGE_SIZE - sizeof(void *))) { + ret = -ENOBUFS; + goto unmap; + } + + page = get_workaround_page(trans, skb); + if (!page) { + ret = -ENOMEM; + goto unmap; + } + + memcpy(page_address(page), virt, len); + + phys = dma_map_single(trans->dev, page_address(page), len, + DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, phys))) + return -ENOMEM; + ret = iwl_txq_gen2_set_tb(trans, tfd, phys, len); + if (ret < 0) { + /* unmap the new allocation as single */ + oldphys = phys; + meta = NULL; + goto unmap; + } + IWL_WARN(trans, + "TB bug workaround: copied %d bytes from 0x%llx to 0x%llx\n", + len, (unsigned long long)oldphys, (unsigned long long)phys); + + ret = 0; +unmap: + if (meta) + dma_unmap_page(trans->dev, oldphys, len, DMA_TO_DEVICE); + else + dma_unmap_single(trans->dev, oldphys, len, DMA_TO_DEVICE); +trace: + trace_iwlwifi_dev_tx_tb(trans->dev, skb, virt, phys, len); + + return ret; +} + +#ifdef CONFIG_INET +struct iwl_tso_hdr_page *get_page_hdr(struct iwl_trans *trans, size_t len, + struct sk_buff *skb) +{ + struct iwl_tso_hdr_page *p = this_cpu_ptr(trans->txqs.tso_hdr_page); + struct page **page_ptr; + + page_ptr = (void *)((u8 *)skb->cb + trans->txqs.page_offs); + + if (WARN_ON(*page_ptr)) + return NULL; + + if (!p->page) + goto alloc; + + /* + * Check if there's enough room on this page + * + * Note that we put a page chaining pointer *last* in the + * page - we need it somewhere, and if it's there then we + * avoid DMA mapping the last bits of the page which may + * trigger the 32-bit boundary hardware bug. + * + * (see also get_workaround_page() in tx-gen2.c) + */ + if (p->pos + len < (u8 *)page_address(p->page) + PAGE_SIZE - + sizeof(void *)) + goto out; + + /* We don't have enough room on this page, get a new one. */ + __free_page(p->page); + +alloc: + p->page = alloc_page(GFP_ATOMIC); + if (!p->page) + return NULL; + p->pos = page_address(p->page); + /* set the chaining pointer to NULL */ + *(void **)((u8 *)page_address(p->page) + PAGE_SIZE - sizeof(void *)) = + NULL; +out: + *page_ptr = p->page; + get_page(p->page); + return p; +} +#endif + +static int iwl_txq_gen2_build_amsdu(struct iwl_trans *trans, + struct sk_buff *skb, + struct iwl_tfh_tfd *tfd, int start_len, + u8 hdr_len, + struct iwl_device_tx_cmd *dev_cmd) +{ +#ifdef CONFIG_INET + struct iwl_tx_cmd_gen2 *tx_cmd = (void *)dev_cmd->payload; + struct ieee80211_hdr *hdr = (void *)skb->data; + unsigned int snap_ip_tcp_hdrlen, ip_hdrlen, total_len, hdr_room; + unsigned int mss = skb_shinfo(skb)->gso_size; + u16 length, amsdu_pad; + u8 *start_hdr; + struct iwl_tso_hdr_page *hdr_page; + struct tso_t tso; + + trace_iwlwifi_dev_tx(trans->dev, skb, tfd, sizeof(*tfd), + &dev_cmd->hdr, start_len, 0); + + ip_hdrlen = skb_transport_header(skb) - skb_network_header(skb); + snap_ip_tcp_hdrlen = 8 + ip_hdrlen + tcp_hdrlen(skb); + total_len = skb->len - snap_ip_tcp_hdrlen - hdr_len; + amsdu_pad = 0; + + /* total amount of header we may need for this A-MSDU */ + hdr_room = DIV_ROUND_UP(total_len, mss) * + (3 + snap_ip_tcp_hdrlen + sizeof(struct ethhdr)); + + /* Our device supports 9 segments at most, it will fit in 1 page */ + hdr_page = get_page_hdr(trans, hdr_room, skb); + if (!hdr_page) + return -ENOMEM; + + start_hdr = hdr_page->pos; + + /* + * Pull the ieee80211 header to be able to use TSO core, + * we will restore it for the tx_status flow. + */ + skb_pull(skb, hdr_len); + + /* + * Remove the length of all the headers that we don't actually + * have in the MPDU by themselves, but that we duplicate into + * all the different MSDUs inside the A-MSDU. + */ + le16_add_cpu(&tx_cmd->len, -snap_ip_tcp_hdrlen); + + tso_start(skb, &tso); + + while (total_len) { + /* this is the data left for this subframe */ + unsigned int data_left = min_t(unsigned int, mss, total_len); + unsigned int tb_len; + dma_addr_t tb_phys; + u8 *subf_hdrs_start = hdr_page->pos; + + total_len -= data_left; + + memset(hdr_page->pos, 0, amsdu_pad); + hdr_page->pos += amsdu_pad; + amsdu_pad = (4 - (sizeof(struct ethhdr) + snap_ip_tcp_hdrlen + + data_left)) & 0x3; + ether_addr_copy(hdr_page->pos, ieee80211_get_DA(hdr)); + hdr_page->pos += ETH_ALEN; + ether_addr_copy(hdr_page->pos, ieee80211_get_SA(hdr)); + hdr_page->pos += ETH_ALEN; + + length = snap_ip_tcp_hdrlen + data_left; + *((__be16 *)hdr_page->pos) = cpu_to_be16(length); + hdr_page->pos += sizeof(length); + + /* + * This will copy the SNAP as well which will be considered + * as MAC header. + */ + tso_build_hdr(skb, hdr_page->pos, &tso, data_left, !total_len); + + hdr_page->pos += snap_ip_tcp_hdrlen; + + tb_len = hdr_page->pos - start_hdr; + tb_phys = dma_map_single(trans->dev, start_hdr, + tb_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + goto out_err; + /* + * No need for _with_wa, this is from the TSO page and + * we leave some space at the end of it so can't hit + * the buggy scenario. + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, tb_len); + trace_iwlwifi_dev_tx_tb(trans->dev, skb, start_hdr, + tb_phys, tb_len); + /* add this subframe's headers' length to the tx_cmd */ + le16_add_cpu(&tx_cmd->len, hdr_page->pos - subf_hdrs_start); + + /* prepare the start_hdr for the next subframe */ + start_hdr = hdr_page->pos; + + /* put the payload */ + while (data_left) { + int ret; + + tb_len = min_t(unsigned int, tso.size, data_left); + tb_phys = dma_map_single(trans->dev, tso.data, + tb_len, DMA_TO_DEVICE); + ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, + tb_phys, tso.data, + tb_len, NULL); + if (ret) + goto out_err; + + data_left -= tb_len; + tso_build_data(skb, &tso, tb_len); + } + } + + /* re -add the WiFi header */ + skb_push(skb, hdr_len); + + return 0; + +out_err: +#endif + return -EINVAL; +} + +static struct +iwl_tfh_tfd *iwl_txq_gen2_build_tx_amsdu(struct iwl_trans *trans, + struct iwl_txq *txq, + struct iwl_device_tx_cmd *dev_cmd, + struct sk_buff *skb, + struct iwl_cmd_meta *out_meta, + int hdr_len, + int tx_cmd_len) +{ + int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + struct iwl_tfh_tfd *tfd = iwl_txq_get_tfd(trans, txq, idx); + dma_addr_t tb_phys; + int len; + void *tb1_addr; + + tb_phys = iwl_txq_get_first_tb_dma(txq, idx); + + /* + * No need for _with_wa, the first TB allocation is aligned up + * to a 64-byte boundary and thus can't be at the end or cross + * a page boundary (much less a 2^32 boundary). + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, IWL_FIRST_TB_SIZE); + + /* + * The second TB (tb1) points to the remainder of the TX command + * and the 802.11 header - dword aligned size + * (This calculation modifies the TX command, so do it before the + * setup of the first TB) + */ + len = tx_cmd_len + sizeof(struct iwl_cmd_header) + hdr_len - + IWL_FIRST_TB_SIZE; + + /* do not align A-MSDU to dword as the subframe header aligns it */ + + /* map the data for TB1 */ + tb1_addr = ((u8 *)&dev_cmd->hdr) + IWL_FIRST_TB_SIZE; + tb_phys = dma_map_single(trans->dev, tb1_addr, len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + goto out_err; + /* + * No need for _with_wa(), we ensure (via alignment) that the data + * here can never cross or end at a page boundary. + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, len); + + if (iwl_txq_gen2_build_amsdu(trans, skb, tfd, len + IWL_FIRST_TB_SIZE, + hdr_len, dev_cmd)) + goto out_err; + + /* building the A-MSDU might have changed this data, memcpy it now */ + memcpy(&txq->first_tb_bufs[idx], dev_cmd, IWL_FIRST_TB_SIZE); + return tfd; + +out_err: + iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd); + return NULL; +} + +static int iwl_txq_gen2_tx_add_frags(struct iwl_trans *trans, + struct sk_buff *skb, + struct iwl_tfh_tfd *tfd, + struct iwl_cmd_meta *out_meta) +{ + int i; + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + dma_addr_t tb_phys; + unsigned int fragsz = skb_frag_size(frag); + int ret; + + if (!fragsz) + continue; + + tb_phys = skb_frag_dma_map(trans->dev, frag, 0, + fragsz, DMA_TO_DEVICE); + ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, tb_phys, + skb_frag_address(frag), + fragsz, out_meta); + if (ret) + return ret; + } + + return 0; +} + +static struct +iwl_tfh_tfd *iwl_txq_gen2_build_tx(struct iwl_trans *trans, + struct iwl_txq *txq, + struct iwl_device_tx_cmd *dev_cmd, + struct sk_buff *skb, + struct iwl_cmd_meta *out_meta, + int hdr_len, + int tx_cmd_len, + bool pad) +{ + int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + struct iwl_tfh_tfd *tfd = iwl_txq_get_tfd(trans, txq, idx); + dma_addr_t tb_phys; + int len, tb1_len, tb2_len; + void *tb1_addr; + struct sk_buff *frag; + + tb_phys = iwl_txq_get_first_tb_dma(txq, idx); + + /* The first TB points to bi-directional DMA data */ + memcpy(&txq->first_tb_bufs[idx], dev_cmd, IWL_FIRST_TB_SIZE); + + /* + * No need for _with_wa, the first TB allocation is aligned up + * to a 64-byte boundary and thus can't be at the end or cross + * a page boundary (much less a 2^32 boundary). + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, IWL_FIRST_TB_SIZE); + + /* + * The second TB (tb1) points to the remainder of the TX command + * and the 802.11 header - dword aligned size + * (This calculation modifies the TX command, so do it before the + * setup of the first TB) + */ + len = tx_cmd_len + sizeof(struct iwl_cmd_header) + hdr_len - + IWL_FIRST_TB_SIZE; + + if (pad) + tb1_len = ALIGN(len, 4); + else + tb1_len = len; + + /* map the data for TB1 */ + tb1_addr = ((u8 *)&dev_cmd->hdr) + IWL_FIRST_TB_SIZE; + tb_phys = dma_map_single(trans->dev, tb1_addr, tb1_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + goto out_err; + /* + * No need for _with_wa(), we ensure (via alignment) that the data + * here can never cross or end at a page boundary. + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, tb1_len); + trace_iwlwifi_dev_tx(trans->dev, skb, tfd, sizeof(*tfd), &dev_cmd->hdr, + IWL_FIRST_TB_SIZE + tb1_len, hdr_len); + + /* set up TFD's third entry to point to remainder of skb's head */ + tb2_len = skb_headlen(skb) - hdr_len; + + if (tb2_len > 0) { + int ret; + + tb_phys = dma_map_single(trans->dev, skb->data + hdr_len, + tb2_len, DMA_TO_DEVICE); + ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, tb_phys, + skb->data + hdr_len, tb2_len, + NULL); + if (ret) + goto out_err; + } + + if (iwl_txq_gen2_tx_add_frags(trans, skb, tfd, out_meta)) + goto out_err; + + skb_walk_frags(skb, frag) { + int ret; + + tb_phys = dma_map_single(trans->dev, frag->data, + skb_headlen(frag), DMA_TO_DEVICE); + ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, tb_phys, + frag->data, + skb_headlen(frag), NULL); + if (ret) + goto out_err; + if (iwl_txq_gen2_tx_add_frags(trans, frag, tfd, out_meta)) + goto out_err; + } + + return tfd; + +out_err: + iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd); + return NULL; +} + +static +struct iwl_tfh_tfd *iwl_txq_gen2_build_tfd(struct iwl_trans *trans, + struct iwl_txq *txq, + struct iwl_device_tx_cmd *dev_cmd, + struct sk_buff *skb, + struct iwl_cmd_meta *out_meta) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + struct iwl_tfh_tfd *tfd = iwl_txq_get_tfd(trans, txq, idx); + int len, hdr_len; + bool amsdu; + + /* There must be data left over for TB1 or this code must be changed */ + BUILD_BUG_ON(sizeof(struct iwl_tx_cmd_gen2) < IWL_FIRST_TB_SIZE); + + memset(tfd, 0, sizeof(*tfd)); + + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + len = sizeof(struct iwl_tx_cmd_gen2); + else + len = sizeof(struct iwl_tx_cmd_gen3); + + amsdu = ieee80211_is_data_qos(hdr->frame_control) && + (*ieee80211_get_qos_ctl(hdr) & + IEEE80211_QOS_CTL_A_MSDU_PRESENT); + + hdr_len = ieee80211_hdrlen(hdr->frame_control); + + /* + * Only build A-MSDUs here if doing so by GSO, otherwise it may be + * an A-MSDU for other reasons, e.g. NAN or an A-MSDU having been + * built in the higher layers already. + */ + if (amsdu && skb_shinfo(skb)->gso_size) + return iwl_txq_gen2_build_tx_amsdu(trans, txq, dev_cmd, skb, + out_meta, hdr_len, len); + return iwl_txq_gen2_build_tx(trans, txq, dev_cmd, skb, out_meta, + hdr_len, len, !amsdu); +} + +int iwl_txq_space(struct iwl_trans *trans, const struct iwl_txq *q) +{ + unsigned int max; + unsigned int used; + + /* + * To avoid ambiguity between empty and completely full queues, there + * should always be less than max_tfd_queue_size elements in the queue. + * If q->n_window is smaller than max_tfd_queue_size, there is no need + * to reserve any queue entries for this purpose. + */ + if (q->n_window < trans->trans_cfg->base_params->max_tfd_queue_size) + max = q->n_window; + else + max = trans->trans_cfg->base_params->max_tfd_queue_size - 1; + + /* + * max_tfd_queue_size is a power of 2, so the following is equivalent to + * modulo by max_tfd_queue_size and is well defined. + */ + used = (q->write_ptr - q->read_ptr) & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1); + + if (WARN_ON(used > max)) + return 0; + + return max - used; +} + +int iwl_txq_gen2_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int txq_id) +{ + struct iwl_cmd_meta *out_meta; + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + u16 cmd_len; + int idx; + void *tfd; + + if (WARN_ONCE(txq_id >= IWL_MAX_TVQM_QUEUES, + "queue %d out of range", txq_id)) + return -EINVAL; + + if (WARN_ONCE(!test_bit(txq_id, trans->txqs.queue_used), + "TX on unused queue %d\n", txq_id)) + return -EINVAL; + + if (skb_is_nonlinear(skb) && + skb_shinfo(skb)->nr_frags > IWL_TRANS_MAX_FRAGS(trans) && + __skb_linearize(skb)) + return -ENOMEM; + + spin_lock(&txq->lock); + + if (iwl_txq_space(trans, txq) < txq->high_mark) { + iwl_txq_stop(trans, txq); + + /* don't put the packet on the ring, if there is no room */ + if (unlikely(iwl_txq_space(trans, txq) < 3)) { + struct iwl_device_tx_cmd **dev_cmd_ptr; + + dev_cmd_ptr = (void *)((u8 *)skb->cb + + trans->txqs.dev_cmd_offs); + + *dev_cmd_ptr = dev_cmd; + __skb_queue_tail(&txq->overflow_q, skb); + spin_unlock(&txq->lock); + return 0; + } + } + + idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + + /* Set up driver data for this TFD */ + txq->entries[idx].skb = skb; + txq->entries[idx].cmd = dev_cmd; + + dev_cmd->hdr.sequence = + cpu_to_le16((u16)(QUEUE_TO_SEQ(txq_id) | + INDEX_TO_SEQ(idx))); + + /* Set up first empty entry in queue's array of Tx/cmd buffers */ + out_meta = &txq->entries[idx].meta; + out_meta->flags = 0; + + tfd = iwl_txq_gen2_build_tfd(trans, txq, dev_cmd, skb, out_meta); + if (!tfd) { + spin_unlock(&txq->lock); + return -1; + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + struct iwl_tx_cmd_gen3 *tx_cmd_gen3 = + (void *)dev_cmd->payload; + + cmd_len = le16_to_cpu(tx_cmd_gen3->len); + } else { + struct iwl_tx_cmd_gen2 *tx_cmd_gen2 = + (void *)dev_cmd->payload; + + cmd_len = le16_to_cpu(tx_cmd_gen2->len); + } + + /* Set up entry for this TFD in Tx byte-count array */ + iwl_pcie_gen2_update_byte_tbl(trans, txq, cmd_len, + iwl_txq_gen2_get_num_tbs(trans, tfd)); + + /* start timer if queue currently empty */ + if (txq->read_ptr == txq->write_ptr && txq->wd_timeout) + mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout); + + /* Tell device the write index *just past* this latest filled TFD */ + txq->write_ptr = iwl_txq_inc_wrap(trans, txq->write_ptr); + iwl_txq_inc_wr_ptr(trans, txq); + /* + * At this point the frame is "transmitted" successfully + * and we will get a TX status notification eventually. + */ + spin_unlock(&txq->lock); + return 0; +} + +/*************** HOST COMMAND QUEUE FUNCTIONS *****/ + +/* + * iwl_txq_gen2_unmap - Unmap any remaining DMA mappings and free skb's + */ +void iwl_txq_gen2_unmap(struct iwl_trans *trans, int txq_id) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + + spin_lock_bh(&txq->lock); + while (txq->write_ptr != txq->read_ptr) { + IWL_DEBUG_TX_REPLY(trans, "Q %d Free %d\n", + txq_id, txq->read_ptr); + + if (txq_id != trans->txqs.cmd.q_id) { + int idx = iwl_txq_get_cmd_index(txq, txq->read_ptr); + struct sk_buff *skb = txq->entries[idx].skb; + + if (!WARN_ON_ONCE(!skb)) + iwl_txq_free_tso_page(trans, skb); + } + iwl_txq_gen2_free_tfd(trans, txq); + txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr); + } + + while (!skb_queue_empty(&txq->overflow_q)) { + struct sk_buff *skb = __skb_dequeue(&txq->overflow_q); + + iwl_op_mode_free_skb(trans->op_mode, skb); + } + + spin_unlock_bh(&txq->lock); + + /* just in case - this queue may have been stopped */ + iwl_wake_queue(trans, txq); +} + +static void iwl_txq_gen2_free_memory(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + struct device *dev = trans->dev; + + /* De-alloc circular buffer of TFDs */ + if (txq->tfds) { + dma_free_coherent(dev, + trans->txqs.tfd.size * txq->n_window, + txq->tfds, txq->dma_addr); + dma_free_coherent(dev, + sizeof(*txq->first_tb_bufs) * txq->n_window, + txq->first_tb_bufs, txq->first_tb_dma); + } + + kfree(txq->entries); + if (txq->bc_tbl.addr) + dma_pool_free(trans->txqs.bc_pool, + txq->bc_tbl.addr, txq->bc_tbl.dma); + kfree(txq); +} + +/* + * iwl_pcie_txq_free - Deallocate DMA queue. + * @txq: Transmit queue to deallocate. + * + * Empty queue by removing and destroying all BD's. + * Free all buffers. + * 0-fill, but do not free "txq" descriptor structure. + */ +static void iwl_txq_gen2_free(struct iwl_trans *trans, int txq_id) +{ + struct iwl_txq *txq; + int i; + + if (WARN_ONCE(txq_id >= IWL_MAX_TVQM_QUEUES, + "queue %d out of range", txq_id)) + return; + + txq = trans->txqs.txq[txq_id]; + + if (WARN_ON(!txq)) + return; + + iwl_txq_gen2_unmap(trans, txq_id); + + /* De-alloc array of command/tx buffers */ + if (txq_id == trans->txqs.cmd.q_id) + for (i = 0; i < txq->n_window; i++) { + kfree_sensitive(txq->entries[i].cmd); + kfree_sensitive(txq->entries[i].free_buf); + } + del_timer_sync(&txq->stuck_timer); + + iwl_txq_gen2_free_memory(trans, txq); + + trans->txqs.txq[txq_id] = NULL; + + clear_bit(txq_id, trans->txqs.queue_used); +} + +/* + * iwl_queue_init - Initialize queue's high/low-water and read/write indexes + */ +static int iwl_queue_init(struct iwl_txq *q, int slots_num) +{ + q->n_window = slots_num; + + /* slots_num must be power-of-two size, otherwise + * iwl_txq_get_cmd_index is broken. */ + if (WARN_ON(!is_power_of_2(slots_num))) + return -EINVAL; + + q->low_mark = q->n_window / 4; + if (q->low_mark < 4) + q->low_mark = 4; + + q->high_mark = q->n_window / 8; + if (q->high_mark < 2) + q->high_mark = 2; + + q->write_ptr = 0; + q->read_ptr = 0; + + return 0; +} + +int iwl_txq_init(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num, + bool cmd_queue) +{ + int ret; + u32 tfd_queue_max_size = + trans->trans_cfg->base_params->max_tfd_queue_size; + + txq->need_update = false; + + /* max_tfd_queue_size must be power-of-two size, otherwise + * iwl_txq_inc_wrap and iwl_txq_dec_wrap are broken. */ + if (WARN_ONCE(tfd_queue_max_size & (tfd_queue_max_size - 1), + "Max tfd queue size must be a power of two, but is %d", + tfd_queue_max_size)) + return -EINVAL; + + /* Initialize queue's high/low-water marks, and head/tail indexes */ + ret = iwl_queue_init(txq, slots_num); + if (ret) + return ret; + + spin_lock_init(&txq->lock); + +#ifdef CONFIG_LOCKDEP + if (cmd_queue) { + static struct lock_class_key iwl_txq_cmd_queue_lock_class; + + lockdep_set_class(&txq->lock, &iwl_txq_cmd_queue_lock_class); + } +#endif + + __skb_queue_head_init(&txq->overflow_q); + + return 0; +} + +void iwl_txq_free_tso_page(struct iwl_trans *trans, struct sk_buff *skb) +{ + struct page **page_ptr; + struct page *next; + + page_ptr = (void *)((u8 *)skb->cb + trans->txqs.page_offs); + next = *page_ptr; + *page_ptr = NULL; + + while (next) { + struct page *tmp = next; + + next = *(void **)((u8 *)page_address(next) + PAGE_SIZE - + sizeof(void *)); + __free_page(tmp); + } +} + +void iwl_txq_log_scd_error(struct iwl_trans *trans, struct iwl_txq *txq) +{ + u32 txq_id = txq->id; + u32 status; + bool active; + u8 fifo; + + if (trans->trans_cfg->use_tfh) { + IWL_ERR(trans, "Queue %d is stuck %d %d\n", txq_id, + txq->read_ptr, txq->write_ptr); + /* TODO: access new SCD registers and dump them */ + return; + } + + status = iwl_read_prph(trans, SCD_QUEUE_STATUS_BITS(txq_id)); + fifo = (status >> SCD_QUEUE_STTS_REG_POS_TXF) & 0x7; + active = !!(status & BIT(SCD_QUEUE_STTS_REG_POS_ACTIVE)); + + IWL_ERR(trans, + "Queue %d is %sactive on fifo %d and stuck for %u ms. SW [%d, %d] HW [%d, %d] FH TRB=0x0%x\n", + txq_id, active ? "" : "in", fifo, + jiffies_to_msecs(txq->wd_timeout), + txq->read_ptr, txq->write_ptr, + iwl_read_prph(trans, SCD_QUEUE_RDPTR(txq_id)) & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1), + iwl_read_prph(trans, SCD_QUEUE_WRPTR(txq_id)) & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1), + iwl_read_direct32(trans, FH_TX_TRB_REG(fifo))); +} + +static void iwl_txq_stuck_timer(struct timer_list *t) +{ + struct iwl_txq *txq = from_timer(txq, t, stuck_timer); + struct iwl_trans *trans = txq->trans; + + spin_lock(&txq->lock); + /* check if triggered erroneously */ + if (txq->read_ptr == txq->write_ptr) { + spin_unlock(&txq->lock); + return; + } + spin_unlock(&txq->lock); + + iwl_txq_log_scd_error(trans, txq); + + iwl_force_nmi(trans); +} + +int iwl_txq_alloc(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num, + bool cmd_queue) +{ + size_t tfd_sz = trans->txqs.tfd.size * + trans->trans_cfg->base_params->max_tfd_queue_size; + size_t tb0_buf_sz; + int i; + + if (WARN_ON(txq->entries || txq->tfds)) + return -EINVAL; + + if (trans->trans_cfg->use_tfh) + tfd_sz = trans->txqs.tfd.size * slots_num; + + timer_setup(&txq->stuck_timer, iwl_txq_stuck_timer, 0); + txq->trans = trans; + + txq->n_window = slots_num; + + txq->entries = kcalloc(slots_num, + sizeof(struct iwl_pcie_txq_entry), + GFP_KERNEL); + + if (!txq->entries) + goto error; + + if (cmd_queue) + for (i = 0; i < slots_num; i++) { + txq->entries[i].cmd = + kmalloc(sizeof(struct iwl_device_cmd), + GFP_KERNEL); + if (!txq->entries[i].cmd) + goto error; + } + + /* Circular buffer of transmit frame descriptors (TFDs), + * shared with device */ + txq->tfds = dma_alloc_coherent(trans->dev, tfd_sz, + &txq->dma_addr, GFP_KERNEL); + if (!txq->tfds) + goto error; + + BUILD_BUG_ON(sizeof(*txq->first_tb_bufs) != IWL_FIRST_TB_SIZE_ALIGN); + + tb0_buf_sz = sizeof(*txq->first_tb_bufs) * slots_num; + + txq->first_tb_bufs = dma_alloc_coherent(trans->dev, tb0_buf_sz, + &txq->first_tb_dma, + GFP_KERNEL); + if (!txq->first_tb_bufs) + goto err_free_tfds; + + return 0; +err_free_tfds: + dma_free_coherent(trans->dev, tfd_sz, txq->tfds, txq->dma_addr); +error: + if (txq->entries && cmd_queue) + for (i = 0; i < slots_num; i++) + kfree(txq->entries[i].cmd); + kfree(txq->entries); + txq->entries = NULL; + + return -ENOMEM; +} + +static int iwl_txq_dyn_alloc_dma(struct iwl_trans *trans, + struct iwl_txq **intxq, int size, + unsigned int timeout) +{ + size_t bc_tbl_size, bc_tbl_entries; + struct iwl_txq *txq; + int ret; + + WARN_ON(!trans->txqs.bc_tbl_size); + + bc_tbl_size = trans->txqs.bc_tbl_size; + bc_tbl_entries = bc_tbl_size / sizeof(u16); + + if (WARN_ON(size > bc_tbl_entries)) + return -EINVAL; + + txq = kzalloc(sizeof(*txq), GFP_KERNEL); + if (!txq) + return -ENOMEM; + + txq->bc_tbl.addr = dma_pool_alloc(trans->txqs.bc_pool, GFP_KERNEL, + &txq->bc_tbl.dma); + if (!txq->bc_tbl.addr) { + IWL_ERR(trans, "Scheduler BC Table allocation failed\n"); + kfree(txq); + return -ENOMEM; + } + + ret = iwl_txq_alloc(trans, txq, size, false); + if (ret) { + IWL_ERR(trans, "Tx queue alloc failed\n"); + goto error; + } + ret = iwl_txq_init(trans, txq, size, false); + if (ret) { + IWL_ERR(trans, "Tx queue init failed\n"); + goto error; + } + + txq->wd_timeout = msecs_to_jiffies(timeout); + + *intxq = txq; + return 0; + +error: + iwl_txq_gen2_free_memory(trans, txq); + return ret; +} + +static int iwl_txq_alloc_response(struct iwl_trans *trans, struct iwl_txq *txq, + struct iwl_host_cmd *hcmd) +{ + struct iwl_tx_queue_cfg_rsp *rsp; + int ret, qid; + u32 wr_ptr; + + if (WARN_ON(iwl_rx_packet_payload_len(hcmd->resp_pkt) != + sizeof(*rsp))) { + ret = -EINVAL; + goto error_free_resp; + } + + rsp = (void *)hcmd->resp_pkt->data; + qid = le16_to_cpu(rsp->queue_number); + wr_ptr = le16_to_cpu(rsp->write_pointer); + + if (qid >= ARRAY_SIZE(trans->txqs.txq)) { + WARN_ONCE(1, "queue index %d unsupported", qid); + ret = -EIO; + goto error_free_resp; + } + + if (test_and_set_bit(qid, trans->txqs.queue_used)) { + WARN_ONCE(1, "queue %d already used", qid); + ret = -EIO; + goto error_free_resp; + } + + if (WARN_ONCE(trans->txqs.txq[qid], + "queue %d already allocated\n", qid)) { + ret = -EIO; + goto error_free_resp; + } + + txq->id = qid; + trans->txqs.txq[qid] = txq; + wr_ptr &= (trans->trans_cfg->base_params->max_tfd_queue_size - 1); + + /* Place first TFD at index corresponding to start sequence number */ + txq->read_ptr = wr_ptr; + txq->write_ptr = wr_ptr; + + IWL_DEBUG_TX_QUEUES(trans, "Activate queue %d\n", qid); + + iwl_free_resp(hcmd); + return qid; + +error_free_resp: + iwl_free_resp(hcmd); + iwl_txq_gen2_free_memory(trans, txq); + return ret; +} + +int iwl_txq_dyn_alloc(struct iwl_trans *trans, __le16 flags, u8 sta_id, u8 tid, + int cmd_id, int size, unsigned int timeout) +{ + struct iwl_txq *txq = NULL; + struct iwl_tx_queue_cfg_cmd cmd = { + .flags = flags, + .sta_id = sta_id, + .tid = tid, + }; + struct iwl_host_cmd hcmd = { + .id = cmd_id, + .len = { sizeof(cmd) }, + .data = { &cmd, }, + .flags = CMD_WANT_SKB, + }; + int ret; + + ret = iwl_txq_dyn_alloc_dma(trans, &txq, size, timeout); + if (ret) + return ret; + + cmd.tfdq_addr = cpu_to_le64(txq->dma_addr); + cmd.byte_cnt_addr = cpu_to_le64(txq->bc_tbl.dma); + cmd.cb_size = cpu_to_le32(TFD_QUEUE_CB_SIZE(size)); + + ret = iwl_trans_send_cmd(trans, &hcmd); + if (ret) + goto error; + + return iwl_txq_alloc_response(trans, txq, &hcmd); + +error: + iwl_txq_gen2_free_memory(trans, txq); + return ret; +} + +void iwl_txq_dyn_free(struct iwl_trans *trans, int queue) +{ + if (WARN(queue >= IWL_MAX_TVQM_QUEUES, + "queue %d out of range", queue)) + return; + + /* + * Upon HW Rfkill - we stop the device, and then stop the queues + * in the op_mode. Just for the sake of the simplicity of the op_mode, + * allow the op_mode to call txq_disable after it already called + * stop_device. + */ + if (!test_and_clear_bit(queue, trans->txqs.queue_used)) { + WARN_ONCE(test_bit(STATUS_DEVICE_ENABLED, &trans->status), + "queue %d not used", queue); + return; + } + + iwl_txq_gen2_free(trans, queue); + + IWL_DEBUG_TX_QUEUES(trans, "Deactivate queue %d\n", queue); +} + +void iwl_txq_gen2_tx_free(struct iwl_trans *trans) +{ + int i; + + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + /* Free all TX queues */ + for (i = 0; i < ARRAY_SIZE(trans->txqs.txq); i++) { + if (!trans->txqs.txq[i]) + continue; + + iwl_txq_gen2_free(trans, i); + } +} + +int iwl_txq_gen2_init(struct iwl_trans *trans, int txq_id, int queue_size) +{ + struct iwl_txq *queue; + int ret; + + /* alloc and init the tx queue */ + if (!trans->txqs.txq[txq_id]) { + queue = kzalloc(sizeof(*queue), GFP_KERNEL); + if (!queue) { + IWL_ERR(trans, "Not enough memory for tx queue\n"); + return -ENOMEM; + } + trans->txqs.txq[txq_id] = queue; + ret = iwl_txq_alloc(trans, queue, queue_size, true); + if (ret) { + IWL_ERR(trans, "Tx %d queue init failed\n", txq_id); + goto error; + } + } else { + queue = trans->txqs.txq[txq_id]; + } + + ret = iwl_txq_init(trans, queue, queue_size, + (txq_id == trans->txqs.cmd.q_id)); + if (ret) { + IWL_ERR(trans, "Tx %d queue alloc failed\n", txq_id); + goto error; + } + trans->txqs.txq[txq_id]->id = txq_id; + set_bit(txq_id, trans->txqs.queue_used); + + return 0; + +error: + iwl_txq_gen2_tx_free(trans); + return ret; +} + +static inline dma_addr_t iwl_txq_gen1_tfd_tb_get_addr(struct iwl_trans *trans, + void *_tfd, u8 idx) +{ + struct iwl_tfd *tfd; + struct iwl_tfd_tb *tb; + dma_addr_t addr; + dma_addr_t hi_len; + + if (trans->trans_cfg->use_tfh) { + struct iwl_tfh_tfd *tfd = _tfd; + struct iwl_tfh_tb *tb = &tfd->tbs[idx]; + + return (dma_addr_t)(le64_to_cpu(tb->addr)); + } + + tfd = _tfd; + tb = &tfd->tbs[idx]; + addr = get_unaligned_le32(&tb->lo); + + if (sizeof(dma_addr_t) <= sizeof(u32)) + return addr; + + hi_len = le16_to_cpu(tb->hi_n_len) & 0xF; + + /* + * shift by 16 twice to avoid warnings on 32-bit + * (where this code never runs anyway due to the + * if statement above) + */ + return addr | ((hi_len << 16) << 16); +} + +void iwl_txq_gen1_tfd_unmap(struct iwl_trans *trans, + struct iwl_cmd_meta *meta, + struct iwl_txq *txq, int index) +{ + int i, num_tbs; + void *tfd = iwl_txq_get_tfd(trans, txq, index); + + /* Sanity check on number of chunks */ + num_tbs = iwl_txq_gen1_tfd_get_num_tbs(trans, tfd); + + if (num_tbs > trans->txqs.tfd.max_tbs) { + IWL_ERR(trans, "Too many chunks: %i\n", num_tbs); + /* @todo issue fatal error, it is quite serious situation */ + return; + } + + /* first TB is never freed - it's the bidirectional DMA data */ + + for (i = 1; i < num_tbs; i++) { + if (meta->tbs & BIT(i)) + dma_unmap_page(trans->dev, + iwl_txq_gen1_tfd_tb_get_addr(trans, + tfd, i), + iwl_txq_gen1_tfd_tb_get_len(trans, + tfd, i), + DMA_TO_DEVICE); + else + dma_unmap_single(trans->dev, + iwl_txq_gen1_tfd_tb_get_addr(trans, + tfd, i), + iwl_txq_gen1_tfd_tb_get_len(trans, + tfd, i), + DMA_TO_DEVICE); + } + + meta->tbs = 0; + + if (trans->trans_cfg->use_tfh) { + struct iwl_tfh_tfd *tfd_fh = (void *)tfd; + + tfd_fh->num_tbs = 0; + } else { + struct iwl_tfd *tfd_fh = (void *)tfd; + + tfd_fh->num_tbs = 0; + } +} + +#define IWL_TX_CRC_SIZE 4 +#define IWL_TX_DELIMITER_SIZE 4 + +/* + * iwl_txq_gen1_update_byte_cnt_tbl - Set up entry in Tx byte-count array + */ +void iwl_txq_gen1_update_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq, u16 byte_cnt, + int num_tbs) +{ + struct iwlagn_scd_bc_tbl *scd_bc_tbl; + int write_ptr = txq->write_ptr; + int txq_id = txq->id; + u8 sec_ctl = 0; + u16 len = byte_cnt + IWL_TX_CRC_SIZE + IWL_TX_DELIMITER_SIZE; + __le16 bc_ent; + struct iwl_device_tx_cmd *dev_cmd = txq->entries[txq->write_ptr].cmd; + struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload; + u8 sta_id = tx_cmd->sta_id; + + scd_bc_tbl = trans->txqs.scd_bc_tbls.addr; + + sec_ctl = tx_cmd->sec_ctl; + + switch (sec_ctl & TX_CMD_SEC_MSK) { + case TX_CMD_SEC_CCM: + len += IEEE80211_CCMP_MIC_LEN; + break; + case TX_CMD_SEC_TKIP: + len += IEEE80211_TKIP_ICV_LEN; + break; + case TX_CMD_SEC_WEP: + len += IEEE80211_WEP_IV_LEN + IEEE80211_WEP_ICV_LEN; + break; + } + if (trans->txqs.bc_table_dword) + len = DIV_ROUND_UP(len, 4); + + if (WARN_ON(len > 0xFFF || write_ptr >= TFD_QUEUE_SIZE_MAX)) + return; + + bc_ent = cpu_to_le16(len | (sta_id << 12)); + + scd_bc_tbl[txq_id].tfd_offset[write_ptr] = bc_ent; + + if (write_ptr < TFD_QUEUE_SIZE_BC_DUP) + scd_bc_tbl[txq_id].tfd_offset[TFD_QUEUE_SIZE_MAX + write_ptr] = + bc_ent; +} + +void iwl_txq_gen1_inval_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + struct iwlagn_scd_bc_tbl *scd_bc_tbl = trans->txqs.scd_bc_tbls.addr; + int txq_id = txq->id; + int read_ptr = txq->read_ptr; + u8 sta_id = 0; + __le16 bc_ent; + struct iwl_device_tx_cmd *dev_cmd = txq->entries[read_ptr].cmd; + struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload; + + WARN_ON(read_ptr >= TFD_QUEUE_SIZE_MAX); + + if (txq_id != trans->txqs.cmd.q_id) + sta_id = tx_cmd->sta_id; + + bc_ent = cpu_to_le16(1 | (sta_id << 12)); + + scd_bc_tbl[txq_id].tfd_offset[read_ptr] = bc_ent; + + if (read_ptr < TFD_QUEUE_SIZE_BC_DUP) + scd_bc_tbl[txq_id].tfd_offset[TFD_QUEUE_SIZE_MAX + read_ptr] = + bc_ent; +} + +/* + * iwl_txq_free_tfd - Free all chunks referenced by TFD [txq->q.read_ptr] + * @trans - transport private data + * @txq - tx queue + * @dma_dir - the direction of the DMA mapping + * + * Does NOT advance any TFD circular buffer read/write indexes + * Does NOT free the TFD itself (which is within circular buffer) + */ +void iwl_txq_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq) +{ + /* rd_ptr is bounded by TFD_QUEUE_SIZE_MAX and + * idx is bounded by n_window + */ + int rd_ptr = txq->read_ptr; + int idx = iwl_txq_get_cmd_index(txq, rd_ptr); + struct sk_buff *skb; + + lockdep_assert_held(&txq->lock); + + if (!txq->entries) + return; + + /* We have only q->n_window txq->entries, but we use + * TFD_QUEUE_SIZE_MAX tfds + */ + iwl_txq_gen1_tfd_unmap(trans, &txq->entries[idx].meta, txq, rd_ptr); + + /* free SKB */ + skb = txq->entries[idx].skb; + + /* Can be called from irqs-disabled context + * If skb is not NULL, it means that the whole queue is being + * freed and that the queue is not empty - free the skb + */ + if (skb) { + iwl_op_mode_free_skb(trans->op_mode, skb); + txq->entries[idx].skb = NULL; + } +} + +void iwl_txq_progress(struct iwl_txq *txq) +{ + lockdep_assert_held(&txq->lock); + + if (!txq->wd_timeout) + return; + + /* + * station is asleep and we send data - that must + * be uAPSD or PS-Poll. Don't rearm the timer. + */ + if (txq->frozen) + return; + + /* + * if empty delete timer, otherwise move timer forward + * since we're making progress on this queue + */ + if (txq->read_ptr == txq->write_ptr) + del_timer(&txq->stuck_timer); + else + mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout); +} + +/* Frees buffers until index _not_ inclusive */ +void iwl_txq_reclaim(struct iwl_trans *trans, int txq_id, int ssn, + struct sk_buff_head *skbs) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + int tfd_num = iwl_txq_get_cmd_index(txq, ssn); + int read_ptr = iwl_txq_get_cmd_index(txq, txq->read_ptr); + int last_to_free; + + /* This function is not meant to release cmd queue*/ + if (WARN_ON(txq_id == trans->txqs.cmd.q_id)) + return; + + spin_lock_bh(&txq->lock); + + if (!test_bit(txq_id, trans->txqs.queue_used)) { + IWL_DEBUG_TX_QUEUES(trans, "Q %d inactive - ignoring idx %d\n", + txq_id, ssn); + goto out; + } + + if (read_ptr == tfd_num) + goto out; + + IWL_DEBUG_TX_REPLY(trans, "[Q %d] %d -> %d (%d)\n", + txq_id, txq->read_ptr, tfd_num, ssn); + + /*Since we free until index _not_ inclusive, the one before index is + * the last we will free. This one must be used */ + last_to_free = iwl_txq_dec_wrap(trans, tfd_num); + + if (!iwl_txq_used(txq, last_to_free)) { + IWL_ERR(trans, + "%s: Read index for txq id (%d), last_to_free %d is out of range [0-%d] %d %d.\n", + __func__, txq_id, last_to_free, + trans->trans_cfg->base_params->max_tfd_queue_size, + txq->write_ptr, txq->read_ptr); + + iwl_op_mode_time_point(trans->op_mode, + IWL_FW_INI_TIME_POINT_FAKE_TX, + NULL); + goto out; + } + + if (WARN_ON(!skb_queue_empty(skbs))) + goto out; + + for (; + read_ptr != tfd_num; + txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr), + read_ptr = iwl_txq_get_cmd_index(txq, txq->read_ptr)) { + struct sk_buff *skb = txq->entries[read_ptr].skb; + + if (WARN_ON_ONCE(!skb)) + continue; + + iwl_txq_free_tso_page(trans, skb); + + __skb_queue_tail(skbs, skb); + + txq->entries[read_ptr].skb = NULL; + + if (!trans->trans_cfg->use_tfh) + iwl_txq_gen1_inval_byte_cnt_tbl(trans, txq); + + iwl_txq_free_tfd(trans, txq); + } + + iwl_txq_progress(txq); + + if (iwl_txq_space(trans, txq) > txq->low_mark && + test_bit(txq_id, trans->txqs.queue_stopped)) { + struct sk_buff_head overflow_skbs; + + __skb_queue_head_init(&overflow_skbs); + skb_queue_splice_init(&txq->overflow_q, &overflow_skbs); + + /* + * We are going to transmit from the overflow queue. + * Remember this state so that wait_for_txq_empty will know we + * are adding more packets to the TFD queue. It cannot rely on + * the state of &txq->overflow_q, as we just emptied it, but + * haven't TXed the content yet. + */ + txq->overflow_tx = true; + + /* + * This is tricky: we are in reclaim path which is non + * re-entrant, so noone will try to take the access the + * txq data from that path. We stopped tx, so we can't + * have tx as well. Bottom line, we can unlock and re-lock + * later. + */ + spin_unlock_bh(&txq->lock); + + while (!skb_queue_empty(&overflow_skbs)) { + struct sk_buff *skb = __skb_dequeue(&overflow_skbs); + struct iwl_device_tx_cmd *dev_cmd_ptr; + + dev_cmd_ptr = *(void **)((u8 *)skb->cb + + trans->txqs.dev_cmd_offs); + + /* + * Note that we can very well be overflowing again. + * In that case, iwl_txq_space will be small again + * and we won't wake mac80211's queue. + */ + iwl_trans_tx(trans, skb, dev_cmd_ptr, txq_id); + } + + if (iwl_txq_space(trans, txq) > txq->low_mark) + iwl_wake_queue(trans, txq); + + spin_lock_bh(&txq->lock); + txq->overflow_tx = false; + } + +out: + spin_unlock_bh(&txq->lock); +} + +/* Set wr_ptr of specific device and txq */ +void iwl_txq_set_q_ptrs(struct iwl_trans *trans, int txq_id, int ptr) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + + spin_lock_bh(&txq->lock); + + txq->write_ptr = ptr; + txq->read_ptr = txq->write_ptr; + + spin_unlock_bh(&txq->lock); +} + +void iwl_trans_txq_freeze_timer(struct iwl_trans *trans, unsigned long txqs, + bool freeze) +{ + int queue; + + for_each_set_bit(queue, &txqs, BITS_PER_LONG) { + struct iwl_txq *txq = trans->txqs.txq[queue]; + unsigned long now; + + spin_lock_bh(&txq->lock); + + now = jiffies; + + if (txq->frozen == freeze) + goto next_queue; + + IWL_DEBUG_TX_QUEUES(trans, "%s TXQ %d\n", + freeze ? "Freezing" : "Waking", queue); + + txq->frozen = freeze; + + if (txq->read_ptr == txq->write_ptr) + goto next_queue; + + if (freeze) { + if (unlikely(time_after(now, + txq->stuck_timer.expires))) { + /* + * The timer should have fired, maybe it is + * spinning right now on the lock. + */ + goto next_queue; + } + /* remember how long until the timer fires */ + txq->frozen_expiry_remainder = + txq->stuck_timer.expires - now; + del_timer(&txq->stuck_timer); + goto next_queue; + } + + /* + * Wake a non-empty queue -> arm timer with the + * remainder before it froze + */ + mod_timer(&txq->stuck_timer, + now + txq->frozen_expiry_remainder); + +next_queue: + spin_unlock_bh(&txq->lock); + } +} + +#define HOST_COMPLETE_TIMEOUT (2 * HZ) + +static int iwl_trans_txq_send_hcmd_sync(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + const char *cmd_str = iwl_get_cmd_string(trans, cmd->id); + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + int cmd_idx; + int ret; + + IWL_DEBUG_INFO(trans, "Attempting to send sync command %s\n", cmd_str); + + if (WARN(test_and_set_bit(STATUS_SYNC_HCMD_ACTIVE, + &trans->status), + "Command %s: a command is already active!\n", cmd_str)) + return -EIO; + + IWL_DEBUG_INFO(trans, "Setting HCMD_ACTIVE for command %s\n", cmd_str); + + cmd_idx = trans->ops->send_cmd(trans, cmd); + if (cmd_idx < 0) { + ret = cmd_idx; + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + IWL_ERR(trans, "Error sending %s: enqueue_hcmd failed: %d\n", + cmd_str, ret); + return ret; + } + + ret = wait_event_timeout(trans->wait_command_queue, + !test_bit(STATUS_SYNC_HCMD_ACTIVE, + &trans->status), + HOST_COMPLETE_TIMEOUT); + if (!ret) { + IWL_ERR(trans, "Error sending %s: time out after %dms.\n", + cmd_str, jiffies_to_msecs(HOST_COMPLETE_TIMEOUT)); + + IWL_ERR(trans, "Current CMD queue read_ptr %d write_ptr %d\n", + txq->read_ptr, txq->write_ptr); + + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + IWL_DEBUG_INFO(trans, "Clearing HCMD_ACTIVE for command %s\n", + cmd_str); + ret = -ETIMEDOUT; + + iwl_trans_sync_nmi(trans); + goto cancel; + } + + if (test_bit(STATUS_FW_ERROR, &trans->status)) { + IWL_ERR(trans, "FW error in SYNC CMD %s\n", cmd_str); + dump_stack(); + ret = -EIO; + goto cancel; + } + + if (!(cmd->flags & CMD_SEND_IN_RFKILL) && + test_bit(STATUS_RFKILL_OPMODE, &trans->status)) { + IWL_DEBUG_RF_KILL(trans, "RFKILL in SYNC CMD... no rsp\n"); + ret = -ERFKILL; + goto cancel; + } + + if ((cmd->flags & CMD_WANT_SKB) && !cmd->resp_pkt) { + IWL_ERR(trans, "Error: Response NULL in '%s'\n", cmd_str); + ret = -EIO; + goto cancel; + } + + return 0; + +cancel: + if (cmd->flags & CMD_WANT_SKB) { + /* + * Cancel the CMD_WANT_SKB flag for the cmd in the + * TX cmd queue. Otherwise in case the cmd comes + * in later, it will possibly set an invalid + * address (cmd->meta.source). + */ + txq->entries[cmd_idx].meta.flags &= ~CMD_WANT_SKB; + } + + if (cmd->resp_pkt) { + iwl_free_resp(cmd); + cmd->resp_pkt = NULL; + } + + return ret; +} + +int iwl_trans_txq_send_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + /* Make sure the NIC is still alive in the bus */ + if (test_bit(STATUS_TRANS_DEAD, &trans->status)) + return -ENODEV; + + if (!(cmd->flags & CMD_SEND_IN_RFKILL) && + test_bit(STATUS_RFKILL_OPMODE, &trans->status)) { + IWL_DEBUG_RF_KILL(trans, "Dropping CMD 0x%x: RF KILL\n", + cmd->id); + return -ERFKILL; + } + + if (unlikely(trans->system_pm_mode == IWL_PLAT_PM_MODE_D3 && + !(cmd->flags & CMD_SEND_IN_D3))) { + IWL_DEBUG_WOWLAN(trans, "Dropping CMD 0x%x: D3\n", cmd->id); + return -EHOSTDOWN; + } + + if (cmd->flags & CMD_ASYNC) { + int ret; + + /* An asynchronous command can not expect an SKB to be set. */ + if (WARN_ON(cmd->flags & CMD_WANT_SKB)) + return -EINVAL; + + ret = trans->ops->send_cmd(trans, cmd); + if (ret < 0) { + IWL_ERR(trans, + "Error sending %s: enqueue_hcmd failed: %d\n", + iwl_get_cmd_string(trans, cmd->id), ret); + return ret; + } + return 0; + } + + return iwl_trans_txq_send_hcmd_sync(trans, cmd); +} + diff --git a/sys/contrib/dev/iwlwifi/queue/tx.h b/sys/contrib/dev/iwlwifi/queue/tx.h new file mode 100644 index 00000000000..19178c88bb2 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/queue/tx.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2020-2021 Intel Corporation + */ +#ifndef __iwl_trans_queue_tx_h__ +#define __iwl_trans_queue_tx_h__ +#include "iwl-fh.h" +#include "fw/api/tx.h" + +struct iwl_tso_hdr_page { + struct page *page; + u8 *pos; +}; + +static inline dma_addr_t +iwl_txq_get_first_tb_dma(struct iwl_txq *txq, int idx) +{ + return txq->first_tb_dma + + sizeof(struct iwl_pcie_first_tb_buf) * idx; +} + +static inline u16 iwl_txq_get_cmd_index(const struct iwl_txq *q, u32 index) +{ + return index & (q->n_window - 1); +} + +void iwl_txq_gen2_unmap(struct iwl_trans *trans, int txq_id); + +static inline void iwl_wake_queue(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + if (test_and_clear_bit(txq->id, trans->txqs.queue_stopped)) { + IWL_DEBUG_TX_QUEUES(trans, "Wake hwq %d\n", txq->id); + iwl_op_mode_queue_not_full(trans->op_mode, txq->id); + } +} + +static inline void *iwl_txq_get_tfd(struct iwl_trans *trans, + struct iwl_txq *txq, int idx) +{ + if (trans->trans_cfg->use_tfh) + idx = iwl_txq_get_cmd_index(txq, idx); + + return (u8 *)txq->tfds + trans->txqs.tfd.size * idx; +} + +int iwl_txq_alloc(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num, + bool cmd_queue); +/* + * We need this inline in case dma_addr_t is only 32-bits - since the + * hardware is always 64-bit, the issue can still occur in that case, + * so use u64 for 'phys' here to force the addition in 64-bit. + */ +static inline bool iwl_txq_crosses_4g_boundary(u64 phys, u16 len) +{ + return upper_32_bits(phys) != upper_32_bits(phys + len); +} + +int iwl_txq_space(struct iwl_trans *trans, const struct iwl_txq *q); + +static inline void iwl_txq_stop(struct iwl_trans *trans, struct iwl_txq *txq) +{ + if (!test_and_set_bit(txq->id, trans->txqs.queue_stopped)) { + iwl_op_mode_queue_full(trans->op_mode, txq->id); + IWL_DEBUG_TX_QUEUES(trans, "Stop hwq %d\n", txq->id); + } else { + IWL_DEBUG_TX_QUEUES(trans, "hwq %d already stopped\n", + txq->id); + } +} + +/** + * iwl_txq_inc_wrap - increment queue index, wrap back to beginning + * @index -- current index + */ +static inline int iwl_txq_inc_wrap(struct iwl_trans *trans, int index) +{ + return ++index & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1); +} + +/** + * iwl_txq_dec_wrap - decrement queue index, wrap back to end + * @index -- current index + */ +static inline int iwl_txq_dec_wrap(struct iwl_trans *trans, int index) +{ + return --index & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1); +} + +static inline bool iwl_txq_used(const struct iwl_txq *q, int i) +{ + int index = iwl_txq_get_cmd_index(q, i); + int r = iwl_txq_get_cmd_index(q, q->read_ptr); + int w = iwl_txq_get_cmd_index(q, q->write_ptr); + + return w >= r ? + (index >= r && index < w) : + !(index < r && index >= w); +} + +void iwl_txq_free_tso_page(struct iwl_trans *trans, struct sk_buff *skb); + +void iwl_txq_log_scd_error(struct iwl_trans *trans, struct iwl_txq *txq); + +int iwl_txq_gen2_set_tb(struct iwl_trans *trans, + struct iwl_tfh_tfd *tfd, dma_addr_t addr, + u16 len); + +void iwl_txq_gen2_tfd_unmap(struct iwl_trans *trans, + struct iwl_cmd_meta *meta, + struct iwl_tfh_tfd *tfd); + +int iwl_txq_dyn_alloc(struct iwl_trans *trans, + __le16 flags, u8 sta_id, u8 tid, + int cmd_id, int size, + unsigned int timeout); + +int iwl_txq_gen2_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int txq_id); + +void iwl_txq_dyn_free(struct iwl_trans *trans, int queue); +void iwl_txq_gen2_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq); +void iwl_txq_inc_wr_ptr(struct iwl_trans *trans, struct iwl_txq *txq); +void iwl_txq_gen2_tx_free(struct iwl_trans *trans); +int iwl_txq_init(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num, + bool cmd_queue); +int iwl_txq_gen2_init(struct iwl_trans *trans, int txq_id, int queue_size); +#ifdef CONFIG_INET +struct iwl_tso_hdr_page *get_page_hdr(struct iwl_trans *trans, size_t len, + struct sk_buff *skb); +#endif +static inline u8 iwl_txq_gen1_tfd_get_num_tbs(struct iwl_trans *trans, + void *_tfd) +{ + struct iwl_tfd *tfd; + + if (trans->trans_cfg->use_tfh) { + struct iwl_tfh_tfd *tfd = _tfd; + + return le16_to_cpu(tfd->num_tbs) & 0x1f; + } + + tfd = (struct iwl_tfd *)_tfd; + return tfd->num_tbs & 0x1f; +} + +static inline u16 iwl_txq_gen1_tfd_tb_get_len(struct iwl_trans *trans, + void *_tfd, u8 idx) +{ + struct iwl_tfd *tfd; + struct iwl_tfd_tb *tb; + + if (trans->trans_cfg->use_tfh) { + struct iwl_tfh_tfd *tfd = _tfd; + struct iwl_tfh_tb *tb = &tfd->tbs[idx]; + + return le16_to_cpu(tb->tb_len); + } + + tfd = (struct iwl_tfd *)_tfd; + tb = &tfd->tbs[idx]; + + return le16_to_cpu(tb->hi_n_len) >> 4; +} + +void iwl_txq_gen1_tfd_unmap(struct iwl_trans *trans, + struct iwl_cmd_meta *meta, + struct iwl_txq *txq, int index); +void iwl_txq_gen1_inval_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq); +void iwl_txq_gen1_update_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq, u16 byte_cnt, + int num_tbs); +void iwl_txq_reclaim(struct iwl_trans *trans, int txq_id, int ssn, + struct sk_buff_head *skbs); +void iwl_txq_set_q_ptrs(struct iwl_trans *trans, int txq_id, int ptr); +void iwl_trans_txq_freeze_timer(struct iwl_trans *trans, unsigned long txqs, + bool freeze); +void iwl_txq_progress(struct iwl_txq *txq); +void iwl_txq_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq); +int iwl_trans_txq_send_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd); +#endif /* __iwl_trans_queue_tx_h__ */ diff --git a/sys/modules/iwlwifi/Makefile b/sys/modules/iwlwifi/Makefile new file mode 100644 index 00000000000..c616e560f51 --- /dev/null +++ b/sys/modules/iwlwifi/Makefile @@ -0,0 +1,90 @@ +#- +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2020-2021 The FreeBSD Foundation +# +# This software was developed by Björn Zeeb under sponsorship from +# the FreeBSD Foundation. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +DEVIWLWIFIDIR= ${SRCTOP}/sys/contrib/dev/iwlwifi + +.PATH: ${DEVIWLWIFIDIR} + +KMOD= if_iwlwifi + +SRCS= iwl-drv.c +SRCS+= iwl-dbg-tlv.c iwl-debug.c +SRCS+= iwl-eeprom-parse.c iwl-eeprom-read.c +SRCS+= iwl-io.c iwl-nvm-parse.c iwl-phy-db.c iwl-trans.c +SRCS+= cfg/7000.c cfg/8000.c cfg/9000.c cfg/22000.c +SRCS+= fw/dbg.c fw/dump.c fw/img.c fw/notif-wait.c +SRCS+= fw/paging.c fw/pnvm.c fw/rs.c fw/smem.c fw/init.c +#SRCS+= fw/uefi.c +SRCS+= mvm/rs.c mvm/binding.c mvm/coex.c mvm/ftm-initiator.c +SRCS+= mvm/ftm-responder.c mvm/fw.c mvm/mac-ctxt.c +SRCS+= mvm/mac80211.c mvm/nvm.c mvm/offloading.c mvm/ops.c +SRCS+= mvm/phy-ctxt.c mvm/power.c mvm/quota.c mvm/rs-fw.c mvm/rfi.c +SRCS+= mvm/rx.c mvm/rxmq.c mvm/scan.c mvm/sf.c mvm/sta.c mvm/tdls.c +SRCS+= mvm/time-event.c mvm/tt.c mvm/tx.c mvm/utils.c +#SRCS+= mvm/led.c +SRCS+= pcie/ctxt-info-gen3.c pcie/ctxt-info.c +SRCS+= pcie/drv.c pcie/rx.c pcie/trans-gen2.c pcie/trans.c +SRCS+= pcie/tx-gen2.c pcie/tx.c +SRCS+= queue/tx.c + +SRCS+= iwl-devtrace.c + +# Other +SRCS+= ${LINUXKPI_GENSRCS} +SRCS+= opt_wlan.h opt_inet6.h opt_inet.h opt_acpi.h + +CFLAGS+= -DKBUILD_MODNAME='"iwlwifi"' + +CFLAGS+= -I${DEVIWLWIFIDIR} +CFLAGS+= -I${SRCTOP}/sys/compat/linuxkpi/common/include +CFLAGS+= -DCONFIG_IWLDVM=0 +CFLAGS+= -DCONFIG_IWLMVM=1 +# Helpful after fresh imports. +#CFLAGS+= -ferror-limit=0 + +#CFLAGS+= -DCONFIG_ACPI=1 +#CFLAGS+= -DCONFIG_INET=1 # Need LKPI TSO implementation. +#CFLAGS+= -DCONFIG_IPV6=1 +CFLAGS+= -DCONFIG_IWLWIFI_BCAST_FILTERING=1 +CFLAGS+= -DCONFIG_IWLWIFI_DEBUG=1 +#CFLAGS+= -DCONFIG_IWLWIFI_LEDS=1 +CFLAGS+= -DCONFIG_IWLWIFI_OPMODE_MODULAR=1 +CFLAGS+= -DCONFIG_IWLWIFI_DEVICE_TRACING=1 +#CFLAGS+= -DCONFIG_LOCKDEP=1 +#CFLAGS+= -DCONFIG_MAC80211_DEBUGFS=1 +#CFLAGS+= -DCONFIG_NL80211_TESTMODE=1 +#CFLAGS+= -DCONFIG_PM=1 +#CFLAGS+= -DCONFIG_PM_SLEEP=1 +#CFLAGS+= -DCONFIG_THERMAL=1 +#CFLAGS+= -DCONFIG_EFI=1 + +.include