mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
wifi: iwlwifi: add iwlmld sub-driver
iwlwifi is the driver of all Intel wifi devices since 2008. Since then, the hardware has changed a lot, but the firmware API has changed even more. The need to keep one driver that supports all those different APIs led us to introduce a new architecture circa 2012 which allowed us to keep the same interface to the hardware (DMAs, Tx queues, etc...) with a new layer to implement the mid-layer between mac80211 and the firmware. The first component is called the 'transport' and the latter is called 'operation_mode' a.k.a op_mode. In 2013 we took advantage of the new architecture to introduce iwlmvm which allowed us to implement the, then, new firmware API. This op_mode supports 7260 and up, those devices supports support at least VHT. Since then, wifi evolved and so did the firmware. It became much bigger and took a lot of functionality from the driver. It became increasingly hard to keep the same op_mode for the newest devices and we experienced frequent regressions on older devices. In order to avoid those regressions and keep the code maintainable, we decided it was about time to start a new op_mode. iwlmld is a new op_mode that supports BE200 or newer if the firmware being used is 97.ucode or newer. If the user has an older devices or BE200 with .96.ucode, iwlmvm will be loaded. Of course, this op_mode selection is seamless. All the features supported in iwlmvm are supported in iwlmld besides a few seldom used use cases: injection and Hotspot 2.0. Those are under work. A few points about the implementation: * iwlmld doesn't have any mutexes, it relies on the wiphy_lock * iwlmld is more "resource oriented": stations, links and interfaces are allocated and freed only after all the relevant flows are completed. * Firmware notifications' sizes are validated in a more structured way. We would love to see this new op_mode merged in 6.15. The firmware for this new driver (.97.ucode) is not yet publicly available but it'll be sent very soon. People eager to get an early version of this firmware can contact Emmanuel at: emmanuel.grumbach@intel.com I've listed the people who directly contributed code, but many others from various teams have contributed in other ways. Co-developed-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: Johannes Berg <johannes.berg@intel.com> Co-developed-by: Avraham Stern <avraham.stern@intel.com> Signed-off-by: Avraham Stern <avraham.stern@intel.com> Co-developed-by: Daniel Gabay <daniel.gabay@intel.com> Signed-off-by: Daniel Gabay <daniel.gabay@intel.com> Co-developed-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com> Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com> Co-developed-by: Anjaneyulu <pagadala.yesu.anjaneyulu@intel.com> Signed-off-by: Anjaneyulu <pagadala.yesu.anjaneyulu@intel.com> Co-developed-by: Yedidya Benshimol <yedidya.ben.shimol@intel.com> Signed-off-by: Yedidya Benshimol <yedidya.ben.shimol@intel.com> Co-developed-by: Benjamin Berg <benjamin.berg@intel.com> Signed-off-by: Benjamin Berg <benjamin.berg@intel.com> Co-developed-by: Shaul Triebitz <shaul.triebitz@intel.com> Signed-off-by: Shaul Triebitz <shaul.triebitz@intel.com> Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com> Link: https://lore.kernel.org/linux-wireless/20250216094321.537988-1-miriam.rachel.korenblit@intel.com/ [fix Kconfig, fix api/phy.h includes, SPDX tag and coding style issues, duplicated includes per 0-day robot] Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
committed by
Johannes Berg
parent
71f8992e34
commit
d1e879ec60
@@ -81,14 +81,25 @@ config IWLMVM
|
||||
of the devices that use this firmware is available here:
|
||||
https://wireless.wiki.kernel.org/en/users/drivers/iwlwifi#firmware
|
||||
|
||||
config IWLMLD
|
||||
tristate "Intel Wireless WiFi MLD Firmware support"
|
||||
select WANT_DEV_COREDUMP
|
||||
depends on MAC80211
|
||||
depends on PTP_1588_CLOCK_OPTIONAL
|
||||
help
|
||||
This is the driver that supports firmwares of MLD capable devices.
|
||||
The list of the devices that use this firmware is available here:
|
||||
https://wireless.wiki.kernel.org/en/users/drivers/iwlwifi#firmware
|
||||
|
||||
# don't call it _MODULE -- will confuse Kconfig/fixdep/...
|
||||
config IWLWIFI_OPMODE_MODULAR
|
||||
bool
|
||||
default y if IWLDVM=m
|
||||
default y if IWLMVM=m
|
||||
default y if IWLMLD=m
|
||||
|
||||
comment "WARNING: iwlwifi is useless without IWLDVM or IWLMVM"
|
||||
depends on IWLDVM=n && IWLMVM=n
|
||||
comment "WARNING: iwlwifi is useless without IWLDVM or IWLMVM or IWLMLD"
|
||||
depends on IWLDVM=n && IWLMVM=n && IWLMLD=n
|
||||
|
||||
menu "Debugging Options"
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ iwlwifi-objs += pcie/ctxt-info.o pcie/ctxt-info-gen3.o
|
||||
iwlwifi-objs += pcie/trans-gen2.o pcie/tx-gen2.o
|
||||
iwlwifi-$(CONFIG_IWLDVM) += cfg/1000.o cfg/2000.o cfg/5000.o cfg/6000.o
|
||||
iwlwifi-$(CONFIG_IWLMVM) += cfg/7000.o cfg/8000.o cfg/9000.o cfg/22000.o
|
||||
iwlwifi-$(CONFIG_IWLMVM) += cfg/ax210.o cfg/bz.o cfg/sc.o cfg/dr.o
|
||||
iwlwifi-$(CONFIG_IWLMVM) += cfg/ax210.o
|
||||
iwlwifi-$(CONFIG_IWLMLD) += cfg/bz.o cfg/sc.o cfg/dr.o
|
||||
iwlwifi-objs += iwl-dbg-tlv.o
|
||||
iwlwifi-objs += iwl-trans.o
|
||||
|
||||
@@ -20,6 +21,7 @@ iwlwifi-objs += fw/img.o fw/notif-wait.o fw/rs.o
|
||||
iwlwifi-objs += fw/dbg.o fw/pnvm.o fw/dump.o
|
||||
iwlwifi-objs += fw/regulatory.o
|
||||
iwlwifi-$(CONFIG_IWLMVM) += fw/paging.o fw/smem.o fw/init.o
|
||||
iwlwifi-$(CONFIG_IWLMLD) += fw/smem.o fw/init.o
|
||||
iwlwifi-$(CONFIG_ACPI) += fw/acpi.o
|
||||
iwlwifi-$(CONFIG_EFI) += fw/uefi.o
|
||||
iwlwifi-$(CONFIG_IWLWIFI_DEBUGFS) += fw/debugfs.o
|
||||
@@ -33,6 +35,7 @@ ccflags-y += -I$(src)
|
||||
obj-$(CONFIG_IWLDVM) += dvm/
|
||||
obj-$(CONFIG_IWLMVM) += mvm/
|
||||
obj-$(CONFIG_IWLMEI) += mei/
|
||||
obj-$(CONFIG_IWLMLD) += mld/
|
||||
|
||||
obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += tests/
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
#define IWL_BZ_A_HR_B_MODULE_FIRMWARE(api) \
|
||||
IWL_BZ_A_HR_B_FW_PRE "-" __stringify(api) ".ucode"
|
||||
|
||||
#if !IS_ENABLED(CONFIG_IWLMVM)
|
||||
const char iwl_ax211_name[] = "Intel(R) Wi-Fi 6E AX211 160MHz";
|
||||
const char iwl_ax201_name[] = "Intel(R) Wi-Fi 6 AX201 160MHz";
|
||||
#endif
|
||||
|
||||
static const struct iwl_base_params iwl_bz_base_params = {
|
||||
.eeprom_size = OTP_LOW_IMAGE_SIZE_32K,
|
||||
.num_of_queues = 512,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
|
||||
/*
|
||||
* Copyright (C) 2012-2014, 2019-2022, 2024 Intel Corporation
|
||||
* Copyright (C) 2012-2014, 2019-2022, 2024-2025 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__
|
||||
#include <linux/types.h>
|
||||
#include <linux/bits.h>
|
||||
|
||||
/**
|
||||
* enum iwl_phy_ops_subcmd_ids - PHY group commands
|
||||
|
||||
@@ -653,6 +653,10 @@ extern const struct iwl_cfg iwl_cfg_ma;
|
||||
|
||||
extern const struct iwl_cfg iwl_cfg_so_a0_hr_a0;
|
||||
extern const struct iwl_cfg iwl_cfg_quz_a0_hr_b0;
|
||||
#endif /* CONFIG_IWLMVM */
|
||||
|
||||
#if IS_ENABLED(CONFIG_IWLMLD)
|
||||
extern const struct iwl_ht_params iwl_bz_ht_params;
|
||||
|
||||
extern const struct iwl_ht_params iwl_bz_ht_params;
|
||||
|
||||
@@ -664,6 +668,6 @@ extern const struct iwl_cfg iwl_cfg_sc2;
|
||||
extern const struct iwl_cfg iwl_cfg_sc2f;
|
||||
extern const struct iwl_cfg iwl_cfg_dr;
|
||||
extern const struct iwl_cfg iwl_cfg_br;
|
||||
#endif /* CONFIG_IWLMVM */
|
||||
#endif /* CONFIG_IWLMLD */
|
||||
|
||||
#endif /* __IWL_CONFIG_H__ */
|
||||
|
||||
@@ -75,6 +75,9 @@ struct iwl_drv {
|
||||
enum {
|
||||
DVM_OP_MODE,
|
||||
MVM_OP_MODE,
|
||||
#if IS_ENABLED(CONFIG_IWLMLD)
|
||||
MLD_OP_MODE,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Protects the table contents, i.e. the ops pointer & drv list */
|
||||
@@ -86,6 +89,9 @@ static struct iwlwifi_opmode_table {
|
||||
} iwlwifi_opmode_table[] = { /* ops set when driver is initialized */
|
||||
[DVM_OP_MODE] = { .name = "iwldvm", .ops = NULL },
|
||||
[MVM_OP_MODE] = { .name = "iwlmvm", .ops = NULL },
|
||||
#if IS_ENABLED(CONFIG_IWLMLD)
|
||||
[MLD_OP_MODE] = { .name = "iwlmld", .ops = NULL },
|
||||
#endif
|
||||
};
|
||||
|
||||
#define IWL_DEFAULT_SCAN_CHANNELS 40
|
||||
@@ -316,6 +322,7 @@ struct iwl_firmware_pieces {
|
||||
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;
|
||||
u32 major;
|
||||
};
|
||||
|
||||
static void alloc_sec_data(struct iwl_firmware_pieces *pieces,
|
||||
@@ -957,19 +964,19 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv,
|
||||
break;
|
||||
case IWL_UCODE_TLV_FW_VERSION: {
|
||||
const __le32 *ptr = (const void *)tlv_data;
|
||||
u32 major, minor;
|
||||
u32 minor;
|
||||
u8 local_comp;
|
||||
|
||||
if (tlv_len != sizeof(u32) * 3)
|
||||
goto invalid_tlv_len;
|
||||
|
||||
major = le32_to_cpup(ptr++);
|
||||
pieces->major = le32_to_cpup(ptr++);
|
||||
minor = le32_to_cpup(ptr++);
|
||||
local_comp = le32_to_cpup(ptr);
|
||||
|
||||
snprintf(drv->fw.fw_version,
|
||||
sizeof(drv->fw.fw_version),
|
||||
"%u.%08x.%u %s", major, minor,
|
||||
"%u.%08x.%u %s", pieces->major, minor,
|
||||
local_comp, iwl_reduced_fw_name(drv));
|
||||
break;
|
||||
}
|
||||
@@ -1468,6 +1475,8 @@ static void _iwl_op_mode_stop(struct iwl_drv *drv)
|
||||
}
|
||||
}
|
||||
|
||||
#define IWL_MLD_SUPPORTED_FW_VERSION 97
|
||||
|
||||
/*
|
||||
* iwl_req_fw_callback - callback when firmware was loaded
|
||||
*
|
||||
@@ -1720,6 +1729,19 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
|
||||
break;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_IWLMLD)
|
||||
if (pieces->major >= IWL_MLD_SUPPORTED_FW_VERSION)
|
||||
op = &iwlwifi_opmode_table[MLD_OP_MODE];
|
||||
else
|
||||
#else
|
||||
if (pieces->major >= IWL_MLD_SUPPORTED_FW_VERSION) {
|
||||
IWL_ERR(drv,
|
||||
"IWLMLD needs to be compiled to support this firmware\n");
|
||||
mutex_unlock(&iwlwifi_opmode_table_mtx);
|
||||
goto out_unbind;
|
||||
}
|
||||
#endif
|
||||
|
||||
IWL_INFO(drv, "loaded firmware version %s op_mode %s\n",
|
||||
drv->fw.fw_version, op->name);
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
obj-$(CONFIG_IWLMLD) += iwlmld.o
|
||||
obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += tests/
|
||||
|
||||
iwlmld-y += mld.o notif.o mac80211.o fw.o power.o iface.o link.o rx.o mcc.o session-protect.o phy.o
|
||||
iwlmld-y += scan.o sta.o tx.o coex.o tlc.o agg.o key.o regulatory.o ap.o thermal.o roc.o stats.o
|
||||
iwlmld-y += low_latency.o mlo.o ptp.o time_sync.o ftm-initiator.o
|
||||
iwlmld-$(CONFIG_IWLWIFI_DEBUGFS) += debugfs.o
|
||||
iwlmld-$(CONFIG_IWLWIFI_LEDS) += led.o
|
||||
iwlmld-$(CONFIG_PM_SLEEP) += d3.o
|
||||
|
||||
# non-upstream things
|
||||
iwlmld-$(CONFIG_IWL_VENDOR_CMDS) += vendor-cmd.o
|
||||
iwlmld-$(CONFIG_IWLMVM_AX_SOFTAP_TESTMODE) += ax-softap-testmode.o
|
||||
|
||||
subdir-ccflags-y += -I$(src)/../
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
|
||||
/*
|
||||
* Copyright (C) 2024 Intel Corporation
|
||||
*/
|
||||
#ifndef __iwl_agg_h__
|
||||
#define __iwl_agg_h__
|
||||
|
||||
#include "mld.h"
|
||||
#include "fw/api/rx.h"
|
||||
|
||||
/**
|
||||
* struct iwl_mld_reorder_buffer - per ra/tid/queue reorder buffer
|
||||
* @head_sn: reorder window head sequence number
|
||||
* @num_stored: number of MPDUs stored in the buffer
|
||||
* @queue: queue of this reorder buffer
|
||||
* @valid: true if reordering is valid for this queue
|
||||
*/
|
||||
struct iwl_mld_reorder_buffer {
|
||||
u16 head_sn;
|
||||
u16 num_stored;
|
||||
int queue;
|
||||
bool valid;
|
||||
} ____cacheline_aligned_in_smp;
|
||||
|
||||
/**
|
||||
* struct iwl_mld_reorder_buf_entry - reorder buffer entry per-queue/per-seqno
|
||||
* @frames: list of skbs stored. a list is necessary because in an A-MSDU,
|
||||
* all sub-frames share the same sequence number, so they are stored
|
||||
* together in the same list.
|
||||
*/
|
||||
struct iwl_mld_reorder_buf_entry {
|
||||
struct sk_buff_head frames;
|
||||
}
|
||||
#ifndef __CHECKER__
|
||||
/* sparse doesn't like this construct: "bad integer constant expression" */
|
||||
__aligned(roundup_pow_of_two(sizeof(struct sk_buff_head)))
|
||||
#endif
|
||||
;
|
||||
|
||||
/**
|
||||
* struct iwl_mld_baid_data - Block Ack session data
|
||||
* @rcu_head: RCU head for freeing this data
|
||||
* @sta_mask: station mask for the BAID
|
||||
* @tid: tid of the session
|
||||
* @baid: baid of the session
|
||||
* @buf_size: the reorder buffer size as set by the last ADDBA request
|
||||
* @entries_per_queue: number of buffers per queue, this actually gets
|
||||
* aligned up to avoid cache line sharing between queues
|
||||
* @timeout: the timeout value specified in the ADDBA request.
|
||||
* @last_rx_timestamp: timestamp of the last received packet (in jiffies). This
|
||||
* value is updated only when the configured @timeout has passed since
|
||||
* the last update to minimize cache bouncing between RX queues.
|
||||
* @session_timer: timer is set to expire after 2 * @timeout (since we want
|
||||
* to minimize the cache bouncing by updating @last_rx_timestamp only once
|
||||
* after @timeout has passed). If no packets are received within this
|
||||
* period, it informs mac80211 to initiate delBA flow, terminating the
|
||||
* BA session.
|
||||
* @rcu_ptr: BA data RCU protected access
|
||||
* @mld: mld pointer, needed for timer context
|
||||
* @reorder_buf: reorder buffer, allocated per queue
|
||||
* @entries: data
|
||||
*/
|
||||
struct iwl_mld_baid_data {
|
||||
struct rcu_head rcu_head;
|
||||
u32 sta_mask;
|
||||
u8 tid;
|
||||
u8 baid;
|
||||
u16 buf_size;
|
||||
u16 entries_per_queue;
|
||||
u16 timeout;
|
||||
struct timer_list session_timer;
|
||||
unsigned long last_rx_timestamp;
|
||||
struct iwl_mld_baid_data __rcu **rcu_ptr;
|
||||
struct iwl_mld *mld;
|
||||
struct iwl_mld_reorder_buffer reorder_buf[IWL_MAX_RX_HW_QUEUES];
|
||||
struct iwl_mld_reorder_buf_entry entries[] ____cacheline_aligned_in_smp;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct iwl_mld_delba_data - RX queue sync data for %IWL_MLD_RXQ_NOTIF_DEL_BA
|
||||
*
|
||||
* @baid: Block Ack id, used to identify the BA session to be removed
|
||||
*/
|
||||
struct iwl_mld_delba_data {
|
||||
u32 baid;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* enum iwl_mld_reorder_result - Possible return values for iwl_mld_reorder()
|
||||
* indicating how the caller should handle the skb based on the result.
|
||||
*
|
||||
* @IWL_MLD_PASS_SKB: skb should be passed to upper layer.
|
||||
* @IWL_MLD_BUFFERED_SKB: skb has been buffered, don't pass it to upper layer.
|
||||
* @IWL_MLD_DROP_SKB: skb should be dropped and freed by the caller.
|
||||
*/
|
||||
enum iwl_mld_reorder_result {
|
||||
IWL_MLD_PASS_SKB,
|
||||
IWL_MLD_BUFFERED_SKB,
|
||||
IWL_MLD_DROP_SKB
|
||||
};
|
||||
|
||||
int iwl_mld_ampdu_rx_start(struct iwl_mld *mld, struct ieee80211_sta *sta,
|
||||
int tid, u16 ssn, u16 buf_size, u16 timeout);
|
||||
int iwl_mld_ampdu_rx_stop(struct iwl_mld *mld, struct ieee80211_sta *sta,
|
||||
int tid);
|
||||
|
||||
enum iwl_mld_reorder_result
|
||||
iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
|
||||
int queue, struct ieee80211_sta *sta,
|
||||
struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc);
|
||||
|
||||
void iwl_mld_handle_frame_release_notif(struct iwl_mld *mld,
|
||||
struct napi_struct *napi,
|
||||
struct iwl_rx_packet *pkt, int queue);
|
||||
void iwl_mld_handle_bar_frame_release_notif(struct iwl_mld *mld,
|
||||
struct napi_struct *napi,
|
||||
struct iwl_rx_packet *pkt,
|
||||
int queue);
|
||||
|
||||
void iwl_mld_del_ba(struct iwl_mld *mld, int queue,
|
||||
struct iwl_mld_delba_data *data);
|
||||
|
||||
int iwl_mld_update_sta_baids(struct iwl_mld *mld,
|
||||
u32 old_sta_mask,
|
||||
u32 new_sta_mask);
|
||||
|
||||
#endif /* __iwl_agg_h__ */
|
||||
@@ -0,0 +1,344 @@
|
||||
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright (C) 2024 Intel Corporation
|
||||
*/
|
||||
#include <linux/crc32.h>
|
||||
|
||||
#include <net/mac80211.h>
|
||||
|
||||
#include "ap.h"
|
||||
#include "hcmd.h"
|
||||
#include "tx.h"
|
||||
#include "power.h"
|
||||
#include "key.h"
|
||||
#include "iwl-utils.h"
|
||||
|
||||
#include "fw/api/sta.h"
|
||||
|
||||
void iwl_mld_set_tim_idx(struct iwl_mld *mld, __le32 *tim_index,
|
||||
u8 *beacon, u32 frame_size)
|
||||
{
|
||||
u32 tim_idx;
|
||||
struct ieee80211_mgmt *mgmt = (void *)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);
|
||||
else
|
||||
IWL_WARN(mld, "Unable to find TIM Element in beacon\n");
|
||||
}
|
||||
|
||||
u8 iwl_mld_get_rate_flags(struct iwl_mld *mld,
|
||||
struct ieee80211_tx_info *info,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link,
|
||||
enum nl80211_band band)
|
||||
{
|
||||
u32 legacy = link->beacon_tx_rate.control[band].legacy;
|
||||
u32 rate_idx, rate_flags = 0, fw_rate;
|
||||
|
||||
/* if beacon rate was configured try using it */
|
||||
if (hweight32(legacy) == 1) {
|
||||
u32 rate = ffs(legacy) - 1;
|
||||
struct ieee80211_supported_band *sband =
|
||||
mld->hw->wiphy->bands[band];
|
||||
|
||||
rate_idx = sband->bitrates[rate].hw_value;
|
||||
} else {
|
||||
rate_idx = iwl_mld_get_lowest_rate(mld, info, vif);
|
||||
}
|
||||
|
||||
if (rate_idx <= IWL_LAST_CCK_RATE)
|
||||
rate_flags = IWL_MAC_BEACON_CCK;
|
||||
|
||||
/* Legacy rates are indexed as follows:
|
||||
* 0 - 3 for CCK and 0 - 7 for OFDM.
|
||||
*/
|
||||
fw_rate = (rate_idx >= IWL_FIRST_OFDM_RATE ?
|
||||
rate_idx - IWL_FIRST_OFDM_RATE : rate_idx);
|
||||
|
||||
return fw_rate | rate_flags;
|
||||
}
|
||||
|
||||
int iwl_mld_send_beacon_template_cmd(struct iwl_mld *mld,
|
||||
struct sk_buff *beacon,
|
||||
struct iwl_mac_beacon_cmd *cmd)
|
||||
{
|
||||
struct iwl_host_cmd hcmd = {
|
||||
.id = BEACON_TEMPLATE_CMD,
|
||||
};
|
||||
|
||||
hcmd.len[0] = sizeof(*cmd);
|
||||
hcmd.data[0] = cmd;
|
||||
|
||||
hcmd.len[1] = beacon->len;
|
||||
hcmd.data[1] = beacon->data;
|
||||
hcmd.dataflags[1] = IWL_HCMD_DFL_DUP;
|
||||
|
||||
return iwl_mld_send_cmd(mld, &hcmd);
|
||||
}
|
||||
|
||||
static int iwl_mld_fill_beacon_template_cmd(struct iwl_mld *mld,
|
||||
struct ieee80211_vif *vif,
|
||||
struct sk_buff *beacon,
|
||||
struct iwl_mac_beacon_cmd *cmd,
|
||||
struct ieee80211_bss_conf *link)
|
||||
{
|
||||
struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
|
||||
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(beacon);
|
||||
struct ieee80211_chanctx_conf *ctx;
|
||||
bool enable_fils;
|
||||
u16 flags = 0;
|
||||
|
||||
lockdep_assert_wiphy(mld->wiphy);
|
||||
|
||||
if (WARN_ON(!mld_link))
|
||||
return -EINVAL;
|
||||
|
||||
cmd->link_id = cpu_to_le32(mld_link->fw_id);
|
||||
|
||||
ctx = wiphy_dereference(mld->wiphy, link->chanctx_conf);
|
||||
if (WARN_ON(!ctx || !ctx->def.chan))
|
||||
return -EINVAL;
|
||||
|
||||
enable_fils = cfg80211_channel_is_psc(ctx->def.chan) ||
|
||||
(ctx->def.chan->band == NL80211_BAND_6GHZ &&
|
||||
ctx->def.width >= NL80211_CHAN_WIDTH_80);
|
||||
|
||||
if (enable_fils) {
|
||||
flags |= IWL_MAC_BEACON_FILS;
|
||||
cmd->short_ssid = cpu_to_le32(~crc32_le(~0, vif->cfg.ssid,
|
||||
vif->cfg.ssid_len));
|
||||
}
|
||||
|
||||
cmd->byte_cnt = cpu_to_le16((u16)beacon->len);
|
||||
|
||||
flags |= iwl_mld_get_rate_flags(mld, info, vif, link,
|
||||
ctx->def.chan->band);
|
||||
|
||||
cmd->flags = cpu_to_le16(flags);
|
||||
|
||||
if (vif->type == NL80211_IFTYPE_AP) {
|
||||
iwl_mld_set_tim_idx(mld, &cmd->tim_idx,
|
||||
beacon->data, beacon->len);
|
||||
|
||||
cmd->btwt_offset =
|
||||
cpu_to_le32(iwl_find_ie_offset(beacon->data,
|
||||
WLAN_EID_S1G_TWT,
|
||||
beacon->len));
|
||||
}
|
||||
|
||||
cmd->csa_offset =
|
||||
cpu_to_le32(iwl_find_ie_offset(beacon->data,
|
||||
WLAN_EID_CHANNEL_SWITCH,
|
||||
beacon->len));
|
||||
cmd->ecsa_offset =
|
||||
cpu_to_le32(iwl_find_ie_offset(beacon->data,
|
||||
WLAN_EID_EXT_CHANSWITCH_ANN,
|
||||
beacon->len));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The beacon template for the AP/GO/IBSS has changed and needs update */
|
||||
int iwl_mld_update_beacon_template(struct iwl_mld *mld,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link_conf)
|
||||
{
|
||||
struct iwl_mac_beacon_cmd cmd = {};
|
||||
struct sk_buff *beacon;
|
||||
int ret;
|
||||
#ifdef CONFIG_IWLWIFI_DEBUGFS
|
||||
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
||||
#endif
|
||||
|
||||
WARN_ON(vif->type != NL80211_IFTYPE_AP &&
|
||||
vif->type != NL80211_IFTYPE_ADHOC);
|
||||
|
||||
if (IWL_MLD_NON_TRANSMITTING_AP)
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_IWLWIFI_DEBUGFS
|
||||
if (mld_vif->beacon_inject_active) {
|
||||
IWL_DEBUG_INFO(mld,
|
||||
"Can't update template, beacon injection's active\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
#endif
|
||||
beacon = ieee80211_beacon_get_template(mld->hw, vif, NULL,
|
||||
link_conf->link_id);
|
||||
if (!beacon)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = iwl_mld_fill_beacon_template_cmd(mld, vif, beacon, &cmd,
|
||||
link_conf);
|
||||
|
||||
if (!ret)
|
||||
ret = iwl_mld_send_beacon_template_cmd(mld, beacon, &cmd);
|
||||
|
||||
dev_kfree_skb(beacon);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void iwl_mld_free_ap_early_key(struct iwl_mld *mld,
|
||||
struct ieee80211_key_conf *key,
|
||||
struct iwl_mld_vif *mld_vif)
|
||||
{
|
||||
struct iwl_mld_link *link;
|
||||
|
||||
if (WARN_ON(key->link_id < 0))
|
||||
return;
|
||||
|
||||
link = iwl_mld_link_dereference_check(mld_vif, key->link_id);
|
||||
if (WARN_ON(!link))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(link->ap_early_keys); i++) {
|
||||
if (link->ap_early_keys[i] != key)
|
||||
continue;
|
||||
/* Those weren't sent to FW, so should be marked as INVALID */
|
||||
if (WARN_ON(key->hw_key_idx != STA_KEY_IDX_INVALID))
|
||||
key->hw_key_idx = STA_KEY_IDX_INVALID;
|
||||
link->ap_early_keys[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int iwl_mld_store_ap_early_key(struct iwl_mld *mld,
|
||||
struct ieee80211_key_conf *key,
|
||||
struct iwl_mld_vif *mld_vif)
|
||||
{
|
||||
struct iwl_mld_link *link;
|
||||
|
||||
if (WARN_ON(key->link_id < 0))
|
||||
return -EINVAL;
|
||||
|
||||
link = iwl_mld_link_dereference_check(mld_vif, key->link_id);
|
||||
if (WARN_ON(!link))
|
||||
return -EINVAL;
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(link->ap_early_keys); i++) {
|
||||
if (!link->ap_early_keys[i]) {
|
||||
link->ap_early_keys[i] = key;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
static int iwl_mld_send_ap_early_keys(struct iwl_mld *mld,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link)
|
||||
{
|
||||
struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
|
||||
int ret = 0;
|
||||
|
||||
if (WARN_ON(!link))
|
||||
return -EINVAL;
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(mld_link->ap_early_keys); i++) {
|
||||
struct ieee80211_key_conf *key = mld_link->ap_early_keys[i];
|
||||
|
||||
if (!key)
|
||||
continue;
|
||||
|
||||
mld_link->ap_early_keys[i] = NULL;
|
||||
|
||||
ret = iwl_mld_add_key(mld, vif, NULL, key);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int iwl_mld_start_ap_ibss(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link)
|
||||
{
|
||||
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
|
||||
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
||||
int ret;
|
||||
|
||||
if (vif->type == NL80211_IFTYPE_AP)
|
||||
iwl_mld_send_ap_tx_power_constraint_cmd(mld, vif, link);
|
||||
|
||||
ret = iwl_mld_update_beacon_template(mld, vif, link);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* the link should be already activated when assigning chan context,
|
||||
* and LINK_CONTEXT_MODIFY_EHT_PARAMS is deprecated
|
||||
*/
|
||||
ret = iwl_mld_change_link_in_fw(mld, link,
|
||||
LINK_CONTEXT_MODIFY_ALL &
|
||||
~(LINK_CONTEXT_MODIFY_ACTIVE |
|
||||
LINK_CONTEXT_MODIFY_EHT_PARAMS));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = iwl_mld_add_mcast_sta(mld, vif, link);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = iwl_mld_add_bcast_sta(mld, vif, link);
|
||||
if (ret)
|
||||
goto rm_mcast;
|
||||
|
||||
/* Those keys were configured by the upper layers before starting the
|
||||
* AP. Now that it is started and the bcast and mcast sta were added to
|
||||
* the FW, we can add the keys too.
|
||||
*/
|
||||
ret = iwl_mld_send_ap_early_keys(mld, vif, link);
|
||||
if (ret)
|
||||
goto rm_bcast;
|
||||
|
||||
if (ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_AP)
|
||||
iwl_mld_vif_update_low_latency(mld, vif, true,
|
||||
LOW_LATENCY_VIF_TYPE);
|
||||
|
||||
mld_vif->ap_ibss_active = true;
|
||||
|
||||
if (vif->p2p && mld->p2p_device_vif)
|
||||
return iwl_mld_mac_fw_action(mld, mld->p2p_device_vif,
|
||||
FW_CTXT_ACTION_MODIFY);
|
||||
|
||||
return 0;
|
||||
rm_bcast:
|
||||
iwl_mld_remove_bcast_sta(mld, vif, link);
|
||||
rm_mcast:
|
||||
iwl_mld_remove_mcast_sta(mld, vif, link);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void iwl_mld_stop_ap_ibss(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link)
|
||||
{
|
||||
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
|
||||
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
||||
|
||||
mld_vif->ap_ibss_active = false;
|
||||
|
||||
if (vif->p2p && mld->p2p_device_vif)
|
||||
iwl_mld_mac_fw_action(mld, mld->p2p_device_vif,
|
||||
FW_CTXT_ACTION_MODIFY);
|
||||
|
||||
if (ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_AP)
|
||||
iwl_mld_vif_update_low_latency(mld, vif, false,
|
||||
LOW_LATENCY_VIF_TYPE);
|
||||
|
||||
iwl_mld_remove_bcast_sta(mld, vif, link);
|
||||
|
||||
iwl_mld_remove_mcast_sta(mld, vif, link);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
|
||||
/*
|
||||
* Copyright (C) 2024 Intel Corporation
|
||||
*/
|
||||
#ifndef __iwl_ap_h__
|
||||
#define __iwl_ap_h__
|
||||
|
||||
#include "mld.h"
|
||||
#include "iface.h"
|
||||
|
||||
#include "fw/api/tx.h"
|
||||
|
||||
int iwl_mld_update_beacon_template(struct iwl_mld *mld,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link_conf);
|
||||
|
||||
int iwl_mld_start_ap_ibss(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link);
|
||||
|
||||
void iwl_mld_stop_ap_ibss(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link);
|
||||
|
||||
int iwl_mld_store_ap_early_key(struct iwl_mld *mld,
|
||||
struct ieee80211_key_conf *key,
|
||||
struct iwl_mld_vif *mld_vif);
|
||||
|
||||
void iwl_mld_free_ap_early_key(struct iwl_mld *mld,
|
||||
struct ieee80211_key_conf *key,
|
||||
struct iwl_mld_vif *mld_vif);
|
||||
|
||||
u8 iwl_mld_get_rate_flags(struct iwl_mld *mld,
|
||||
struct ieee80211_tx_info *info,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *link,
|
||||
enum nl80211_band band);
|
||||
|
||||
void iwl_mld_set_tim_idx(struct iwl_mld *mld, __le32 *tim_index,
|
||||
u8 *beacon, u32 frame_size);
|
||||
|
||||
int iwl_mld_send_beacon_template_cmd(struct iwl_mld *mld,
|
||||
struct sk_buff *beacon,
|
||||
struct iwl_mac_beacon_cmd *cmd);
|
||||
|
||||
#endif /* __iwl_ap_h__ */
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Intel Corporation
|
||||
*/
|
||||
|
||||
#include "fw/api/coex.h"
|
||||
|
||||
#include "coex.h"
|
||||
#include "mld.h"
|
||||
#include "hcmd.h"
|
||||
#include "mlo.h"
|
||||
|
||||
int iwl_mld_send_bt_init_conf(struct iwl_mld *mld)
|
||||
{
|
||||
struct iwl_bt_coex_cmd cmd = {
|
||||
.mode = cpu_to_le32(BT_COEX_NW),
|
||||
.enabled_modules = cpu_to_le32(BT_COEX_MPLUT_ENABLED |
|
||||
BT_COEX_HIGH_BAND_RET),
|
||||
};
|
||||
|
||||
return iwl_mld_send_cmd_pdu(mld, BT_CONFIG, &cmd);
|
||||
}
|
||||
|
||||
void iwl_mld_handle_bt_coex_notif(struct iwl_mld *mld,
|
||||
struct iwl_rx_packet *pkt)
|
||||
{
|
||||
const struct iwl_bt_coex_profile_notif *notif = (void *)pkt->data;
|
||||
const struct iwl_bt_coex_profile_notif zero_notif = {};
|
||||
/* zeroed structure means that BT is OFF */
|
||||
bool bt_is_active = memcmp(notif, &zero_notif, sizeof(*notif));
|
||||
|
||||
if (bt_is_active == mld->bt_is_active)
|
||||
return;
|
||||
|
||||
IWL_DEBUG_INFO(mld, "BT was turned %s\n", bt_is_active ? "ON" : "OFF");
|
||||
|
||||
mld->bt_is_active = bt_is_active;
|
||||
|
||||
iwl_mld_emlsr_check_bt(mld);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
|
||||
/*
|
||||
* Copyright (C) 2024 Intel Corporation
|
||||
*/
|
||||
#ifndef __iwl_mld_coex_h__
|
||||
#define __iwl_mld_coex_h__
|
||||
|
||||
#include "mld.h"
|
||||
|
||||
int iwl_mld_send_bt_init_conf(struct iwl_mld *mld);
|
||||
|
||||
void iwl_mld_handle_bt_coex_notif(struct iwl_mld *mld,
|
||||
struct iwl_rx_packet *pkt);
|
||||
|
||||
#endif /* __iwl_mld_coex_h__ */
|
||||
@@ -0,0 +1,88 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Intel Corporation
|
||||
*/
|
||||
#ifndef __iwl_mld_constants_h__
|
||||
#define __iwl_mld_constants_h__
|
||||
|
||||
#define IWL_MLD_MISSED_BEACONS_SINCE_RX_THOLD 4
|
||||
#define IWL_MLD_MISSED_BEACONS_THRESHOLD 8
|
||||
#define IWL_MLD_MISSED_BEACONS_THRESHOLD_LONG 19
|
||||
#define IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS 5
|
||||
#define IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH 15
|
||||
#define IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED 11
|
||||
#define IWL_MLD_LOW_RSSI_MLO_SCAN_THRESH -72
|
||||
|
||||
#define IWL_MLD_DEFAULT_PS_TX_DATA_TIMEOUT (100 * USEC_PER_MSEC)
|
||||
#define IWL_MLD_DEFAULT_PS_RX_DATA_TIMEOUT (100 * USEC_PER_MSEC)
|
||||
#define IWL_MLD_WOWLAN_PS_TX_DATA_TIMEOUT (10 * USEC_PER_MSEC)
|
||||
#define IWL_MLD_WOWLAN_PS_RX_DATA_TIMEOUT (10 * USEC_PER_MSEC)
|
||||
#define IWL_MLD_SHORT_PS_TX_DATA_TIMEOUT (2 * 1024) /* defined in TU */
|
||||
#define IWL_MLD_SHORT_PS_RX_DATA_TIMEOUT (40 * 1024) /* defined in TU */
|
||||
|
||||
#define IWL_MLD_UAPSD_RX_DATA_TIMEOUT (50 * USEC_PER_MSEC)
|
||||
#define IWL_MLD_UAPSD_TX_DATA_TIMEOUT (50 * USEC_PER_MSEC)
|
||||
|
||||
#define IWL_MLD_PS_SNOOZE_INTERVAL 25
|
||||
#define IWL_MLD_PS_SNOOZE_INTERVAL 25
|
||||
#define IWL_MLD_PS_SNOOZE_WINDOW 50
|
||||
|
||||
#define IWL_MLD_PS_SNOOZE_HEAVY_TX_THLD_PACKETS 30
|
||||
#define IWL_MLD_PS_SNOOZE_HEAVY_RX_THLD_PACKETS 20
|
||||
|
||||
#define IWL_MLD_PS_HEAVY_TX_THLD_PERCENT 50
|
||||
#define IWL_MLD_PS_HEAVY_RX_THLD_PERCENT 50
|
||||
#define IWL_MLD_PS_HEAVY_TX_THLD_PACKETS 20
|
||||
#define IWL_MLD_PS_HEAVY_RX_THLD_PACKETS 8
|
||||
|
||||
#define IWL_MLD_TRIGGER_LINK_SEL_TIME_SEC 30
|
||||
#define IWL_MLD_SCAN_EXPIRE_TIME_SEC 20
|
||||
|
||||
#define IWL_MLD_TPT_COUNT_WINDOW (5 * HZ)
|
||||
|
||||
/* OMI reduced BW thresholds (channel load percentage) */
|
||||
#define IWL_MLD_OMI_ENTER_CHAN_LOAD 10
|
||||
#define IWL_MLD_OMI_EXIT_CHAN_LOAD_160 20
|
||||
#define IWL_MLD_OMI_EXIT_CHAN_LOAD_320 30
|
||||
/* time (in milliseconds) to let AP "settle" the OMI */
|
||||
#define IWL_MLD_OMI_AP_SETTLE_DELAY 27
|
||||
/* time (in milliseconds) to not enter OMI reduced BW after leaving */
|
||||
#define IWL_MLD_OMI_EXIT_PROTECTION 5000
|
||||
|
||||
#define IWL_MLD_DIS_RANDOM_FW_ID false
|
||||
#define IWL_MLD_D3_DEBUG false
|
||||
#define IWL_MLD_NON_TRANSMITTING_AP false
|
||||
#define IWL_MLD_6GHZ_PASSIVE_SCAN_TIMEOUT 3000 /* in seconds */
|
||||
#define IWL_MLD_6GHZ_PASSIVE_SCAN_ASSOC_TIMEOUT 60 /* in seconds */
|
||||
#define IWL_MLD_CONN_LISTEN_INTERVAL 10
|
||||
#define IWL_MLD_ADAPTIVE_DWELL_NUM_APS_OVERRIDE 0
|
||||
#define IWL_MLD_AUTO_EML_ENABLE true
|
||||
|
||||
#define IWL_MLD_HIGH_RSSI_THRESH_20MHZ -67
|
||||
#define IWL_MLD_LOW_RSSI_THRESH_20MHZ -72
|
||||
#define IWL_MLD_HIGH_RSSI_THRESH_40MHZ -64
|
||||
#define IWL_MLD_LOW_RSSI_THRESH_40MHZ -72
|
||||
#define IWL_MLD_HIGH_RSSI_THRESH_80MHZ -61
|
||||
#define IWL_MLD_LOW_RSSI_THRESH_80MHZ -72
|
||||
#define IWL_MLD_HIGH_RSSI_THRESH_160MHZ -58
|
||||
#define IWL_MLD_LOW_RSSI_THRESH_160MHZ -72
|
||||
|
||||
#define IWL_MLD_ENTER_EMLSR_TPT_THRESH 400
|
||||
#define IWL_MLD_CHAN_LOAD_THRESH 2 /* in percentage */
|
||||
|
||||
#define IWL_MLD_FTM_INITIATOR_ALGO IWL_TOF_ALGO_TYPE_MAX_LIKE
|
||||
#define IWL_MLD_FTM_INITIATOR_DYNACK true
|
||||
#define IWL_MLD_FTM_LMR_FEEDBACK_TERMINATE false
|
||||
#define IWL_MLD_FTM_TEST_INCORRECT_SAC false
|
||||
#define IWL_MLD_FTM_R2I_MAX_REP 7
|
||||
#define IWL_MLD_FTM_I2R_MAX_REP 7
|
||||
#define IWL_MLD_FTM_R2I_MAX_STS 1
|
||||
#define IWL_MLD_FTM_I2R_MAX_STS 1
|
||||
#define IWL_MLD_FTM_R2I_MAX_TOTAL_LTF 3
|
||||
#define IWL_MLD_FTM_I2R_MAX_TOTAL_LTF 3
|
||||
#define IWL_MLD_FTM_RESP_NDP_SUPPORT true
|
||||
#define IWL_MLD_FTM_RESP_LMR_FEEDBACK_SUPPORT true
|
||||
#define IWL_MLD_FTM_NON_TB_MIN_TIME_BETWEEN_MSR 7
|
||||
#define IWL_MLD_FTM_NON_TB_MAX_TIME_BETWEEN_MSR 1000
|
||||
|
||||
#endif /* __iwl_mld_constants_h__ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
|
||||
/*
|
||||
* Copyright (C) 2024 Intel Corporation
|
||||
*/
|
||||
#ifndef __iwl_mld_d3_h__
|
||||
#define __iwl_mld_d3_h__
|
||||
|
||||
#include "fw/api/d3.h"
|
||||
|
||||
struct iwl_mld_rekey_data {
|
||||
bool valid;
|
||||
u8 kck[NL80211_KCK_EXT_LEN];
|
||||
u8 kek[NL80211_KEK_EXT_LEN];
|
||||
size_t kck_len;
|
||||
size_t kek_len;
|
||||
__le64 replay_ctr;
|
||||
u32 akm;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct iwl_mld_wowlan_data - data used by the wowlan suspend flow
|
||||
*
|
||||
* @target_ipv6_addrs: IPv6 addresses on this interface for offload
|
||||
* @tentative_addrs: bitmap of tentative IPv6 addresses in @target_ipv6_addrs
|
||||
* @num_target_ipv6_addrs: number of @target_ipv6_addrs
|
||||
* @rekey_data: security key data used for rekeying during D3
|
||||
*/
|
||||
struct iwl_mld_wowlan_data {
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
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
|
||||
struct iwl_mld_rekey_data rekey_data;
|
||||
};
|
||||
|
||||
int iwl_mld_no_wowlan_resume(struct iwl_mld *mld);
|
||||
int iwl_mld_no_wowlan_suspend(struct iwl_mld *mld);
|
||||
int iwl_mld_wowlan_suspend(struct iwl_mld *mld,
|
||||
struct cfg80211_wowlan *wowlan);
|
||||
int iwl_mld_wowlan_resume(struct iwl_mld *mld);
|
||||
void iwl_mld_set_rekey_data(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
struct cfg80211_gtk_rekey_data *data);
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
void iwl_mld_ipv6_addr_change(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
struct inet6_dev *idev);
|
||||
#endif
|
||||
|
||||
#endif /* __iwl_mld_d3_h__ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,200 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
|
||||
/*
|
||||
* Copyright (C) 2024 Intel Corporation
|
||||
*/
|
||||
#include "iface.h"
|
||||
#include "sta.h"
|
||||
|
||||
#define MLD_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \
|
||||
struct dbgfs_##name##_data { \
|
||||
argtype *arg; \
|
||||
bool read_done; \
|
||||
ssize_t rlen; \
|
||||
char buf[buflen]; \
|
||||
}; \
|
||||
static int _iwl_dbgfs_##name##_open(struct inode *inode, \
|
||||
struct file *file) \
|
||||
{ \
|
||||
struct dbgfs_##name##_data *data; \
|
||||
\
|
||||
if ((file->f_flags & O_ACCMODE) == O_RDWR) \
|
||||
return -EOPNOTSUPP; \
|
||||
\
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL); \
|
||||
if (!data) \
|
||||
return -ENOMEM; \
|
||||
\
|
||||
data->read_done = false; \
|
||||
data->arg = inode->i_private; \
|
||||
file->private_data = data; \
|
||||
\
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#define MLD_DEBUGFS_READ_WRAPPER(name) \
|
||||
static ssize_t _iwl_dbgfs_##name##_read(struct file *file, \
|
||||
char __user *user_buf, \
|
||||
size_t count, loff_t *ppos) \
|
||||
{ \
|
||||
struct dbgfs_##name##_data *data = file->private_data; \
|
||||
\
|
||||
if (!data->read_done) { \
|
||||
data->read_done = true; \
|
||||
data->rlen = iwl_dbgfs_##name##_read(data->arg, \
|
||||
sizeof(data->buf),\
|
||||
data->buf); \
|
||||
} \
|
||||
\
|
||||
if (data->rlen < 0) \
|
||||
return data->rlen; \
|
||||
return simple_read_from_buffer(user_buf, count, ppos, \
|
||||
data->buf, data->rlen); \
|
||||
}
|
||||
|
||||
static int _iwl_dbgfs_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
kfree(file->private_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define _MLD_DEBUGFS_READ_FILE_OPS(name, buflen, argtype) \
|
||||
MLD_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \
|
||||
MLD_DEBUGFS_READ_WRAPPER(name) \
|
||||
static const struct file_operations iwl_dbgfs_##name##_ops = { \
|
||||
.read = _iwl_dbgfs_##name##_read, \
|
||||
.open = _iwl_dbgfs_##name##_open, \
|
||||
.llseek = generic_file_llseek, \
|
||||
.release = _iwl_dbgfs_release, \
|
||||
}
|
||||
|
||||
#define WIPHY_DEBUGFS_HANDLER_WRAPPER(name) \
|
||||
static ssize_t iwl_dbgfs_##name##_write_handler(struct wiphy *wiphy, \
|
||||
struct file *file, char *buf, \
|
||||
size_t count, void *data) \
|
||||
{ \
|
||||
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); \
|
||||
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); \
|
||||
return iwl_dbgfs_##name##_write(mld, buf, count, data); \
|
||||
}
|
||||
|
||||
static inline struct iwl_mld *
|
||||
iwl_mld_from_link_sta(struct ieee80211_link_sta *link_sta)
|
||||
{
|
||||
struct ieee80211_vif *vif =
|
||||
iwl_mld_sta_from_mac80211(link_sta->sta)->vif;
|
||||
return iwl_mld_vif_from_mac80211(vif)->mld;
|
||||
}
|
||||
|
||||
static inline struct iwl_mld *
|
||||
iwl_mld_from_bss_conf(struct ieee80211_bss_conf *link)
|
||||
{
|
||||
return iwl_mld_vif_from_mac80211(link->vif)->mld;
|
||||
}
|
||||
|
||||
static inline struct iwl_mld *iwl_mld_from_vif(struct ieee80211_vif *vif)
|
||||
{
|
||||
return iwl_mld_vif_from_mac80211(vif)->mld;
|
||||
}
|
||||
|
||||
#define WIPHY_DEBUGFS_WRITE_WRAPPER(name, bufsz, objtype) \
|
||||
WIPHY_DEBUGFS_HANDLER_WRAPPER(name) \
|
||||
static ssize_t __iwl_dbgfs_##name##_write(struct file *file, \
|
||||
const char __user *user_buf, \
|
||||
size_t count, loff_t *ppos) \
|
||||
{ \
|
||||
struct ieee80211_##objtype *arg = file->private_data; \
|
||||
struct iwl_mld *mld = iwl_mld_from_##objtype(arg); \
|
||||
char buf[bufsz] = {}; \
|
||||
\
|
||||
return wiphy_locked_debugfs_write(mld->wiphy, file, \
|
||||
buf, sizeof(buf), \
|
||||
user_buf, count, \
|
||||
iwl_dbgfs_##name##_write_handler, \
|
||||
arg); \
|
||||
}
|
||||
|
||||
#define WIPHY_DEBUGFS_WRITE_FILE_OPS(name, bufsz, objtype) \
|
||||
WIPHY_DEBUGFS_WRITE_WRAPPER(name, bufsz, objtype) \
|
||||
static const struct file_operations iwl_dbgfs_##name##_ops = { \
|
||||
.write = __iwl_dbgfs_##name##_write, \
|
||||
.open = simple_open, \
|
||||
.llseek = generic_file_llseek, \
|
||||
}
|
||||
|
||||
#define WIPHY_DEBUGFS_HANDLER_WRAPPER_MLD(name) \
|
||||
static ssize_t iwl_dbgfs_##name##_write_handler(struct wiphy *wiphy, \
|
||||
struct file *file, char *buf, \
|
||||
size_t count, void *data) \
|
||||
{ \
|
||||
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); \
|
||||
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); \
|
||||
return iwl_dbgfs_##name##_write(mld, buf, count); \
|
||||
}
|
||||
|
||||
#define WIPHY_DEBUGFS_WRITE_WRAPPER_MLD(name) \
|
||||
WIPHY_DEBUGFS_HANDLER_WRAPPER_MLD(name) \
|
||||
static ssize_t __iwl_dbgfs_##name##_write(struct file *file, \
|
||||
const char __user *user_buf, \
|
||||
size_t count, loff_t *ppos) \
|
||||
{ \
|
||||
struct dbgfs_##name##_data *data = file->private_data; \
|
||||
struct iwl_mld *mld = data->arg; \
|
||||
\
|
||||
return wiphy_locked_debugfs_write(mld->wiphy, file, \
|
||||
data->buf, sizeof(data->buf), \
|
||||
user_buf, count, \
|
||||
iwl_dbgfs_##name##_write_handler, \
|
||||
NULL); \
|
||||
}
|
||||
|
||||
#define WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(name, bufsz) \
|
||||
MLD_DEBUGFS_OPEN_WRAPPER(name, bufsz, struct iwl_mld) \
|
||||
WIPHY_DEBUGFS_WRITE_WRAPPER_MLD(name) \
|
||||
static const struct file_operations iwl_dbgfs_##name##_ops = { \
|
||||
.write = __iwl_dbgfs_##name##_write, \
|
||||
.open = _iwl_dbgfs_##name##_open, \
|
||||
.llseek = generic_file_llseek, \
|
||||
.release = _iwl_dbgfs_release, \
|
||||
}
|
||||
|
||||
#define WIPHY_DEBUGFS_READ_WRITE_FILE_OPS_MLD(name, bufsz) \
|
||||
MLD_DEBUGFS_OPEN_WRAPPER(name, bufsz, struct iwl_mld) \
|
||||
WIPHY_DEBUGFS_WRITE_WRAPPER_MLD(name) \
|
||||
MLD_DEBUGFS_READ_WRAPPER(name) \
|
||||
static const struct file_operations iwl_dbgfs_##name##_ops = { \
|
||||
.write = __iwl_dbgfs_##name##_write, \
|
||||
.read = _iwl_dbgfs_##name##_read, \
|
||||
.open = _iwl_dbgfs_##name##_open, \
|
||||
.llseek = generic_file_llseek, \
|
||||
.release = _iwl_dbgfs_release, \
|
||||
}
|
||||
|
||||
#define WIPHY_DEBUGFS_WRITE_WRAPPER_IEEE80211(name, bufsz, objtype) \
|
||||
WIPHY_DEBUGFS_HANDLER_WRAPPER(name) \
|
||||
static ssize_t _iwl_dbgfs_##name##_write(struct file *file, \
|
||||
const char __user *user_buf, \
|
||||
size_t count, loff_t *ppos) \
|
||||
{ \
|
||||
struct dbgfs_##name##_data *data = file->private_data; \
|
||||
struct ieee80211_##objtype *arg = data->arg; \
|
||||
struct iwl_mld *mld = iwl_mld_from_##objtype(arg); \
|
||||
char buf[bufsz] = {}; \
|
||||
\
|
||||
return wiphy_locked_debugfs_write(mld->wiphy, file, \
|
||||
buf, sizeof(buf), \
|
||||
user_buf, count, \
|
||||
iwl_dbgfs_##name##_write_handler, \
|
||||
arg); \
|
||||
}
|
||||
|
||||
#define IEEE80211_WIPHY_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz, objtype) \
|
||||
MLD_DEBUGFS_OPEN_WRAPPER(name, bufsz, struct ieee80211_##objtype) \
|
||||
WIPHY_DEBUGFS_WRITE_WRAPPER_IEEE80211(name, bufsz, objtype) \
|
||||
MLD_DEBUGFS_READ_WRAPPER(name) \
|
||||
static const struct file_operations iwl_dbgfs_##name##_ops = { \
|
||||
.write = _iwl_dbgfs_##name##_write, \
|
||||
.read = _iwl_dbgfs_##name##_read, \
|
||||
.open = _iwl_dbgfs_##name##_open, \
|
||||
.llseek = generic_file_llseek, \
|
||||
.release = _iwl_dbgfs_release, \
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright (C) 2025 Intel Corporation
|
||||
*/
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/math64.h>
|
||||
#include <net/cfg80211.h>
|
||||
#include "mld.h"
|
||||
#include "iface.h"
|
||||
#include "phy.h"
|
||||
#include "iwl-io.h"
|
||||
#include "iwl-prph.h"
|
||||
#include "constants.h"
|
||||
#include "fw/api/location.h"
|
||||
#include "ftm-initiator.h"
|
||||
|
||||
static void iwl_mld_ftm_cmd_common(struct iwl_mld *mld,
|
||||
struct ieee80211_vif *vif,
|
||||
struct iwl_tof_range_req_cmd *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(min(req->timeout, 0xfffff));
|
||||
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->cfg.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_mld_vif *mld_vif =
|
||||
iwl_mld_vif_from_mac80211(vif);
|
||||
|
||||
cmd->tsf_mac_id = cpu_to_le32(mld_vif->fw_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 int
|
||||
iwl_mld_ftm_set_target_chandef(struct iwl_mld *mld,
|
||||
struct cfg80211_pmsr_request_peer *peer,
|
||||
struct iwl_tof_range_req_ap_entry *target)
|
||||
{
|
||||
u32 freq = peer->chandef.chan->center_freq;
|
||||
|
||||
target->channel_num = ieee80211_frequency_to_channel(freq);
|
||||
|
||||
switch (peer->chandef.width) {
|
||||
case NL80211_CHAN_WIDTH_20_NOHT:
|
||||
target->format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY;
|
||||
target->format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS;
|
||||
break;
|
||||
case NL80211_CHAN_WIDTH_20:
|
||||
target->format_bw = IWL_LOCATION_FRAME_FORMAT_HT;
|
||||
target->format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS;
|
||||
break;
|
||||
case NL80211_CHAN_WIDTH_40:
|
||||
target->format_bw = IWL_LOCATION_FRAME_FORMAT_HT;
|
||||
target->format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS;
|
||||
break;
|
||||
case NL80211_CHAN_WIDTH_80:
|
||||
target->format_bw = IWL_LOCATION_FRAME_FORMAT_VHT;
|
||||
target->format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS;
|
||||
break;
|
||||
case NL80211_CHAN_WIDTH_160:
|
||||
target->format_bw = IWL_LOCATION_FRAME_FORMAT_HE;
|
||||
target->format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS;
|
||||
break;
|
||||
default:
|
||||
IWL_ERR(mld, "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)
|
||||
target->format_bw |= IWL_LOCATION_FRAME_FORMAT_HE;
|
||||
|
||||
target->ctrl_ch_position =
|
||||
(peer->chandef.width > NL80211_CHAN_WIDTH_20) ?
|
||||
iwl_mld_get_fw_ctrl_pos(&peer->chandef) : 0;
|
||||
|
||||
target->band = iwl_mld_nl80211_band_to_fw(peer->chandef.chan->band);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define FTM_SET_FLAG(flag) (target->initiator_ap_flags |= \
|
||||
cpu_to_le32(IWL_INITIATOR_AP_FLAGS_##flag))
|
||||
|
||||
static void
|
||||
iwl_mld_ftm_set_target_flags(struct iwl_mld *mld,
|
||||
struct cfg80211_pmsr_request_peer *peer,
|
||||
struct iwl_tof_range_req_ap_entry *target)
|
||||
{
|
||||
target->initiator_ap_flags = cpu_to_le32(0);
|
||||
|
||||
if (peer->ftm.asap)
|
||||
FTM_SET_FLAG(ASAP);
|
||||
|
||||
if (peer->ftm.request_lci)
|
||||
FTM_SET_FLAG(LCI_REQUEST);
|
||||
|
||||
if (peer->ftm.request_civicloc)
|
||||
FTM_SET_FLAG(CIVIC_REQUEST);
|
||||
|
||||
if (IWL_MLD_FTM_INITIATOR_DYNACK)
|
||||
FTM_SET_FLAG(DYN_ACK);
|
||||
|
||||
if (IWL_MLD_FTM_INITIATOR_ALGO == IWL_TOF_ALGO_TYPE_LINEAR_REG)
|
||||
FTM_SET_FLAG(ALGO_LR);
|
||||
else if (IWL_MLD_FTM_INITIATOR_ALGO == IWL_TOF_ALGO_TYPE_FFT)
|
||||
FTM_SET_FLAG(ALGO_FFT);
|
||||
|
||||
if (peer->ftm.trigger_based)
|
||||
FTM_SET_FLAG(TB);
|
||||
else if (peer->ftm.non_trigger_based)
|
||||
FTM_SET_FLAG(NON_TB);
|
||||
|
||||
if ((peer->ftm.trigger_based || peer->ftm.non_trigger_based) &&
|
||||
peer->ftm.lmr_feedback)
|
||||
FTM_SET_FLAG(LMR_FEEDBACK);
|
||||
}
|
||||
|
||||
static void iwl_mld_ftm_set_sta(struct iwl_mld *mld, struct ieee80211_vif *vif,
|
||||
struct cfg80211_pmsr_request_peer *peer,
|
||||
struct iwl_tof_range_req_ap_entry *target)
|
||||
{
|
||||
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
||||
u32 sta_id_mask;
|
||||
|
||||
target->sta_id = IWL_INVALID_STA;
|
||||
|
||||
/* TODO: add ftm_unprotected debugfs support */
|
||||
|
||||
if (!vif->cfg.assoc || !mld_vif->ap_sta)
|
||||
return;
|
||||
|
||||
sta_id_mask = iwl_mld_fw_sta_id_mask(mld, mld_vif->ap_sta);
|
||||
if (WARN_ON(hweight32(sta_id_mask) != 1))
|
||||
return;
|
||||
|
||||
target->sta_id = __ffs(sta_id_mask);
|
||||
|
||||
if (mld_vif->ap_sta->mfp &&
|
||||
(peer->ftm.trigger_based || peer->ftm.non_trigger_based))
|
||||
FTM_SET_FLAG(PMF);
|
||||
}
|
||||
|
||||
static int
|
||||
iwl_mld_ftm_set_target(struct iwl_mld *mld, struct ieee80211_vif *vif,
|
||||
struct cfg80211_pmsr_request_peer *peer,
|
||||
struct iwl_tof_range_req_ap_entry *target)
|
||||
{
|
||||
u32 i2r_max_sts;
|
||||
int ret;
|
||||
|
||||
ret = iwl_mld_ftm_set_target_chandef(mld, peer, target);
|
||||
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;
|
||||
iwl_mld_ftm_set_target_flags(mld, peer, target);
|
||||
iwl_mld_ftm_set_sta(mld, vif, peer, target);
|
||||
|
||||
/* TODO: add secured ranging support */
|
||||
|
||||
i2r_max_sts = IWL_MLD_FTM_I2R_MAX_STS > 1 ? 1 :
|
||||
IWL_MLD_FTM_I2R_MAX_STS;
|
||||
|
||||
target->r2i_ndp_params = IWL_MLD_FTM_R2I_MAX_REP |
|
||||
(IWL_MLD_FTM_R2I_MAX_STS << IWL_LOCATION_MAX_STS_POS) |
|
||||
(IWL_MLD_FTM_R2I_MAX_TOTAL_LTF << IWL_LOCATION_TOTAL_LTF_POS);
|
||||
target->i2r_ndp_params = IWL_MLD_FTM_I2R_MAX_REP |
|
||||
(i2r_max_sts << IWL_LOCATION_MAX_STS_POS) |
|
||||
(IWL_MLD_FTM_I2R_MAX_TOTAL_LTF << IWL_LOCATION_TOTAL_LTF_POS);
|
||||
|
||||
if (peer->ftm.non_trigger_based) {
|
||||
target->min_time_between_msr =
|
||||
cpu_to_le16(IWL_MLD_FTM_NON_TB_MIN_TIME_BETWEEN_MSR);
|
||||
target->burst_period =
|
||||
cpu_to_le16(IWL_MLD_FTM_NON_TB_MAX_TIME_BETWEEN_MSR);
|
||||
} else {
|
||||
target->min_time_between_msr = cpu_to_le16(0);
|
||||
}
|
||||
|
||||
/* TODO: Beacon interval is currently unknown, so use the common value
|
||||
* of 100 TUs.
|
||||
*/
|
||||
target->beacon_interval = cpu_to_le16(100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iwl_mld_ftm_start(struct iwl_mld *mld, struct ieee80211_vif *vif,
|
||||
struct cfg80211_pmsr_request *req)
|
||||
{
|
||||
struct iwl_tof_range_req_cmd cmd;
|
||||
struct iwl_host_cmd hcmd = {
|
||||
.id = WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD),
|
||||
.dataflags[0] = IWL_HCMD_DFL_DUP,
|
||||
.data[0] = &cmd,
|
||||
.len[0] = sizeof(cmd),
|
||||
};
|
||||
u8 i;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_wiphy(mld->wiphy);
|
||||
|
||||
if (mld->ftm_initiator.req)
|
||||
return -EBUSY;
|
||||
|
||||
if (req->n_peers > ARRAY_SIZE(cmd.ap))
|
||||
return -EINVAL;
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
|
||||
iwl_mld_ftm_cmd_common(mld, 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 *target = &cmd.ap[i];
|
||||
|
||||
ret = iwl_mld_ftm_set_target(mld, vif, peer, target);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* TODO: get the status from the response*/
|
||||
ret = iwl_mld_send_cmd(mld, &hcmd);
|
||||
if (!ret) {
|
||||
mld->ftm_initiator.req = req;
|
||||
mld->ftm_initiator.req_wdev = ieee80211_vif_to_wdev(vif);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void iwl_mld_ftm_reset(struct iwl_mld *mld)
|
||||
{
|
||||
lockdep_assert_wiphy(mld->wiphy);
|
||||
|
||||
mld->ftm_initiator.req = NULL;
|
||||
mld->ftm_initiator.req_wdev = NULL;
|
||||
memset(mld->ftm_initiator.responses, 0,
|
||||
sizeof(mld->ftm_initiator.responses));
|
||||
}
|
||||
|
||||
static int iwl_mld_ftm_range_resp_valid(struct iwl_mld *mld, u8 request_id,
|
||||
u8 num_of_aps)
|
||||
{
|
||||
if (IWL_FW_CHECK(mld, request_id != (u8)mld->ftm_initiator.req->cookie,
|
||||
"Request ID mismatch, got %u, active %u\n",
|
||||
request_id, (u8)mld->ftm_initiator.req->cookie))
|
||||
return -EINVAL;
|
||||
|
||||
if (IWL_FW_CHECK(mld, num_of_aps > mld->ftm_initiator.req->n_peers ||
|
||||
num_of_aps > IWL_TOF_MAX_APS,
|
||||
"FTM range response: invalid num of APs (%u)\n",
|
||||
num_of_aps))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int iwl_mld_ftm_find_peer(struct cfg80211_pmsr_request *req,
|
||||
const u8 *addr)
|
||||
{
|
||||
for (int 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 void iwl_mld_debug_range_resp(struct iwl_mld *mld, u8 index,
|
||||
struct cfg80211_pmsr_result *res)
|
||||
{
|
||||
s64 rtt_avg = div_s64(res->ftm.rtt_avg * 100, 6666);
|
||||
|
||||
IWL_DEBUG_INFO(mld, "entry %d\n", index);
|
||||
IWL_DEBUG_INFO(mld, "\tstatus: %d\n", res->status);
|
||||
IWL_DEBUG_INFO(mld, "\tBSSID: %pM\n", res->addr);
|
||||
IWL_DEBUG_INFO(mld, "\thost time: %llu\n", res->host_time);
|
||||
IWL_DEBUG_INFO(mld, "\tburst index: %d\n", res->ftm.burst_index);
|
||||
IWL_DEBUG_INFO(mld, "\tsuccess num: %u\n", res->ftm.num_ftmr_successes);
|
||||
IWL_DEBUG_INFO(mld, "\trssi: %d\n", res->ftm.rssi_avg);
|
||||
IWL_DEBUG_INFO(mld, "\trssi spread: %d\n", res->ftm.rssi_spread);
|
||||
IWL_DEBUG_INFO(mld, "\trtt: %lld\n", res->ftm.rtt_avg);
|
||||
IWL_DEBUG_INFO(mld, "\trtt var: %llu\n", res->ftm.rtt_variance);
|
||||
IWL_DEBUG_INFO(mld, "\trtt spread: %llu\n", res->ftm.rtt_spread);
|
||||
IWL_DEBUG_INFO(mld, "\tdistance: %lld\n", rtt_avg);
|
||||
}
|
||||
|
||||
void iwl_mld_handle_ftm_resp_notif(struct iwl_mld *mld,
|
||||
struct iwl_rx_packet *pkt)
|
||||
{
|
||||
struct iwl_tof_range_rsp_ntfy *fw_resp = (void *)pkt->data;
|
||||
u8 num_of_aps, last_in_batch;
|
||||
|
||||
if (IWL_FW_CHECK(mld, !mld->ftm_initiator.req,
|
||||
"FTM response without a pending request\n"))
|
||||
return;
|
||||
|
||||
if (iwl_mld_ftm_range_resp_valid(mld, fw_resp->request_id,
|
||||
fw_resp->num_of_aps))
|
||||
return;
|
||||
|
||||
num_of_aps = fw_resp->num_of_aps;
|
||||
last_in_batch = fw_resp->last_report;
|
||||
|
||||
IWL_DEBUG_INFO(mld, "Range response received\n");
|
||||
IWL_DEBUG_INFO(mld, "request id: %llu, num of entries: %u\n",
|
||||
mld->ftm_initiator.req->cookie, num_of_aps);
|
||||
|
||||
for (int i = 0; i < num_of_aps; i++) {
|
||||
struct cfg80211_pmsr_result result = {};
|
||||
struct iwl_tof_range_rsp_ap_entry_ntfy *fw_ap;
|
||||
int peer_idx;
|
||||
|
||||
fw_ap = &fw_resp->ap[i];
|
||||
result.final = fw_ap->last_burst;
|
||||
result.ap_tsf = le32_to_cpu(fw_ap->start_tsf);
|
||||
result.ap_tsf_valid = 1;
|
||||
|
||||
peer_idx = iwl_mld_ftm_find_peer(mld->ftm_initiator.req,
|
||||
fw_ap->bssid);
|
||||
if (peer_idx < 0) {
|
||||
IWL_WARN(mld,
|
||||
"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);
|
||||
|
||||
/* TODO: convert the timestamp from the result to systime */
|
||||
result.host_time = ktime_get_boottime_ns();
|
||||
|
||||
result.type = NL80211_PMSR_TYPE_FTM;
|
||||
result.ftm.burst_index = mld->ftm_initiator.responses[peer_idx];
|
||||
mld->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;
|
||||
|
||||
cfg80211_pmsr_report(mld->ftm_initiator.req_wdev,
|
||||
mld->ftm_initiator.req,
|
||||
&result, GFP_KERNEL);
|
||||
|
||||
if (fw_has_api(&mld->fw->ucode_capa,
|
||||
IWL_UCODE_TLV_API_FTM_RTT_ACCURACY))
|
||||
IWL_DEBUG_INFO(mld, "RTT confidence: %u\n",
|
||||
fw_ap->rttConfidence);
|
||||
|
||||
iwl_mld_debug_range_resp(mld, i, &result);
|
||||
}
|
||||
|
||||
if (last_in_batch) {
|
||||
cfg80211_pmsr_complete(mld->ftm_initiator.req_wdev,
|
||||
mld->ftm_initiator.req,
|
||||
GFP_KERNEL);
|
||||
iwl_mld_ftm_reset(mld);
|
||||
}
|
||||
}
|
||||
|
||||
void iwl_mld_ftm_restart_cleanup(struct iwl_mld *mld)
|
||||
{
|
||||
struct cfg80211_pmsr_result result = {
|
||||
.status = NL80211_PMSR_STATUS_FAILURE,
|
||||
.final = 1,
|
||||
.host_time = ktime_get_boottime_ns(),
|
||||
.type = NL80211_PMSR_TYPE_FTM,
|
||||
};
|
||||
|
||||
if (!mld->ftm_initiator.req)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < mld->ftm_initiator.req->n_peers; i++) {
|
||||
memcpy(result.addr, mld->ftm_initiator.req->peers[i].addr,
|
||||
ETH_ALEN);
|
||||
|
||||
cfg80211_pmsr_report(mld->ftm_initiator.req_wdev,
|
||||
mld->ftm_initiator.req,
|
||||
&result, GFP_KERNEL);
|
||||
}
|
||||
|
||||
cfg80211_pmsr_complete(mld->ftm_initiator.req_wdev,
|
||||
mld->ftm_initiator.req, GFP_KERNEL);
|
||||
iwl_mld_ftm_reset(mld);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
|
||||
/*
|
||||
* Copyright (C) 2025 Intel Corporation
|
||||
*/
|
||||
#ifndef __iwl_mld_ftm_initiator_h__
|
||||
#define __iwl_mld_ftm_initiator_h__
|
||||
|
||||
int iwl_mld_ftm_start(struct iwl_mld *mld, struct ieee80211_vif *vif,
|
||||
struct cfg80211_pmsr_request *req);
|
||||
|
||||
void iwl_mld_handle_ftm_resp_notif(struct iwl_mld *mld,
|
||||
struct iwl_rx_packet *pkt);
|
||||
void iwl_mld_ftm_restart_cleanup(struct iwl_mld *mld);
|
||||
|
||||
#endif /* __iwl_mld_ftm_initiator_h__ */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user