Mostly driver updates:

- iwlwifi
    - more UHR support
    - NAN (multicast, schedule improvements, multi-station)
    - cleanups, etc.
  - ath12k
    - thermal throttling/cooling device support
    - 6 GHz incumbent interference detection
    - channel 177 in 5 GHz
  - hwsim: S1G fixes
  - mac80211: NAN channel handling improvements
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEpeA8sTs3M8SN2hR410qiO8sPaAAFAmoYNp4ACgkQ10qiO8sP
 aACvLA//ULTpJxcURZgcIqcY54i8GoHZl0NaiaPpqnQ1OpvDoUsLwst54AyZjclq
 aYj6G10gtPh4gYC/DgftPGNw5KWcdg7zZzqX1Zzg1ZKeJ/m8cW/2wnwf1IrSzx3g
 tFRkpDBATxV2Qe9F0s9rLMauir6dimhkNe9UfaTX7s2tIM/agIrZyv1kDAZqI8SF
 crH7rek2iaOgnAU17bwLFH8NZ5OtB5zzqSxXVMt7FxBGgps5jOX+g0KS3+5heMCZ
 bZiEscfBbJ756j34iFYmX08mtCV5bfAWAsCA8rr5Qr8FQfBLJScgSm0iLHULoDxX
 sO4fo1Y9YoKqAnxyDIM+UdfqphQokwTmkxp1camZGVyOLqalZ+Lq2ud6oZnOLw98
 ZsfG+8Z2kE9pffwQJQjr6EMT8bmnBxIlZhNmGURZe3s+BCgZMknjGQ6uGk3VpHnZ
 IzzX5Wcwn1pkvDo2QWbokbf1mDCO6VXe+aBwKS6erEvokhWcMq3MjvL1Vbw/1veu
 zFTt1+NY8+B2lgQ7NPJHnBv45ayEhxQob+1AveBUCyFjpNygB0+8uFIwr/KJoOJt
 ocOQaOEJw2XYYPf1Ij+wd0FTRclVsYnNkHrmecsMliHvzj73p0uCNEDrjBZrQBzW
 bE8F0d4P84mJgwZ1GCm8I10QDr8bS+BMhLI5ZwVN4OqnXbZE6e4=
 =F3sA
 -----END PGP SIGNATURE-----

Merge tag 'wireless-next-2026-05-28' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next

Johannes Berg says:

====================
Mostly driver updates:
 - iwlwifi
   - more UHR support
   - NAN (multicast, schedule improvements, multi-station)
   - cleanups, etc.
 - ath12k
   - thermal throttling/cooling device support
   - 6 GHz incumbent interference detection
   - channel 177 in 5 GHz
 - hwsim: S1G fixes
 - mac80211: NAN channel handling improvements

* tag 'wireless-next-2026-05-28' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next: (143 commits)
  wifi: cfg80211: use strscpy in cfg80211_wext_giwname
  wifi: mac80211: fix channel evacuation logic
  wifi: mac80211: refactor ieee80211_nan_try_evacuate
  wifi: mac80211: add an option to filter out a channel in combinations check
  wifi: mac80211_hwsim: add debug messages for link changes
  wifi: nl80211: re-check wiphy netns in testmode and vendor dump continuations
  wifi: mac80211_hwsim: modernise S1G channel list
  wifi: mac80211_hwsim: don't run RC update on new STA on S1G vif
  wifi: mwifiex: remove an unnecessary check
  wifi: mac80211: add KUnit coverage for negotiated TTLM parser
  wifi: ath12k: fix error unwind on arch_init() failure in PCI probe
  wifi: iwlwifi: mld: fix indentation in iwl_mld_fill_supp_rates()
  wifi: iwlwifi: transport: add memory read under NIC access
  wifi: iwlwifi: dbg: remove unused 'range_len' arg from dump
  wifi: iwlwifi: fw: separate out old-style dump code
  wifi: iwlwifi: fw: dbg: always use non-tracing PRPH access
  wifi: iwlwifi: fw: separate ini dump allocation
  wifi: iwlwifi: fw: move struct iwl_fw_ini_dump_entry to dbg.c
  wifi: iwlwifi: clean up location format/BW encoding
  wifi: iwlwifi: Add names for Killer BE1735x and BE1730x
  ...
====================

Link: https://patch.msgid.link/20260528123911.284536-26-johannes@sipsolutions.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-05-28 17:05:23 -07:00
commit e7d6bd24e8
129 changed files with 7083 additions and 2559 deletions

View File

@ -1269,7 +1269,6 @@ struct ath10k {
} testmode;
struct {
struct gpio_led wifi_led;
struct led_classdev cdev;
char label[48];
u32 gpio_state_pin;

View File

@ -1353,7 +1353,7 @@ static int ath10k_htt_tx_hl(struct ath10k_htt *htt, enum ath10k_hw_txrx_mode txm
msdu_id = res;
}
/* As msdu is freed by mac80211 (in ieee80211_tx_status()) and by
/* As msdu is freed by mac80211 (in ieee80211_tx_status_skb()) and by
* ath10k (in ath10k_htt_htc_tx_complete()) we have to increase
* reference by one to avoid a use-after-free case and a double
* free.

View File

@ -19,15 +19,13 @@ static int ath10k_leds_set_brightness_blocking(struct led_classdev *led_cdev,
{
struct ath10k *ar = container_of(led_cdev, struct ath10k,
leds.cdev);
struct gpio_led *led = &ar->leds.wifi_led;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH10K_STATE_ON)
goto out;
ar->leds.gpio_state_pin = (brightness != LED_OFF) ^ led->active_low;
ath10k_wmi_gpio_output(ar, ar->hw_params.led_pin, ar->leds.gpio_state_pin);
ath10k_wmi_gpio_output(ar, ar->hw_params.led_pin, brightness == LED_OFF);
out:
mutex_unlock(&ar->conf_mutex);
@ -63,13 +61,9 @@ int ath10k_leds_register(struct ath10k *ar)
snprintf(ar->leds.label, sizeof(ar->leds.label), "ath10k-%s",
wiphy_name(ar->hw->wiphy));
ar->leds.wifi_led.active_low = 1;
ar->leds.wifi_led.name = ar->leds.label;
ar->leds.wifi_led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
ar->leds.cdev.name = ar->leds.label;
ar->leds.cdev.brightness_set_blocking = ath10k_leds_set_brightness_blocking;
ar->leds.cdev.default_trigger = ar->leds.wifi_led.default_trigger;
ret = led_classdev_register(wiphy_dev(ar->hw->wiphy), &ar->leds.cdev);
if (ret)

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2018 The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/completion.h>
@ -1112,7 +1112,7 @@ int ath10k_qmi_init(struct ath10k *ar, u32 msa_size)
spin_lock_init(&qmi->event_lock);
INIT_WORK(&qmi->event_work, ath10k_qmi_driver_event_work);
ret = qmi_add_lookup(&qmi->qmi_hdl, WLFW_SERVICE_ID_V01,
ret = qmi_add_lookup(&qmi->qmi_hdl, QMI_SERVICE_ID_WLFW,
WLFW_SERVICE_VERS_V01, 0);
if (ret)
goto err_qmi_lookup;

View File

@ -7,7 +7,6 @@
#ifndef WCN3990_QMI_SVC_V01_H
#define WCN3990_QMI_SVC_V01_H
#define WLFW_SERVICE_ID_V01 0x45
#define WLFW_SERVICE_VERS_V01 0x01
#define QMI_WLFW_BDF_DOWNLOAD_REQ_V01 0x0025

View File

@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-3-Clause-Clear
config ATH11K
tristate "Qualcomm Technologies 802.11ax chipset support"
tristate "Qualcomm 802.11ax chipset support"
depends on MAC80211 && HAS_DMA
select ATH_COMMON
select QCOM_QMI_HELPERS

View File

@ -1040,6 +1040,7 @@ void ath11k_dp_free(struct ath11k_base *ab)
idr_destroy(&dp->tx_ring[i].txbuf_idr);
spin_unlock_bh(&dp->tx_ring[i].tx_idr_lock);
kfree(dp->tx_ring[i].tx_status);
dp->tx_ring[i].tx_status = NULL;
}
/* Deinit any SOC level resource */

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2020 The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/msi.h>
@ -282,8 +282,10 @@ static void ath11k_mhi_op_status_cb(struct mhi_controller *mhi_cntrl,
break;
}
spin_lock_bh(&ab->base_lock);
if (!(test_bit(ATH11K_FLAG_UNREGISTERING, &ab->dev_flags)))
queue_work(ab->workqueue_aux, &ab->reset_work);
spin_unlock_bh(&ab->base_lock);
break;
default:

View File

@ -1210,6 +1210,14 @@ static void ath11k_pci_shutdown(struct pci_dev *pdev)
struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
ath11k_pci_set_irq_affinity_hint(ab_pci, NULL);
spin_lock_bh(&ab->base_lock);
set_bit(ATH11K_FLAG_UNREGISTERING, &ab->dev_flags);
spin_unlock_bh(&ab->base_lock);
cancel_work_sync(&ab->reset_work);
cancel_work_sync(&ab->dump_work);
ath11k_pci_power_down(ab, false);
}

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
@ -3337,7 +3336,7 @@ int ath11k_qmi_init_service(struct ath11k_base *ab)
spin_lock_init(&ab->qmi.event_lock);
INIT_WORK(&ab->qmi.event_work, ath11k_qmi_driver_event_work);
ret = qmi_add_lookup(&ab->qmi.handle, ATH11K_QMI_WLFW_SERVICE_ID_V01,
ret = qmi_add_lookup(&ab->qmi.handle, QMI_SERVICE_ID_WLFW,
ATH11K_QMI_WLFW_SERVICE_VERS_V01,
ab->qmi.service_ins_id);
if (ret < 0) {

View File

@ -15,7 +15,6 @@
#define ATH11K_QMI_MAX_BDF_FILE_NAME_SIZE 64
#define ATH11K_QMI_CALDB_ADDRESS 0x4BA00000
#define ATH11K_QMI_WLANFW_MAX_BUILD_ID_LEN_V01 128
#define ATH11K_QMI_WLFW_SERVICE_ID_V01 0x45
#define ATH11K_QMI_WLFW_SERVICE_VERS_V01 0x01
#define ATH11K_QMI_WLFW_SERVICE_INS_ID_V01 0x02
#define ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_QCA6390 0x01

View File

@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-3-Clause-Clear
config ATH12K
tristate "Qualcomm Technologies Wi-Fi 7 support (ath12k)"
tristate "Qualcomm Wi-Fi 7 support (ath12k)"
depends on MAC80211 && HAS_DMA && PCI
select QCOM_QMI_HELPERS
select MHI_BUS
@ -15,7 +15,7 @@ config ATH12K
If you choose to build a module, it'll be called ath12k.
config ATH12K_AHB
bool "QTI ath12k AHB support"
bool "Qualcomm ath12k AHB support"
depends on ATH12K && REMOTEPROC
select QCOM_MDT_LOADER
select QCOM_SCM
@ -33,7 +33,7 @@ config ATH12K_DEBUG
you want optimal performance choose N.
config ATH12K_DEBUGFS
bool "QTI ath12k debugfs support"
bool "Qualcomm ath12k debugfs support"
depends on ATH12K && MAC80211_DEBUGFS
help
Enable ath12k debugfs support

View File

@ -1006,6 +1006,27 @@ static void ath12k_core_device_cleanup(struct ath12k_base *ab)
mutex_unlock(&ab->core_lock);
}
static int ath12k_core_device_setup(struct ath12k_base *ab)
{
int ret;
guard(mutex)(&ab->core_lock);
ret = ath12k_core_pdev_create(ab);
if (ret) {
ath12k_err(ab, "failed to create pdev core %d\n", ret);
return ret;
}
ath12k_hif_irq_enable(ab);
ret = ath12k_core_rfkill_config(ab);
if (ret && ret != -EOPNOTSUPP)
return ret;
return 0;
}
static void ath12k_core_hw_group_stop(struct ath12k_hw_group *ag)
{
struct ath12k_base *ab;
@ -1015,10 +1036,6 @@ static void ath12k_core_hw_group_stop(struct ath12k_hw_group *ag)
clear_bit(ATH12K_GROUP_FLAG_REGISTERED, &ag->flags);
ath12k_mac_unregister(ag);
ath12k_mac_mlo_teardown(ag);
for (i = ag->num_devices - 1; i >= 0; i--) {
ab = ag->ab[i];
if (!ab)
@ -1029,6 +1046,12 @@ static void ath12k_core_hw_group_stop(struct ath12k_hw_group *ag)
ath12k_core_device_cleanup(ab);
}
/* Unregister MAC (drops wiphys) only after per-device cleanup */
ath12k_mac_unregister(ag);
/* Teardown MLO state after MAC unregister for symmetry */
ath12k_mac_mlo_teardown(ag);
ath12k_mac_destroy(ag);
}
@ -1165,26 +1188,11 @@ static int ath12k_core_hw_group_start(struct ath12k_hw_group *ag)
if (!ab)
continue;
mutex_lock(&ab->core_lock);
set_bit(ATH12K_FLAG_REGISTERED, &ab->dev_flags);
ret = ath12k_core_pdev_create(ab);
if (ret) {
ath12k_err(ab, "failed to create pdev core %d\n", ret);
mutex_unlock(&ab->core_lock);
ret = ath12k_core_device_setup(ab);
if (ret)
goto err;
}
ath12k_hif_irq_enable(ab);
ret = ath12k_core_rfkill_config(ab);
if (ret && ret != -EOPNOTSUPP) {
mutex_unlock(&ab->core_lock);
goto err;
}
mutex_unlock(&ab->core_lock);
}
return 0;

View File

@ -542,8 +542,8 @@ struct ath12k_sta {
#define ATH12K_MAX_5GHZ_FREQ (ATH12K_5GHZ_MAX_CENTER + ATH12K_HALF_20MHZ_BW)
#define ATH12K_MIN_6GHZ_FREQ (ATH12K_6GHZ_MIN_CENTER - ATH12K_HALF_20MHZ_BW)
#define ATH12K_MAX_6GHZ_FREQ (ATH12K_6GHZ_MAX_CENTER + ATH12K_HALF_20MHZ_BW)
#define ATH12K_NUM_CHANS 101
#define ATH12K_MAX_5GHZ_CHAN 173
#define ATH12K_NUM_CHANS 102
#define ATH12K_MAX_5GHZ_CHAN 177
static inline bool ath12k_is_2ghz_channel_freq(u32 freq)
{
@ -763,6 +763,14 @@ struct ath12k {
struct ath12k_pdev_rssi_offsets rssi_info;
struct ath12k_thermal thermal;
/* Protected by ar->data_lock */
struct ath12k_incumbent_signal_interference {
u32 center_freq;
enum nl80211_chan_width width;
u32 chan_bw_interference_bitmap;
bool handling_in_progress;
} incumbent_signal_interference;
};
struct ath12k_hw {

View File

@ -1450,6 +1450,44 @@ static const struct file_operations fops_pdev_stats = {
.llseek = default_llseek,
};
static ssize_t
ath12k_write_simulate_incumbent_signal_interference(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath12k *ar = file->private_data;
struct ath12k_hw *ah = ath12k_ar_to_ah(ar);
struct wiphy *wiphy = ath12k_ar_to_hw(ar)->wiphy;
u32 chan_bw_interference_bitmap;
int ret;
if (ah->state != ATH12K_HW_STATE_ON)
return -ENETDOWN;
/*
* Bitmap uses the firmware primary-based ordering documented in
* ath12k_wmi_transform_interference_bitmap() & intf_map_80.
*/
if (kstrtou32_from_user(user_buf, count, 0, &chan_bw_interference_bitmap))
return -EINVAL;
wiphy_lock(wiphy);
ret = ath12k_wmi_simulate_incumbent_signal_interference(ar, chan_bw_interference_bitmap);
if (ret)
goto exit;
ret = count;
exit:
wiphy_unlock(wiphy);
return ret;
}
static const struct file_operations fops_simulate_incumbent_signal_interference = {
.write = ath12k_write_simulate_incumbent_signal_interference,
.open = simple_open,
};
static
void ath12k_debugfs_fw_stats_register(struct ath12k *ar)
{
@ -1515,6 +1553,14 @@ void ath12k_debugfs_register(struct ath12k *ar)
ar, &fops_tpc_stats_type);
init_completion(&ar->debug.tpc_complete);
if (ar->mac.sbands[NL80211_BAND_6GHZ].channels &&
test_bit(WMI_TLV_SERVICE_DCS_INCUMBENT_SIGNAL_INTERFERENCE_SUPPORT,
ar->ab->wmi_ab.svc_map)) {
debugfs_create_file("simulate_incumbent_signal_interference", 0200,
ar->debug.debugfs_pdev, ar,
&fops_simulate_incumbent_signal_interference);
}
ath12k_debugfs_htt_stats_register(ar);
ath12k_debugfs_fw_stats_register(ar);

View File

@ -17,6 +17,11 @@
#include "dp_mon.h"
#include "debugfs_htt_stats.h"
#define ATH12K_2GHZ_MIN_CHAN_NUM 1
#define ATH12K_2GHZ_MAX_CHAN_NUM 14
#define ATH12K_5GHZ_MIN_CHAN_NUM 36
#define ATH12K_5GHZ_MAX_CHAN_NUM 177
static int ath12k_dp_rx_tid_delete_handler(struct ath12k_base *ab,
struct ath12k_dp_rx_tid_rxq *rx_tid);
@ -1095,7 +1100,8 @@ static void ath12k_get_dot11_hdr_from_rx_desc(struct ath12k_pdev_dp *dp_pdev,
static void ath12k_dp_rx_h_undecap_eth(struct ath12k_pdev_dp *dp_pdev,
struct sk_buff *msdu,
enum hal_encrypt_type enctype,
struct hal_rx_desc_data *rx_info)
struct hal_rx_desc_data *rx_info,
enum ath12k_dp_rx_decap_type decap_type)
{
struct ieee80211_hdr *hdr;
struct ethhdr *eth;
@ -1103,12 +1109,24 @@ static void ath12k_dp_rx_h_undecap_eth(struct ath12k_pdev_dp *dp_pdev,
u8 sa[ETH_ALEN];
struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu);
struct ath12k_dp_rx_rfc1042_hdr rfc = {0xaa, 0xaa, 0x03, {0x00, 0x00, 0x00}};
struct ath12k_dp_rx_rfc1042_hdr *llc;
eth = (struct ethhdr *)msdu->data;
ether_addr_copy(da, eth->h_dest);
ether_addr_copy(sa, eth->h_source);
rfc.snap_type = eth->h_proto;
skb_pull(msdu, sizeof(*eth));
if (decap_type == DP_RX_DECAP_TYPE_8023) {
/*
* For 802.3 frames, eth->h_proto carries a length field, not
* an EtherType. The actual EtherType is in the LLC/SNAP header
* that follows the Ethernet header.
*/
llc = (struct ath12k_dp_rx_rfc1042_hdr *)(msdu->data + sizeof(*eth));
rfc.snap_type = llc->snap_type;
skb_pull(msdu, sizeof(*eth) + sizeof(*llc));
} else {
rfc.snap_type = eth->h_proto;
skb_pull(msdu, sizeof(*eth));
}
memcpy(skb_push(msdu, sizeof(rfc)), &rfc,
sizeof(rfc));
ath12k_get_dot11_hdr_from_rx_desc(dp_pdev, msdu, rxcb, enctype, rx_info);
@ -1126,9 +1144,10 @@ void ath12k_dp_rx_h_undecap(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *msdu
bool decrypted,
struct hal_rx_desc_data *rx_info)
{
enum ath12k_dp_rx_decap_type decap_type = rx_info->decap_type;
struct ethhdr *ehdr;
switch (rx_info->decap_type) {
switch (decap_type) {
case DP_RX_DECAP_TYPE_NATIVE_WIFI:
ath12k_dp_rx_h_undecap_nwifi(dp_pdev, msdu, enctype, rx_info);
break;
@ -1142,19 +1161,33 @@ void ath12k_dp_rx_h_undecap(struct ath12k_pdev_dp *dp_pdev, struct sk_buff *msdu
/* mac80211 allows fast path only for authorized STA */
if (ehdr->h_proto == cpu_to_be16(ETH_P_PAE)) {
ATH12K_SKB_RXCB(msdu)->is_eapol = true;
ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info);
ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info,
decap_type);
break;
}
/* PN for mcast packets will be validated in mac80211;
* remove eth header and add 802.11 header.
*/
if (ATH12K_SKB_RXCB(msdu)->is_mcbc && decrypted)
ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info);
if (ATH12K_SKB_RXCB(msdu)->is_mcbc && decrypted) {
ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info,
decap_type);
break;
}
rx_info->rx_status->flag |= RX_FLAG_8023;
break;
case DP_RX_DECAP_TYPE_8023:
/* TODO: Handle undecap for these formats */
break;
/*
* Note that ethernet decap format indicates that the decapped
* packet is either Ethernet 2 (DIX) or 802.3 (uses SNAP/LLC).
*/
if (ATH12K_SKB_RXCB(msdu)->is_mcbc && decrypted) {
ath12k_dp_rx_h_undecap_eth(dp_pdev, msdu, enctype, rx_info,
decap_type);
break;
}
rx_info->rx_status->flag |= RX_FLAG_8023;
}
}
EXPORT_SYMBOL(ath12k_dp_rx_h_undecap);
@ -1289,9 +1322,11 @@ void ath12k_dp_rx_h_ppdu(struct ath12k_pdev_dp *dp_pdev,
center_freq <= ATH12K_MAX_6GHZ_FREQ) {
rx_status->band = NL80211_BAND_6GHZ;
rx_status->freq = center_freq;
} else if (channel_num >= 1 && channel_num <= 14) {
} else if (channel_num >= ATH12K_2GHZ_MIN_CHAN_NUM &&
channel_num <= ATH12K_2GHZ_MAX_CHAN_NUM) {
rx_status->band = NL80211_BAND_2GHZ;
} else if (channel_num >= 36 && channel_num <= 173) {
} else if (channel_num >= ATH12K_5GHZ_MIN_CHAN_NUM &&
channel_num <= ATH12K_5GHZ_MAX_CHAN_NUM) {
rx_status->band = NL80211_BAND_5GHZ;
}
@ -1336,9 +1371,7 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
struct ath12k_dp_peer *peer;
struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu);
struct ieee80211_rx_status *status = rx_info->rx_status;
u8 decap = rx_info->decap_type;
bool is_mcbc = rxcb->is_mcbc;
bool is_eapol = rxcb->is_eapol;
peer = ath12k_dp_peer_find_by_peerid(dp_pdev, rxcb->peer_id);
@ -1383,15 +1416,6 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
/* TODO: trace rx packet */
/* PN for multicast packets are not validate in HW,
* so skip 802.3 rx path
* Also, fast_rx expects the STA to be authorized, hence
* eapol packets are sent in slow path.
*/
if (decap == DP_RX_DECAP_TYPE_ETHERNET2_DIX && !is_eapol &&
!(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED))
rx_status->flag |= RX_FLAG_8023;
ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi);
}
EXPORT_SYMBOL(ath12k_dp_rx_deliver_msdu);

View File

@ -51,6 +51,9 @@
.max_power = 30, \
}
#define ATH12K_5_9_GHZ_MIN_FREQ 5845
#define ATH12K_5_9_GHZ_MAX_FREQ 5885
static const struct ieee80211_channel ath12k_2ghz_channels[] = {
CHAN2G(1, 2412, 0),
CHAN2G(2, 2417, 0),
@ -96,6 +99,7 @@ static const struct ieee80211_channel ath12k_5ghz_channels[] = {
CHAN5G(165, 5825, 0),
CHAN5G(169, 5845, 0),
CHAN5G(173, 5865, 0),
CHAN5G(177, 5885, 0),
};
static const struct ieee80211_channel ath12k_6ghz_channels[] = {
@ -5615,12 +5619,14 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
if (ret)
goto exit;
arg = kzalloc_obj(*arg);
arg = kzalloc_flex(*arg, chan_list, n_channels);
if (!arg) {
ret = -ENOMEM;
goto exit;
}
arg->num_chan = n_channels;
ath12k_wmi_start_scan_init(ar, arg);
arg->vdev_id = arvif->vdev_id;
arg->scan_id = ATH12K_SCAN_ID;
@ -5642,18 +5648,8 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
arg->scan_f_passive = 1;
}
if (n_channels) {
arg->num_chan = n_channels;
arg->chan_list = kcalloc(arg->num_chan, sizeof(*arg->chan_list),
GFP_KERNEL);
if (!arg->chan_list) {
ret = -ENOMEM;
goto exit;
}
for (i = 0; i < arg->num_chan; i++)
arg->chan_list[i] = chan_list[i]->center_freq;
}
for (i = 0; i < arg->num_chan; i++)
arg->chan_list[i] = chan_list[i]->center_freq;
ret = ath12k_start_scan(ar, arg);
if (ret) {
@ -5678,7 +5674,6 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
exit:
if (arg) {
kfree(arg->chan_list);
kfree(arg->extraie.ptr);
kfree(arg);
}
@ -9650,6 +9645,10 @@ static int ath12k_mac_start(struct ath12k *ar)
ar->allocated_vdev_map = 0;
ar->chan_tx_pwr = ATH12K_PDEV_TX_POWER_INVALID;
spin_lock_bh(&ar->data_lock);
ar->incumbent_signal_interference.handling_in_progress = false;
spin_unlock_bh(&ar->data_lock);
/* Configure monitor status ring with default rx_filter to get rx status
* such as rssi, rx_duration.
*/
@ -9677,6 +9676,12 @@ static int ath12k_mac_start(struct ath12k *ar)
}
}
ret = ath12k_thermal_throttling_config_default(ar);
if (ret) {
ath12k_err(ab, "failed to set thermal throttle: %d\n", ret);
goto err;
}
rcu_assign_pointer(ab->pdevs_active[ar->pdev_idx],
&ab->pdevs[ar->pdev_idx]);
@ -9857,6 +9862,10 @@ static void ath12k_mac_stop(struct ath12k *ar)
synchronize_rcu();
atomic_set(&ar->num_pending_mgmt_tx, 0);
spin_lock_bh(&ar->data_lock);
ar->incumbent_signal_interference.handling_in_progress = false;
spin_unlock_bh(&ar->data_lock);
}
void ath12k_mac_op_stop(struct ieee80211_hw *hw, bool suspend)
@ -11443,8 +11452,10 @@ ath12k_mac_update_vif_chan(struct ath12k *ar,
struct ieee80211_vif_chanctx_switch *vifs,
int n_vifs)
{
struct ath12k_incumbent_signal_interference *incumbent;
struct ath12k_wmi_vdev_up_params params = {};
struct ieee80211_bss_conf *link_conf;
struct cfg80211_chan_def *chandef;
struct ath12k_base *ab = ar->ab;
struct ath12k_link_vif *arvif;
struct ieee80211_vif *vif;
@ -11556,6 +11567,42 @@ ath12k_mac_update_vif_chan(struct ath12k *ar,
if (!ath12k_mac_monitor_stop(ar))
ath12k_mac_monitor_start(ar);
}
incumbent = &ar->incumbent_signal_interference;
spin_lock_bh(&ar->data_lock);
if (incumbent->handling_in_progress) {
chandef = &vifs[0].new_ctx->def;
if (incumbent->chan_bw_interference_bitmap &
ATH12K_WMI_DCS_SEG_PRI20) {
if (incumbent->center_freq !=
chandef->chan->center_freq) {
incumbent->chan_bw_interference_bitmap = 0;
incumbent->handling_in_progress = false;
ath12k_dbg(ab, ATH12K_DBG_MAC,
"incumbent signal interference chan switch completed\n");
} else {
ath12k_warn(ab,
"incumbent signal interference chan switch not done, freq %u\n",
incumbent->center_freq);
}
} else {
if (incumbent->center_freq !=
chandef->chan->center_freq ||
incumbent->width != chandef->width) {
incumbent->chan_bw_interference_bitmap = 0;
incumbent->handling_in_progress = false;
ath12k_dbg(ab, ATH12K_DBG_MAC,
"Bandwidth/channel change due to incumbent signal interference completed\n");
} else {
ath12k_warn(ab, "Bandwidth/channel change due to incumbent sig intf not done intf_freq %u chan_freq %u intf_width %u chan_width %u\n",
incumbent->center_freq,
chandef->chan->center_freq,
incumbent->width,
chandef->width);
}
}
}
spin_unlock_bh(&ar->data_lock);
}
static void
@ -13739,19 +13786,13 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
scan_time_msec = hw->wiphy->max_remain_on_channel_duration * 2;
struct ath12k_wmi_scan_req_arg *arg __free(kfree) =
kzalloc_obj(*arg);
kzalloc_flex(*arg, chan_list, 1);
if (!arg)
return -ENOMEM;
ath12k_wmi_start_scan_init(ar, arg);
arg->num_chan = 1;
ath12k_wmi_start_scan_init(ar, arg);
u32 *chan_list __free(kfree) = kcalloc(arg->num_chan, sizeof(*chan_list),
GFP_KERNEL);
if (!chan_list)
return -ENOMEM;
arg->chan_list = chan_list;
arg->vdev_id = arvif->vdev_id;
arg->scan_id = ATH12K_SCAN_ID;
arg->chan_list[0] = chan->center_freq;
@ -13904,6 +13945,26 @@ static int ath12k_mac_update_band(struct ath12k *ar,
return 0;
}
static void ath12k_mac_update_5_9_ghz_ch_list(struct ath12k *ar,
struct ieee80211_supported_band *band)
{
int i;
if (test_bit(WMI_TLV_SERVICE_5_9GHZ_SUPPORT,
ar->ab->wmi_ab.svc_map))
return;
guard(spinlock_bh)(&ar->ab->base_lock);
if (ar->ab->dfs_region != ATH12K_DFS_REG_FCC)
return;
for (i = 0; i < band->n_channels; i++) {
if (band->channels[i].center_freq >= ATH12K_5_9_GHZ_MIN_FREQ &&
band->channels[i].center_freq <= ATH12K_5_9_GHZ_MAX_FREQ)
band->channels[i].flags |= IEEE80211_CHAN_DISABLED;
}
}
static int ath12k_mac_setup_channels_rates(struct ath12k *ar,
u32 supported_bands,
struct ieee80211_supported_band *bands[])
@ -14037,6 +14098,8 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar,
band->n_bitrates = ath12k_a_rates_size;
band->bitrates = ath12k_a_rates;
ath12k_mac_update_5_9_ghz_ch_list(ar, band);
if (ab->hw_params->single_pdev_only) {
phy_id = ath12k_get_phy_id(ar, WMI_HOST_WLAN_5GHZ_CAP);
reg_cap = &ab->hal_reg_cap[phy_id];
@ -14465,6 +14528,8 @@ static int ath12k_mac_setup_register(struct ath12k *ar,
ar->rssi_info.temp_offset = 0;
ar->rssi_info.noise_floor = ar->rssi_info.min_nf_dbm + ar->rssi_info.temp_offset;
ath12k_thermal_init_configs(ar);
return 0;
}
@ -14817,6 +14882,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
init_completion(&ar->completed_11d_scan);
init_completion(&ar->regd_update_completed);
init_completion(&ar->thermal.wmi_sync);
mutex_init(&ar->thermal.lock);
ar->thermal.temperature = 0;
ar->thermal.hwmon_dev = NULL;

View File

@ -1639,7 +1639,7 @@ static int ath12k_pci_probe(struct pci_dev *pdev,
ret = ab_pci->device_family_ops->arch_init(ab);
if (ret) {
ath12k_err(ab, "PCI arch_init failed %d\n", ret);
goto err_pci_msi_free;
goto err_free_irq;
}
ret = ath12k_core_init(ab);

View File

@ -4061,7 +4061,7 @@ int ath12k_qmi_init_service(struct ath12k_base *ab)
spin_lock_init(&ab->qmi.event_lock);
INIT_WORK(&ab->qmi.event_work, ath12k_qmi_driver_event_work);
ret = qmi_add_lookup(&ab->qmi.handle, ATH12K_QMI_WLFW_SERVICE_ID_V01,
ret = qmi_add_lookup(&ab->qmi.handle, QMI_SERVICE_ID_WLFW,
ATH12K_QMI_WLFW_SERVICE_VERS_V01,
ab->qmi.service_ins_id);
if (ret < 0) {

View File

@ -15,7 +15,6 @@
#define ATH12K_QMI_MAX_BDF_FILE_NAME_SIZE 64
#define ATH12K_QMI_CALDB_ADDRESS 0x4BA00000
#define ATH12K_QMI_WLANFW_MAX_BUILD_ID_LEN_V01 128
#define ATH12K_QMI_WLFW_SERVICE_ID_V01 0x45
#define ATH12K_QMI_WLFW_SERVICE_VERS_V01 0x01
#define ATH12K_QMI_WLFW_SERVICE_INS_ID_V01 0x02
#define ATH12K_QMI_WLFW_SERVICE_INS_ID_V01_WCN7850 0x1

View File

@ -12,6 +12,137 @@
#include "core.h"
#include "debug.h"
static const struct ath12k_wmi_tt_level_config_param
tt_level_configs[ATH12K_TT_CFG_IDX_MAX][ENHANCED_THERMAL_LEVELS] = {
[ATH12K_TT_CFG_IDX_IPA] = {
[0] = { .tmplwm = -100, .tmphwm = 115, .dcoffpercent = 0,
.pout_reduction_db = 0 },
[1] = { .tmplwm = 110, .tmphwm = 120, .dcoffpercent = 0,
.pout_reduction_db = 12 },
[2] = { .tmplwm = 115, .tmphwm = 125, .dcoffpercent = 50,
.pout_reduction_db = 12 },
[3] = { .tmplwm = 120, .tmphwm = 130, .dcoffpercent = 90,
.pout_reduction_db = 12 },
[4] = { .tmplwm = 125, .tmphwm = 130, .dcoffpercent = 100,
.pout_reduction_db = 12 },
},
[ATH12K_TT_CFG_IDX_XFEM] = {
[0] = { .tmplwm = -100, .tmphwm = 105, .dcoffpercent = 0,
.pout_reduction_db = 0 },
[1] = { .tmplwm = 100, .tmphwm = 110, .dcoffpercent = 0,
.pout_reduction_db = 0 },
[2] = { .tmplwm = 105, .tmphwm = 115, .dcoffpercent = 50,
.pout_reduction_db = 0 },
[3] = { .tmplwm = 110, .tmphwm = 120, .dcoffpercent = 90,
.pout_reduction_db = 0 },
[4] = { .tmplwm = 115, .tmphwm = 120, .dcoffpercent = 100,
.pout_reduction_db = 0 },
},
};
static enum ath12k_thermal_cfg_idx ath12k_thermal_cfg_index(struct ath12k *ar)
{
if (test_bit(WMI_TLV_SERVICE_IS_TARGET_IPA, ar->ab->wmi_ab.svc_map))
return ATH12K_TT_CFG_IDX_IPA;
return ATH12K_TT_CFG_IDX_XFEM;
}
int ath12k_thermal_throttling_config_default(struct ath12k *ar)
{
struct ath12k_wmi_thermal_mitigation_arg param = {};
int ret;
if (test_bit(WMI_TLV_SERVICE_THERM_THROT_5_LEVELS, ar->ab->wmi_ab.svc_map))
param.num_levels = ENHANCED_THERMAL_LEVELS;
else
param.num_levels = THERMAL_LEVELS;
param.levelconf = ar->thermal.tt_level_configs;
ret = ath12k_wmi_send_thermal_mitigation_cmd(ar, &param);
if (ret)
ath12k_warn(ar->ab,
"failed to send thermal mitigation cmd for default config: %d\n",
ret);
return ret;
}
void ath12k_thermal_init_configs(struct ath12k *ar)
{
enum ath12k_thermal_cfg_idx cfg_idx;
cfg_idx = ath12k_thermal_cfg_index(ar);
ar->thermal.tt_level_configs = &tt_level_configs[cfg_idx][0];
}
static int
ath12k_thermal_get_max_throttle_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
*state = ATH12K_THERMAL_THROTTLE_MAX;
return 0;
}
static int
ath12k_thermal_get_cur_throttle_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct ath12k *ar = cdev->devdata;
mutex_lock(&ar->thermal.lock);
*state = ar->thermal.throttle_state;
mutex_unlock(&ar->thermal.lock);
return 0;
}
int ath12k_thermal_set_throttling(struct ath12k *ar, u32 throttle_state)
{
struct ath12k_wmi_thermal_mitigation_arg param = {};
struct ath12k_wmi_tt_level_config_param cfg = {};
int ret;
param.num_levels = 1;
cfg.dcoffpercent = throttle_state;
param.levelconf = &cfg;
ret = ath12k_wmi_send_thermal_mitigation_cmd(ar, &param);
if (ret)
ath12k_warn(ar->ab, "failed to send thermal mitigation cmd: %d\n",
ret);
return ret;
}
static int
ath12k_thermal_set_cur_throttle_state(struct thermal_cooling_device *cdev,
unsigned long throttle_state)
{
struct ath12k *ar = cdev->devdata;
if (throttle_state > ATH12K_THERMAL_THROTTLE_MAX)
return -EINVAL;
scoped_guard(mutex, &ar->thermal.lock) {
if (ar->thermal.throttle_state == throttle_state)
return 0;
ar->thermal.throttle_state = throttle_state;
}
if (throttle_state == 0)
return ath12k_thermal_throttling_config_default(ar);
return ath12k_thermal_set_throttling(ar, throttle_state);
}
static const struct thermal_cooling_device_ops ath12k_thermal_ops = {
.get_max_state = ath12k_thermal_get_max_throttle_state,
.get_cur_state = ath12k_thermal_get_cur_throttle_state,
.set_cur_state = ath12k_thermal_set_cur_throttle_state,
};
static ssize_t ath12k_thermal_temp_show(struct device *dev,
struct device_attribute *attr,
char *buf)
@ -66,59 +197,108 @@ static struct attribute *ath12k_hwmon_attrs[] = {
};
ATTRIBUTE_GROUPS(ath12k_hwmon);
static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i)
{
char pdev_name[20];
struct ath12k *ar;
int ret;
ar = ab->pdevs[i].ar;
if (!ar)
return 0;
ar->thermal.cdev =
thermal_cooling_device_register("ath12k_thermal", ar,
&ath12k_thermal_ops);
if (IS_ERR(ar->thermal.cdev)) {
ret = PTR_ERR(ar->thermal.cdev);
ar->thermal.cdev = NULL;
ath12k_err(ar->ab, "failed to register cooling device: %d\n",
ret);
return ret;
}
scnprintf(pdev_name, sizeof(pdev_name), "cooling_device%u",
ar->hw_link_id);
ret = sysfs_create_link(&ar->ah->hw->wiphy->dev.kobj,
&ar->thermal.cdev->device.kobj, pdev_name);
if (ret) {
ath12k_err(ab, "failed to create cooling device symlink: %d\n",
ret);
goto unregister_cdev;
}
ar->thermal.hwmon_dev =
hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev,
"ath12k_hwmon", ar,
ath12k_hwmon_groups);
if (IS_ERR(ar->thermal.hwmon_dev)) {
ret = PTR_ERR(ar->thermal.hwmon_dev);
ar->thermal.hwmon_dev = NULL;
ath12k_err(ar->ab, "failed to register hwmon device: %d\n",
ret);
goto remove_sysfs;
}
return 0;
remove_sysfs:
sysfs_remove_link(&ar->ah->hw->wiphy->dev.kobj, pdev_name);
unregister_cdev:
thermal_cooling_device_unregister(ar->thermal.cdev);
ar->thermal.cdev = NULL;
return ret;
}
static void ath12k_thermal_cleanup_radio(struct ath12k_base *ab, int i)
{
char pdev_name[20];
struct ath12k *ar;
ar = ab->pdevs[i].ar;
if (!ar)
return;
hwmon_device_unregister(ar->thermal.hwmon_dev);
ar->thermal.hwmon_dev = NULL;
scnprintf(pdev_name, sizeof(pdev_name), "cooling_device%u",
ar->hw_link_id);
sysfs_remove_link(&ar->ah->hw->wiphy->dev.kobj, pdev_name);
thermal_cooling_device_unregister(ar->thermal.cdev);
ar->thermal.cdev = NULL;
}
int ath12k_thermal_register(struct ath12k_base *ab)
{
struct ath12k *ar;
int i, j, ret;
int i, ret;
if (!IS_REACHABLE(CONFIG_HWMON))
return 0;
for (i = 0; i < ab->num_radios; i++) {
ar = ab->pdevs[i].ar;
if (!ar)
continue;
ar->thermal.hwmon_dev =
hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev,
"ath12k_hwmon", ar,
ath12k_hwmon_groups);
if (IS_ERR(ar->thermal.hwmon_dev)) {
ret = PTR_ERR(ar->thermal.hwmon_dev);
ar->thermal.hwmon_dev = NULL;
ath12k_err(ar->ab, "failed to register hwmon device: %d\n",
ret);
for (j = i - 1; j >= 0; j--) {
ar = ab->pdevs[j].ar;
if (!ar)
continue;
hwmon_device_unregister(ar->thermal.hwmon_dev);
ar->thermal.hwmon_dev = NULL;
}
return ret;
}
ret = ath12k_thermal_setup_radio(ab, i);
if (ret)
goto out;
}
return 0;
out:
for (i--; i >= 0; i--)
ath12k_thermal_cleanup_radio(ab, i);
return ret;
}
void ath12k_thermal_unregister(struct ath12k_base *ab)
{
struct ath12k *ar;
int i;
if (!IS_REACHABLE(CONFIG_HWMON))
return;
for (i = 0; i < ab->num_radios; i++) {
ar = ab->pdevs[i].ar;
if (!ar)
continue;
if (ar->thermal.hwmon_dev) {
hwmon_device_unregister(ar->thermal.hwmon_dev);
ar->thermal.hwmon_dev = NULL;
}
}
for (i = 0; i < ab->num_radios; i++)
ath12k_thermal_cleanup_radio(ab, i);
}

View File

@ -7,20 +7,41 @@
#ifndef _ATH12K_THERMAL_
#define _ATH12K_THERMAL_
#include <linux/mutex.h>
#define ATH12K_THERMAL_SYNC_TIMEOUT_HZ (5 * HZ)
#define ATH12K_THERMAL_DEFAULT_DUTY_CYCLE 100
#define ATH12K_THERMAL_THROTTLE_MAX 100
enum ath12k_thermal_cfg_idx {
/* Internal Power Amplifier Device */
ATH12K_TT_CFG_IDX_IPA,
/* External Power Amplifier Device or External Front End Module */
ATH12K_TT_CFG_IDX_XFEM,
ATH12K_TT_CFG_IDX_MAX,
};
struct ath12k_thermal {
struct completion wmi_sync;
/* temperature value in Celsius degree protected by data_lock. */
int temperature;
struct device *hwmon_dev;
const struct ath12k_wmi_tt_level_config_param *tt_level_configs;
struct thermal_cooling_device *cdev;
/* Serialize thermal operations and hwmon reads */
struct mutex lock;
u32 throttle_state;
};
#if IS_REACHABLE(CONFIG_THERMAL)
int ath12k_thermal_register(struct ath12k_base *ab);
void ath12k_thermal_unregister(struct ath12k_base *ab);
void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature);
int ath12k_thermal_throttling_config_default(struct ath12k *ar);
void ath12k_thermal_init_configs(struct ath12k *ar);
int ath12k_thermal_set_throttling(struct ath12k *ar, u32 throttle_state);
#else
static inline int ath12k_thermal_register(struct ath12k_base *ab)
{
@ -36,5 +57,19 @@ static inline void ath12k_thermal_event_temperature(struct ath12k *ar,
{
}
static inline int ath12k_thermal_throttling_config_default(struct ath12k *ar)
{
return 0;
}
static inline void ath12k_thermal_init_configs(struct ath12k *ar)
{
}
static inline int ath12k_thermal_set_throttling(struct ath12k *ar,
u32 throttle_state)
{
return 0;
}
#endif
#endif /* _ATH12K_THERMAL_ */

View File

@ -234,6 +234,68 @@ static const int ath12k_hw_mode_pri_map[] = {
PRIMAP(WMI_HOST_HW_MODE_MAX),
};
/*
* Interference bitmap transform maps used by
* ath12k_wmi_transform_interference_bitmap().
*
* Firmware reports bitmap bits in a primary-based order where:
* - bit 0 is always the primary 20 MHz segment,
* - bit 1 is the adjacent 20 MHz in the same 40 MHz block,
* - bit 2 is the lower 20 MHz segment of the adjacent 40 MHz segment
* - bit 3 is the higher 20 MHz segment of the adjacent 40 MHz segment
* - remaining bits continue outward in 80/160/320 MHz groups.
*
* cfg80211 userspace notification expects absolute frequency order where:
* - bit 0 is the lowest-frequency 20 MHz segment in the current chandef,
* - bit N increases monotonically toward higher frequency.
*
* For each bandwidth-specific map:
* - row index = primary 20 MHz index in absolute (low->high) order,
* - column index = source bit position from firmware bitmap,
* - value = destination bit position in absolute order bitmap.
*
* Example for 80 MHz: if primary index is 2 (third 20 MHz chunk from low
* frequency), row intf_map_80[2] = { 2, 3, 0, 1 } means firmware bits {0,1,2,3}
* are remapped to destination bits {2,3,0,1} before notifying cfg80211.
*/
static const int intf_map_80[4][4] = {
{ 0, 1, 2, 3 },
{ 1, 0, 2, 3 },
{ 2, 3, 0, 1 },
{ 3, 2, 0, 1 }
};
static const int intf_map_160[8][8] = {
{ 0, 1, 2, 3, 4, 5, 6, 7 },
{ 1, 0, 2, 3, 4, 5, 6, 7 },
{ 2, 3, 0, 1, 4, 5, 6, 7 },
{ 3, 2, 0, 1, 4, 5, 6, 7 },
{ 4, 5, 6, 7, 0, 1, 2, 3 },
{ 5, 4, 6, 7, 0, 1, 2, 3 },
{ 6, 7, 4, 5, 0, 1, 2, 3 },
{ 7, 6, 4, 5, 0, 1, 2, 3 }
};
static const int intf_map_320[16][16] = {
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 2, 3, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 3, 2, 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 4, 5, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 5, 4, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 6, 7, 4, 5, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 7, 6, 4, 5, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 },
{ 9, 8, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 },
{ 10, 11, 8, 9, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 },
{ 11, 10, 8, 9, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7 },
{ 12, 13, 14, 15, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 },
{ 13, 12, 14, 15, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 },
{ 14, 15, 12, 13, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 },
{ 15, 14, 12, 13, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7 }
};
static int
ath12k_wmi_tlv_iter(struct ath12k_base *ab, const void *ptr, size_t len,
int (*iter)(struct ath12k_base *ab, u16 tag, u16 len,
@ -3380,6 +3442,75 @@ int ath12k_wmi_send_set_current_country_cmd(struct ath12k *ar,
return ret;
}
int
ath12k_wmi_send_thermal_mitigation_cmd(struct ath12k *ar,
struct ath12k_wmi_thermal_mitigation_arg *arg)
{
struct ath12k_wmi_therm_throt_level_config_param *lvl_conf;
struct ath12k_wmi_therm_throt_config_request_cmd *cmd;
struct ath12k_wmi_pdev *wmi = ar->wmi;
struct wmi_tlv *tlv;
struct sk_buff *skb;
int i, ret, len;
len = sizeof(*cmd) + TLV_HDR_SIZE + (arg->num_levels * sizeof(*lvl_conf));
skb = ath12k_wmi_alloc_skb(wmi->wmi_ab, len);
if (!skb)
return -ENOMEM;
cmd = (struct ath12k_wmi_therm_throt_config_request_cmd *)skb->data;
cmd->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_THERM_THROT_CONFIG_REQUEST,
sizeof(*cmd));
cmd->pdev_id = cpu_to_le32(ar->pdev->pdev_id);
cmd->enable = cpu_to_le32(1);
cmd->dc = cpu_to_le32(100);
cmd->dc_per_event = cpu_to_le32(0xffffffff);
cmd->therm_throt_levels = cpu_to_le32(arg->num_levels);
tlv = (struct wmi_tlv *)(skb->data + sizeof(*cmd));
tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT,
arg->num_levels * sizeof(*lvl_conf));
lvl_conf = (struct ath12k_wmi_therm_throt_level_config_param *)tlv->value;
for (i = 0; i < arg->num_levels; i++) {
lvl_conf->tlv_header =
ath12k_wmi_tlv_cmd_hdr(WMI_TAG_THERM_THROT_LEVEL_CONFIG_INFO,
sizeof(*lvl_conf));
lvl_conf->temp_lwm = a_cpu_to_sle32(arg->levelconf[i].tmplwm);
lvl_conf->temp_hwm = a_cpu_to_sle32(arg->levelconf[i].tmphwm);
lvl_conf->dc_off_percent = cpu_to_le32(arg->levelconf[i].dcoffpercent);
if (test_bit(WMI_TLV_SERVICE_THERM_THROT_POUT_REDUCTION,
ar->ab->wmi_ab.svc_map))
lvl_conf->pout_reduction_25db =
cpu_to_le32(arg->levelconf[i].pout_reduction_db);
if (test_bit(WMI_TLV_SERVICE_THERM_THROT_TX_CHAIN_MASK,
ar->ab->wmi_ab.svc_map))
lvl_conf->tx_chain_mask = cpu_to_le32(ar->cfg_tx_chainmask);
lvl_conf->duty_cycle = cpu_to_le32(ATH12K_THERMAL_DEFAULT_DUTY_CYCLE);
lvl_conf++;
}
ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
"WMI vdev set thermal throt pdev_id %u enable dc 100 dc_per_event 0xffffffff levels %d\n",
ar->pdev->pdev_id, arg->num_levels);
ret = ath12k_wmi_cmd_send(wmi, skb, WMI_THERM_THROT_SET_CONF_CMDID);
if (ret) {
ath12k_warn(ar->ab,
"failed to send WMI_THERM_THROT_SET_CONF cmd: %d\n",
ret);
dev_kfree_skb(skb);
}
return ret;
}
int ath12k_wmi_send_11d_scan_start_cmd(struct ath12k *ar,
struct wmi_11d_scan_start_arg *arg)
{
@ -4199,12 +4330,9 @@ int ath12k_wmi_pdev_lro_cfg(struct ath12k *ar,
if (ret) {
ath12k_warn(ar->ab,
"failed to send lro cfg req wmi cmd\n");
goto err;
dev_kfree_skb(skb);
}
return 0;
err:
dev_kfree_skb(skb);
return ret;
}
@ -4335,12 +4463,9 @@ int ath12k_wmi_vdev_spectral_conf(struct ath12k *ar,
if (ret) {
ath12k_warn(ar->ab,
"failed to send spectral scan config wmi cmd\n");
goto err;
dev_kfree_skb(skb);
}
return 0;
err:
dev_kfree_skb(skb);
return ret;
}
@ -4372,12 +4497,9 @@ int ath12k_wmi_vdev_spectral_enable(struct ath12k *ar, u32 vdev_id,
if (ret) {
ath12k_warn(ar->ab,
"failed to send spectral enable wmi cmd\n");
goto err;
dev_kfree_skb(skb);
}
return 0;
err:
dev_kfree_skb(skb);
return ret;
}
@ -4418,12 +4540,9 @@ int ath12k_wmi_pdev_dma_ring_cfg(struct ath12k *ar,
if (ret) {
ath12k_warn(ar->ab,
"failed to send dma ring cfg req wmi cmd\n");
goto err;
dev_kfree_skb(skb);
}
return 0;
err:
dev_kfree_skb(skb);
return ret;
}
@ -8540,6 +8659,330 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
ev->ctl_failsafe_status);
}
static int
ath12k_wmi_incumbent_signal_interference_subtlv_parser(struct ath12k_base *ab,
u16 tag, u16 len,
const void *ptr,
void *data)
{
const struct ath12k_wmi_incumbent_signal_interference_params *info;
struct ath12k_wmi_incumbent_signal_interference_arg *arg = data;
switch (tag) {
case WMI_TAG_DCS_INCUMBENT_SIGNAL_INTERFERENCE_TYPE:
if (len < sizeof(*info)) {
ath12k_warn(ab,
"DCS incumbent signal interference subtlv 0x%x invalid len %u\n",
tag, len);
return -EINVAL;
}
info = ptr;
arg->chan_width = le32_to_cpu(info->chan_width);
arg->chan_freq = le32_to_cpu(info->chan_freq);
arg->center_freq0 = le32_to_cpu(info->center_freq0);
arg->center_freq1 = le32_to_cpu(info->center_freq1);
arg->chan_bw_interference_bitmap =
le32_to_cpu(info->chan_bw_interference_bitmap);
ath12k_dbg(ab, ATH12K_DBG_WMI,
"incumbent signal interference chan width %u freq %u center_freq0 %u center_freq1 %u bitmap 0x%x\n",
arg->chan_width, arg->chan_freq,
arg->center_freq0, arg->center_freq1,
arg->chan_bw_interference_bitmap);
break;
default:
ath12k_warn(ab, "Received invalid tag 0x%x for WMI DCS interference in subtlvs\n",
tag);
return -EINVAL;
}
return 0;
}
static int ath12k_wmi_dcs_interference_event_parser(struct ath12k_base *ab,
u16 tag, u16 len,
const void *ptr, void *data)
{
int ret = 0;
switch (tag) {
case WMI_TAG_DCS_INTERFERENCE_EVENT:
/* Fixed param should already be processed */
break;
case WMI_TAG_ARRAY_STRUCT:
ret = ath12k_wmi_tlv_iter(ab, ptr, len,
ath12k_wmi_incumbent_signal_interference_subtlv_parser,
data);
break;
default:
ath12k_warn(ab, "Received invalid tag 0x%x for WMI DCS interference event\n",
tag);
ret = -EINVAL;
break;
}
return ret;
}
static bool
ath12k_wmi_validate_interference_info(struct ath12k *ar,
struct ath12k_wmi_incumbent_signal_interference_arg *info)
{
switch (info->chan_width) {
case WMI_CHAN_WIDTH_20:
if (info->chan_bw_interference_bitmap > ATH12K_WMI_DCS_SEG_PRI20) {
ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
"DCS interference event received with wrong chan width bmap 0x%x for 20 MHz",
info->chan_bw_interference_bitmap);
return false;
}
break;
case WMI_CHAN_WIDTH_40:
if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
ATH12K_WMI_DCS_SEG_SEC20)) {
ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
"DCS interference event received with wrong chan width bmap 0x%x for 40 MHz",
info->chan_bw_interference_bitmap);
return false;
}
break;
case WMI_CHAN_WIDTH_80:
if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
ATH12K_WMI_DCS_SEG_SEC20 |
ATH12K_WMI_DCS_SEG_SEC40)) {
ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
"DCS interference event received with wrong chan width bmap 0x%x for 80 MHz",
info->chan_bw_interference_bitmap);
return false;
}
break;
case WMI_CHAN_WIDTH_160:
if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
ATH12K_WMI_DCS_SEG_SEC20 |
ATH12K_WMI_DCS_SEG_SEC40 |
ATH12K_WMI_DCS_SEG_SEC80)) {
ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
"DCS interference event received with wrong chan width bmap 0x%x for 160 MHz",
info->chan_bw_interference_bitmap);
return false;
}
break;
case WMI_CHAN_WIDTH_320:
if (info->chan_bw_interference_bitmap > (ATH12K_WMI_DCS_SEG_PRI20 |
ATH12K_WMI_DCS_SEG_SEC20 |
ATH12K_WMI_DCS_SEG_SEC40 |
ATH12K_WMI_DCS_SEG_SEC80 |
ATH12K_WMI_DCS_SEG_SEC160)) {
ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
"DCS interference event received with wrong chan width bmap 0x%x for 320 MHz",
info->chan_bw_interference_bitmap);
return false;
}
break;
default:
ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
"DCS interference event received with unknown channel width %u",
info->chan_width);
return false;
}
return true;
}
static u32
ath12k_wmi_transform_interference_bitmap(int input_bitmap,
struct cfg80211_chan_def *chandef)
{
u16 output_bits[ATH12K_MAX_20MHZ_SEGMENTS] = {};
u16 input_bits[ATH12K_MAX_20MHZ_SEGMENTS] = {};
u32 start_freq, segment_freq;
int primary_index = -1;
u32 output_bitmap = 0;
u16 num_sub_chans;
int bandwidth;
bandwidth = nl80211_chan_width_to_mhz(chandef->width);
if (bandwidth < 0)
return 0;
/*
* Firmware reports bit 0 as primary 20 MHz irrespective of absolute
* frequency position. Convert to standardized lowest-to-highest 20 MHz
* ordering expected by cfg80211/mac80211 userspace consumers.
*/
num_sub_chans = bandwidth / 20;
start_freq = (chandef->center_freq1 - bandwidth / 2) + 10;
for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; i++) {
segment_freq = start_freq + (i * 20);
if (segment_freq == chandef->chan->center_freq) {
primary_index = i;
break;
}
}
if (primary_index == -1)
return 0;
for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; ++i)
input_bits[i] = BIT(i) & input_bitmap;
for (int i = 0; i < num_sub_chans; ++i) {
int src = i, dst = i;
switch (bandwidth) {
case 40:
if (primary_index == 1)
dst = 1 - i;
break;
case 80:
dst = intf_map_80[primary_index][i];
break;
case 160:
dst = intf_map_160[primary_index][i];
break;
case 320:
dst = intf_map_320[primary_index][i];
break;
}
output_bits[dst] = input_bits[src];
}
for (int i = 0; i < ATH12K_MAX_20MHZ_SEGMENTS; ++i)
output_bitmap |= output_bits[i] ? BIT(i) : 0;
return output_bitmap;
}
static void
ath12k_wmi_process_incumbent_signal_interference_evt(struct ath12k_base *ab,
struct sk_buff *skb,
const struct ath12k_wmi_intf_arg *intf_arg)
{
struct ath12k_wmi_incumbent_signal_interference_arg info = {};
struct ath12k_incumbent_signal_interference *incumbent;
struct ath12k_mac_get_any_chanctx_conf_arg arg;
u32 transformed_intf_bitmap;
struct ieee80211_hw *hw;
struct ath12k *ar;
int ret;
guard(rcu)();
ar = ath12k_mac_get_ar_by_pdev_id(ab, intf_arg->pdev_id);
if (!ar) {
ath12k_warn(ab, "incumbent signal interference detected on invalid pdev %d\n",
intf_arg->pdev_id);
return;
}
if (!ar->supports_6ghz) {
ath12k_warn(ab, "pdev does not support 6 GHz, dropping DCS interference event\n");
return;
}
incumbent = &ar->incumbent_signal_interference;
spin_lock_bh(&ar->data_lock);
if (incumbent->handling_in_progress) {
spin_unlock_bh(&ar->data_lock);
ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
"incumbent signal interference handling ongoing, dropping DCS interference event");
return;
}
spin_unlock_bh(&ar->data_lock);
ret = ath12k_wmi_tlv_iter(ab, skb->data, skb->len,
ath12k_wmi_dcs_interference_event_parser,
&info);
if (ret) {
ath12k_warn(ab,
"failed to parse incumbent signal interference TLV. Error %d\n",
ret);
return;
}
if (!ath12k_wmi_validate_interference_info(ar, &info)) {
ath12k_warn(ab, "invalid DCS incumbent signal interference TLV - Skipping event");
return;
}
arg.ar = ar;
arg.chanctx_conf = NULL;
hw = ath12k_ar_to_hw(ar);
ieee80211_iter_chan_contexts_atomic(hw,
ath12k_mac_get_any_chanctx_conf_iter,
&arg);
if (!arg.chanctx_conf) {
ath12k_warn(ab, "failed to find valid chanctx_conf in incumbent signal intf detected event\n");
return;
}
if (info.chan_freq != arg.chanctx_conf->def.chan->center_freq) {
ath12k_dbg(ab, ATH12K_DBG_WMI,
"dcs interference event received with wrong channel %d (ctx freq %d)",
info.chan_freq, arg.chanctx_conf->def.chan->center_freq);
return;
}
spin_lock_bh(&ar->data_lock);
incumbent->center_freq = arg.chanctx_conf->def.chan->center_freq;
incumbent->width = arg.chanctx_conf->def.width;
incumbent->chan_bw_interference_bitmap = info.chan_bw_interference_bitmap;
incumbent->handling_in_progress = true;
spin_unlock_bh(&ar->data_lock);
transformed_intf_bitmap =
ath12k_wmi_transform_interference_bitmap(info.chan_bw_interference_bitmap,
&arg.chanctx_conf->def);
ath12k_dbg(ab, ATH12K_DBG_WMI,
"incumbent signal interference bitmap 0x%x (transformed 0x%x)\n",
info.chan_bw_interference_bitmap, transformed_intf_bitmap);
cfg80211_incumbent_signal_notify(hw->wiphy,
&arg.chanctx_conf->def,
transformed_intf_bitmap,
GFP_ATOMIC);
}
static void
ath12k_wmi_dcs_interference_event(struct ath12k_base *ab,
struct sk_buff *skb)
{
const struct ath12k_wmi_dcs_interference_ev_fixed_params *dcs_intf_ev;
struct ath12k_wmi_intf_arg dcs_intf_arg;
const struct wmi_tlv *tlv;
u16 tlv_tag;
u8 *ptr;
if (skb->len < (sizeof(*dcs_intf_ev) + TLV_HDR_SIZE)) {
ath12k_warn(ab, "DCS interference event is of incorrect length\n");
return;
}
ptr = skb->data;
tlv = (struct wmi_tlv *)ptr;
tlv_tag = le32_get_bits(tlv->header, WMI_TLV_TAG);
ptr += sizeof(*tlv);
if (tlv_tag != WMI_TAG_DCS_INTERFERENCE_EVENT) {
ath12k_warn(ab, "DCS interference event received with wrong tag\n");
return;
}
dcs_intf_ev = (struct ath12k_wmi_dcs_interference_ev_fixed_params *)ptr;
dcs_intf_arg.interference_type =
le32_to_cpu(dcs_intf_ev->interference_type);
dcs_intf_arg.pdev_id = le32_to_cpu(dcs_intf_ev->pdev_id);
if (dcs_intf_arg.interference_type ==
ATH12K_WMI_DCS_INCUMBENT_SIGNAL_INTERFERENCE) {
ath12k_dbg(ab, ATH12K_DBG_WMI,
"incumbent signal interference (Type %u) detected on pdev %u.",
dcs_intf_arg.interference_type,
dcs_intf_arg.pdev_id);
ath12k_wmi_process_incumbent_signal_interference_evt(ab, skb,
&dcs_intf_arg);
}
}
static void
ath12k_wmi_process_csa_switch_count_event(struct ath12k_base *ab,
const struct ath12k_wmi_pdev_csa_event *ev,
@ -8762,6 +9205,42 @@ ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
rcu_read_unlock();
}
static void ath12k_wmi_thermal_throt_stats_event(struct ath12k_base *ab,
struct sk_buff *skb)
{
const struct wmi_therm_throt_stats_event *ev;
struct ath12k *ar;
const void **tb;
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ath12k_err(ab, "failed to parse thermal throttling stats tlv: %ld\n",
PTR_ERR(tb));
return;
}
ev = tb[WMI_TAG_THERM_THROT_STATS_EVENT];
if (!ev) {
ath12k_err(ab, "failed to fetch thermal throt stats ev\n");
return;
}
rcu_read_lock();
ar = ath12k_mac_get_ar_by_pdev_id(ab, le32_to_cpu(ev->pdev_id));
if (!ar) {
ath12k_warn(ab, "received thermal_throt_stats in invalid pdev %u\n",
le32_to_cpu(ev->pdev_id));
rcu_read_unlock();
return;
}
rcu_read_unlock();
ath12k_dbg(ab, ATH12K_DBG_WMI,
"thermal stats ev level %u pdev_id %u temp %u throt_levels %u\n",
le32_to_cpu(ev->level), le32_to_cpu(ev->pdev_id),
le32_to_cpu(ev->temp), le32_to_cpu(ev->therm_throt_levels));
}
static void ath12k_fils_discovery_event(struct ath12k_base *ab,
struct sk_buff *skb)
{
@ -9811,6 +10290,9 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb)
case WMI_PDEV_TEMPERATURE_EVENTID:
ath12k_wmi_pdev_temperature_event(ab, skb);
break;
case WMI_THERM_THROT_STATS_EVENTID:
ath12k_wmi_thermal_throt_stats_event(ab, skb);
break;
case WMI_PDEV_DMA_RING_BUF_RELEASE_EVENTID:
ath12k_wmi_pdev_dma_ring_buf_release_event(ab, skb);
break;
@ -9865,6 +10347,9 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb)
case WMI_OBSS_COLOR_COLLISION_DETECTION_EVENTID:
ath12k_wmi_obss_color_collision_event(ab, skb);
break;
case WMI_DCS_INTERFERENCE_EVENTID:
ath12k_wmi_dcs_interference_event(ab, skb);
break;
/* add Unsupported events (rare) here */
case WMI_TBTTOFFSET_EXT_UPDATE_EVENTID:
case WMI_PEER_OPER_MODE_CHANGE_EVENTID:
@ -10083,6 +10568,42 @@ int ath12k_wmi_send_tpc_stats_request(struct ath12k *ar,
return ret;
}
int ath12k_wmi_simulate_incumbent_signal_interference(struct ath12k *ar,
u32 chan_bw_interference_bitmap)
{
struct wmi_unit_test_arg wmi_ut = {};
struct ath12k_link_vif *arvif;
struct ath12k_vif *ahvif;
bool arvif_found = false;
list_for_each_entry(arvif, &ar->arvifs, list) {
ahvif = arvif->ahvif;
if (arvif->is_started && ahvif->vdev_type == WMI_VDEV_TYPE_AP) {
arvif_found = true;
break;
}
}
if (!arvif_found)
return -EINVAL;
wmi_ut.args[ATH12K_WMI_INCUMBENT_SIGNAL_TEST_INTF] =
ATH12K_WMI_UNIT_TEST_INCUMBENT_SIGNAL_INTF_TYPE;
wmi_ut.args[ATH12K_WMI_INCUMBENT_SIGNAL_TEST_BITMAP] =
chan_bw_interference_bitmap;
wmi_ut.vdev_id = arvif->vdev_id;
wmi_ut.module_id = ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_MODULE;
wmi_ut.num_args = ATH12K_WMI_INCUMBENT_SIGNAL_MAX_TEST_ARGS;
wmi_ut.diag_token = ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_TOKEN;
ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
"Triggering incumbent signal interference simulation, interference bitmap: 0x%x\n",
chan_bw_interference_bitmap);
return ath12k_wmi_send_unit_test_cmd(ar, &wmi_ut);
}
int ath12k_wmi_connect(struct ath12k_base *ab)
{
u32 i;
@ -10874,10 +11395,9 @@ int ath12k_wmi_mlo_setup(struct ath12k *ar, struct wmi_mlo_setup_arg *mlo_params
ath12k_warn(ar->ab, "failed to submit WMI_MLO_SETUP_CMDID command: %d\n",
ret);
dev_kfree_skb(skb);
return ret;
}
return 0;
return ret;
}
int ath12k_wmi_mlo_ready(struct ath12k *ar)
@ -10902,10 +11422,9 @@ int ath12k_wmi_mlo_ready(struct ath12k *ar)
ath12k_warn(ar->ab, "failed to submit WMI_MLO_READY_CMDID command: %d\n",
ret);
dev_kfree_skb(skb);
return ret;
}
return 0;
return ret;
}
int ath12k_wmi_mlo_teardown(struct ath12k *ar)
@ -10931,10 +11450,9 @@ int ath12k_wmi_mlo_teardown(struct ath12k *ar)
ath12k_warn(ar->ab, "failed to submit WMI MLO teardown command: %d\n",
ret);
dev_kfree_skb(skb);
return ret;
}
return 0;
return ret;
}
bool ath12k_wmi_supports_6ghz_cc_ext(struct ath12k *ar)
@ -10997,10 +11515,9 @@ int ath12k_wmi_send_vdev_set_tpc_power(struct ath12k *ar,
if (ret) {
ath12k_warn(ar->ab, "failed to send WMI_VDEV_SET_TPC_POWER_CMDID\n");
dev_kfree_skb(skb);
return ret;
}
return 0;
return ret;
}
static int

View File

@ -870,6 +870,7 @@ enum wmi_tlv_event_id {
WMI_READ_DATA_FROM_FLASH_EVENTID,
WMI_REPORT_RX_AGGR_FAILURE_EVENTID,
WMI_PKGID_EVENTID,
WMI_THERM_THROT_STATS_EVENTID,
WMI_GPIO_INPUT_EVENTID = WMI_TLV_CMD(WMI_GRP_GPIO),
WMI_UPLOADH_EVENTID,
WMI_CAPTUREH_EVENTID,
@ -2014,7 +2015,7 @@ enum wmi_tlv_tag {
WMI_TAG_VDEV_CH_POWER_INFO,
WMI_TAG_MLO_LINK_SET_ACTIVE_CMD = 0x3BE,
WMI_TAG_EHT_RATE_SET = 0x3C4,
WMI_TAG_DCS_AWGN_INT_TYPE = 0x3C5,
WMI_TAG_DCS_INCUMBENT_SIGNAL_INTERFERENCE_TYPE = 0x3C5,
WMI_TAG_MLO_TX_SEND_PARAMS,
WMI_TAG_MLO_PARTNER_LINK_PARAMS,
WMI_TAG_MLO_PARTNER_LINK_PARAMS_PEER_ASSOC,
@ -2259,6 +2260,7 @@ enum wmi_tlv_service {
WMI_TLV_SERVICE_FREQINFO_IN_METADATA = 219,
WMI_TLV_SERVICE_EXT2_MSG = 220,
WMI_TLV_SERVICE_BEACON_PROTECTION_SUPPORT = 244,
WMI_TLV_SERVICE_5_9GHZ_SUPPORT = 247,
WMI_TLV_SERVICE_SRG_SRP_SPATIAL_REUSE_SUPPORT = 249,
WMI_TLV_SERVICE_MBSS_PARAM_IN_VDEV_START_SUPPORT = 253,
@ -2268,11 +2270,17 @@ enum wmi_tlv_service {
WMI_TLV_SERVICE_REG_CC_EXT_EVENT_SUPPORT = 281,
WMI_TLV_SERVICE_DCS_INCUMBENT_SIGNAL_INTERFERENCE_SUPPORT = 286,
WMI_TLV_SERVICE_11BE = 289,
WMI_TLV_SERVICE_WMSK_COMPACTION_RX_TLVS = 361,
WMI_TLV_SERVICE_PEER_METADATA_V1A_V1B_SUPPORT = 365,
WMI_TLV_SERVICE_THERM_THROT_POUT_REDUCTION = 410,
WMI_TLV_SERVICE_IS_TARGET_IPA = 425,
WMI_TLV_SERVICE_THERM_THROT_TX_CHAIN_MASK = 426,
WMI_TLV_SERVICE_THERM_THROT_5_LEVELS = 429,
WMI_TLV_SERVICE_ETH_OFFLOAD = 461,
WMI_MAX_EXT2_SERVICE,
@ -3586,7 +3594,6 @@ struct ath12k_wmi_scan_req_arg {
u32 num_bssid;
u32 num_ssids;
u32 n_probes;
u32 *chan_list;
u32 notify_scan_events;
struct cfg80211_ssid ssid[WLAN_SCAN_MAX_NUM_SSID];
struct ath12k_wmi_mac_addr_params bssid_list[WLAN_SCAN_MAX_NUM_BSSID];
@ -3595,6 +3602,7 @@ struct ath12k_wmi_scan_req_arg {
u32 num_hint_bssid;
struct ath12k_wmi_hint_short_ssid_arg hint_s_ssid[WLAN_SCAN_MAX_HINT_S_SSID];
struct ath12k_wmi_hint_bssid_arg hint_bssid[WLAN_SCAN_MAX_HINT_BSSID];
u32 chan_list[] __counted_by(num_chan);
};
struct wmi_ssid_arg {
@ -4120,6 +4128,49 @@ enum set_init_cc_flags {
ALPHA_IS_SET,
};
struct wmi_therm_throt_stats_event {
__le32 pdev_id;
__le32 temp;
__le32 level;
__le32 therm_throt_levels;
} __packed;
#define THERMAL_LEVELS 4
#define ENHANCED_THERMAL_LEVELS 5
struct ath12k_wmi_tt_level_config_param {
s32 tmplwm;
s32 tmphwm;
u32 dcoffpercent;
u32 pout_reduction_db;
};
struct ath12k_wmi_therm_throt_config_request_cmd {
__le32 tlv_header;
__le32 pdev_id;
__le32 enable;
__le32 dc;
/* After how many duty cycles the firmware sends stats to host */
__le32 dc_per_event;
__le32 therm_throt_levels;
} __packed;
struct ath12k_wmi_therm_throt_level_config_param {
__le32 tlv_header;
a_sle32 temp_lwm;
a_sle32 temp_hwm;
__le32 dc_off_percent;
__le32 prio;
__le32 pout_reduction_25db;
__le32 tx_chain_mask;
__le32 duty_cycle;
} __packed;
struct ath12k_wmi_thermal_mitigation_arg {
int num_levels;
const struct ath12k_wmi_tt_level_config_param *levelconf;
};
struct ath12k_wmi_init_country_arg {
union {
u16 country_code;
@ -4203,6 +4254,16 @@ enum dfs_test_args_idx {
DFS_MAX_TEST_ARGS,
};
#define ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_MODULE 0x18
#define ATH12K_WMI_INCUMBENT_SIGNAL_UNIT_TEST_TOKEN 0
#define ATH12K_WMI_UNIT_TEST_INCUMBENT_SIGNAL_INTF_TYPE 1
enum ath12k_wmi_incumbent_signal_test_args_idx {
ATH12K_WMI_INCUMBENT_SIGNAL_TEST_INTF,
ATH12K_WMI_INCUMBENT_SIGNAL_TEST_BITMAP,
ATH12K_WMI_INCUMBENT_SIGNAL_MAX_TEST_ARGS,
};
/* update if another test command requires more */
#define WMI_UNIT_TEST_ARGS_MAX DFS_MAX_TEST_ARGS
@ -4487,6 +4548,62 @@ struct ath12k_wmi_pdev_radar_event {
a_sle32 sidx;
} __packed;
#define ATH12K_WMI_DCS_INCUMBENT_SIGNAL_INTERFERENCE 0x04
struct ath12k_wmi_dcs_interference_ev_fixed_params {
__le32 interference_type;
__le32 pdev_id;
} __packed;
struct ath12k_wmi_incumbent_signal_interference_params {
__le32 chan_width;
__le32 chan_freq;
__le32 center_freq0;
__le32 center_freq1;
__le32 chan_bw_interference_bitmap;
} __packed;
struct ath12k_wmi_incumbent_signal_interference_arg {
u32 chan_width;
u32 chan_freq;
u32 center_freq0;
u32 center_freq1;
u32 chan_bw_interference_bitmap;
};
struct ath12k_wmi_intf_arg {
u32 interference_type;
u32 pdev_id;
};
enum ath12k_wmi_dcs_interference_chan_segment {
/*
* Firmware reports interference bitmap in primary-based order.
* Bit 0 is the primary 20 MHz, bit 1 is the adjacent 20 MHz within
* the primary 40 MHz. Bits 2-3 cover the secondary 40 MHz, bits 4-7
* cover the secondary 80 MHz, and bits 8-15 cover the secondary 160 MHz.
*/
ATH12K_WMI_DCS_SEG_PRI20 = 0x1,
ATH12K_WMI_DCS_SEG_SEC20 = 0x2,
ATH12K_WMI_DCS_SEG_SEC40_LOW = 0x4,
ATH12K_WMI_DCS_SEG_SEC40_UP = 0x8,
ATH12K_WMI_DCS_SEG_SEC40 = 0xC,
ATH12K_WMI_DCS_SEG_SEC80_LOW = 0x10,
ATH12K_WMI_DCS_SEG_SEC80_LOW_UP = 0x20,
ATH12K_WMI_DCS_SEG_SEC80_UP_LOW = 0x40,
ATH12K_WMI_DCS_SEG_SEC80_UP = 0x80,
ATH12K_WMI_DCS_SEG_SEC80 = 0xF0,
ATH12K_WMI_DCS_SEG_SEC160_LOW = 0x0100,
ATH12K_WMI_DCS_SEG_SEC160_LOW_UP = 0x0200,
ATH12K_WMI_DCS_SEG_SEC160_LOW_UP_UP = 0x0400,
ATH12K_WMI_DCS_SEG_SEC160_LOW_UP_UP_UP = 0x0800,
ATH12K_WMI_DCS_SEG_SEC160_UP_LOW_LOW_LOW = 0x1000,
ATH12K_WMI_DCS_SEG_SEC160_UP_LOW_LOW = 0x2000,
ATH12K_WMI_DCS_SEG_SEC160_UP_LOW = 0x4000,
ATH12K_WMI_DCS_SEG_SEC160_UP = 0x8000,
ATH12K_WMI_DCS_SEG_SEC160 = 0xFF00,
};
struct wmi_pdev_temperature_event {
/* temperature value in Celsius degree */
a_sle32 temp;
@ -6514,6 +6631,8 @@ __le32 ath12k_wmi_tlv_hdr(u32 cmd, u32 len);
int ath12k_wmi_send_tpc_stats_request(struct ath12k *ar,
enum wmi_halphy_ctrl_path_stats_id tpc_stats_type);
void ath12k_wmi_free_tpc_stats_mem(struct ath12k *ar);
int ath12k_wmi_send_thermal_mitigation_cmd(struct ath12k *ar,
struct ath12k_wmi_thermal_mitigation_arg *arg);
static inline u32
ath12k_wmi_caps_ext_get_pdev_id(const struct ath12k_wmi_caps_ext_params *param)
@ -6576,6 +6695,8 @@ int ath12k_wmi_send_vdev_set_tpc_power(struct ath12k *ar,
struct ath12k_reg_tpc_power_info *param);
int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab,
struct wmi_mlo_link_set_active_arg *param);
int ath12k_wmi_simulate_incumbent_signal_interference(struct ath12k *ar,
u32 chan_bw_interference_bitmap);
int ath12k_wmi_alloc(void);
void ath12k_wmi_free(void);

View File

@ -80,14 +80,14 @@ static int ar9002_hw_init_mode_regs(struct ath_hw *ah)
/* iniAddac needs to be modified for these chips */
if (AR_SREV_9160(ah) || !AR_SREV_5416_22_OR_LATER(ah)) {
struct ar5416IniArray *addac = &ah->iniAddac;
u32 size = sizeof(u32) * addac->ia_rows * addac->ia_columns;
u32 n = addac->ia_rows * addac->ia_columns;
u32 *data;
data = devm_kzalloc(ah->dev, size, GFP_KERNEL);
data = devm_kmemdup_array(ah->dev, addac->ia_array, n, sizeof(u32),
GFP_KERNEL);
if (!data)
return -ENOMEM;
memcpy(data, addac->ia_array, size);
addac->ia_array = data;
if (!AR_SREV_5416_22_OR_LATER(ah)) {

View File

@ -137,24 +137,6 @@ static void owl_fw_cb(const struct firmware *fw, void *context)
release_firmware(fw);
}
static const char *owl_get_eeprom_name(struct pci_dev *pdev)
{
struct device *dev = &pdev->dev;
char *eeprom_name;
dev_dbg(dev, "using auto-generated eeprom filename\n");
eeprom_name = devm_kzalloc(dev, EEPROM_FILENAME_LEN, GFP_KERNEL);
if (!eeprom_name)
return NULL;
/* this should match the pattern used in ath9k/init.c */
scnprintf(eeprom_name, EEPROM_FILENAME_LEN, "ath9k-eeprom-pci-%s.bin",
dev_name(dev));
return eeprom_name;
}
static void owl_nvmem_work(struct work_struct *work)
{
struct owl_ctx *ctx = container_of(work, struct owl_ctx, work);
@ -195,8 +177,9 @@ static int owl_nvmem_probe(struct owl_ctx *ctx)
static int owl_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
char eeprom_name[EEPROM_FILENAME_LEN];
struct device *dev = &pdev->dev;
struct owl_ctx *ctx;
const char *eeprom_name;
int err = 0;
if (pci_enable_device(pdev))
@ -215,11 +198,11 @@ static int owl_probe(struct pci_dev *pdev,
if (err <= 0)
return err;
eeprom_name = owl_get_eeprom_name(pdev);
if (!eeprom_name) {
dev_err(&pdev->dev, "no eeprom filename found.\n");
return -ENODEV;
}
dev_dbg(dev, "using auto-generated eeprom filename\n");
/* this should match the pattern used in ath9k/init.c */
scnprintf(eeprom_name, sizeof(eeprom_name), "ath9k-eeprom-pci-%s.bin",
dev_name(dev));
err = request_firmware_nowait(THIS_MODULE, true, eeprom_name,
&pdev->dev, GFP_KERNEL, ctx, owl_fw_cb);

View File

@ -133,13 +133,11 @@ int ath9k_cmn_init_channels_rates(struct ath_common *common)
ATH9K_NUM_CHANNELS);
if (ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) {
channels = devm_kzalloc(ah->dev,
channels = devm_kmemdup(ah->dev, ath9k_2ghz_chantable,
sizeof(ath9k_2ghz_chantable), GFP_KERNEL);
if (!channels)
return -ENOMEM;
memcpy(channels, ath9k_2ghz_chantable,
sizeof(ath9k_2ghz_chantable));
common->sbands[NL80211_BAND_2GHZ].channels = channels;
common->sbands[NL80211_BAND_2GHZ].band = NL80211_BAND_2GHZ;
common->sbands[NL80211_BAND_2GHZ].n_channels =
@ -150,13 +148,11 @@ int ath9k_cmn_init_channels_rates(struct ath_common *common)
}
if (ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) {
channels = devm_kzalloc(ah->dev,
channels = devm_kmemdup(ah->dev, ath9k_5ghz_chantable,
sizeof(ath9k_5ghz_chantable), GFP_KERNEL);
if (!channels)
return -ENOMEM;
memcpy(channels, ath9k_5ghz_chantable,
sizeof(ath9k_5ghz_chantable));
common->sbands[NL80211_BAND_5GHZ].channels = channels;
common->sbands[NL80211_BAND_5GHZ].band = NL80211_BAND_5GHZ;
common->sbands[NL80211_BAND_5GHZ].n_channels =

View File

@ -285,7 +285,7 @@ int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd,
{
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
u8 *ds;
int i, bsize, desc_len;
int i, desc_len;
ath_dbg(common, CONFIG, "%s DMA: %u buffers %u desc/buf\n",
name, nbuf, ndesc);
@ -339,8 +339,7 @@ int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd,
if (is_tx) {
struct ath_buf *bf;
bsize = sizeof(struct ath_buf) * nbuf;
bf = devm_kzalloc(sc->dev, bsize, GFP_KERNEL);
bf = devm_kcalloc(sc->dev, sizeof(*bf), nbuf, GFP_KERNEL);
if (!bf)
return -ENOMEM;
@ -370,8 +369,7 @@ int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd,
} else {
struct ath_rxbuf *bf;
bsize = sizeof(struct ath_rxbuf) * nbuf;
bf = devm_kzalloc(sc->dev, bsize, GFP_KERNEL);
bf = devm_kcalloc(sc->dev, sizeof(struct ath_rxbuf), nbuf, GFP_KERNEL);
if (!bf)
return -ENOMEM;
@ -576,7 +574,7 @@ static int ath9k_nvmem_request_eeprom(struct ath_softc *sc)
size_t len;
int err;
cell = devm_nvmem_cell_get(sc->dev, "calibration");
cell = nvmem_cell_get(sc->dev, "calibration");
if (IS_ERR(cell)) {
err = PTR_ERR(cell);
@ -593,6 +591,7 @@ static int ath9k_nvmem_request_eeprom(struct ath_softc *sc)
}
buf = nvmem_cell_read(cell, &len);
nvmem_cell_put(cell);
if (IS_ERR(buf))
return PTR_ERR(buf);

View File

@ -202,7 +202,6 @@ static int ath_rx_edma_init(struct ath_softc *sc, int nbufs)
struct sk_buff *skb;
struct ath_rxbuf *bf;
int error = 0, i;
u32 size;
ath9k_hw_set_rx_bufsize(ah, common->rx_bufsize -
ah->caps.rx_status_len);
@ -212,8 +211,7 @@ static int ath_rx_edma_init(struct ath_softc *sc, int nbufs)
ath_rx_edma_init_queue(&sc->rx.rx_edma[ATH9K_RX_QUEUE_HP],
ah->caps.rx_hp_qdepth);
size = sizeof(struct ath_rxbuf) * nbufs;
bf = devm_kzalloc(sc->dev, size, GFP_KERNEL);
bf = devm_kcalloc(sc->dev, sizeof(struct ath_rxbuf), nbufs, GFP_KERNEL);
if (!bf)
return -ENOMEM;

View File

@ -1993,7 +1993,6 @@ void ath_txq_schedule(struct ath_softc *sc, struct ath_txq *txq)
ieee80211_txq_schedule_start(hw, txq->mac80211_qnum);
spin_lock_bh(&sc->chan_lock);
rcu_read_lock();
if (sc->cur_chan->stopped)
goto out;
@ -2011,7 +2010,6 @@ void ath_txq_schedule(struct ath_softc *sc, struct ath_txq *txq)
}
out:
rcu_read_unlock();
spin_unlock_bh(&sc->chan_lock);
ieee80211_txq_schedule_end(hw, txq->mac80211_qnum);
}
@ -2746,6 +2744,11 @@ void ath_tx_edma_tasklet(struct ath_softc *sc)
continue;
}
if (ts.qid >= ATH9K_NUM_TX_QUEUES) {
ath_dbg(common, XMIT, "invalid qid %d\n", ts.qid);
continue;
}
txq = &sc->tx.txq[ts.qid];
ath_txq_lock(sc, txq);

View File

@ -32,7 +32,7 @@ iwlwifi-objs += iwl-dbg-tlv.o
iwlwifi-objs += iwl-trans.o
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/dbg.o fw/dbg-old.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

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2025 Intel Corporation
* Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/module.h>
#include <linux/stringify.h>
@ -15,26 +15,26 @@
/* Lowest firmware API version supported */
#define IWL_22000_UCODE_API_MIN 77
/* Memory offsets and lengths */
#define IWL_22000_SMEM_OFFSET 0x400000
#define IWL_22000_SMEM_LEN 0xD0000
#define IWL_CC_A_FW_PRE "iwlwifi-cc-a0"
#define IWL_QU_B_HR_B_FW_PRE "iwlwifi-Qu-b0-hr-b0"
#define IWL_QU_C_HR_B_FW_PRE "iwlwifi-Qu-c0-hr-b0"
#define IWL_QUZ_A_HR_B_FW_PRE "iwlwifi-QuZ-a0-hr-b0"
#define IWL_CC_A_MODULE_FIRMWARE(api) \
IWL_CC_A_FW_PRE "-" __stringify(api) ".ucode"
#define IWL_QU_B_HR_B_MODULE_FIRMWARE(api) \
IWL_QU_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_QU_C_HR_B_MODULE_FIRMWARE(api) \
IWL_QU_C_HR_B_FW_PRE "-" __stringify(api) ".ucode"
static const struct iwl_family_base_params iwl_22000_base = {
.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,
.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,
.mac_addr_from_csr = 0x380,
@ -113,4 +113,7 @@ const char iwl_ax201_killer_1650s_name[] =
const char iwl_ax201_killer_1650i_name[] =
"Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201NGW)";
MODULE_FIRMWARE(IWL_QU_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_QUZ_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_CC_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX));

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2012-2014, 2018-2020, 2023, 2025 Intel Corporation
* Copyright (C) 2012-2014, 2018-2020, 2023, 2025-2026 Intel Corporation
* Copyright (C) 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015 Intel Deutschland GmbH
*/
@ -53,10 +53,7 @@ static const struct iwl_family_base_params iwl7000_base = {
.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,

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2014, 2018-2020, 2023, 2025 Intel Corporation
* Copyright (C) 2014, 2018-2020, 2023, 2025-2026 Intel Corporation
* Copyright (C) 2014-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016 Intel Deutschland GmbH
*/
@ -39,10 +39,7 @@ static const struct iwl_family_base_params iwl8000_base = {
.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,
.nvm_hw_section_num = 10,

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2021, 2023, 2025 Intel Corporation
* Copyright (C) 2018-2021, 2023, 2025-2026 Intel Corporation
*/
#include <linux/module.h>
#include <linux/stringify.h>
@ -30,10 +30,7 @@ static const struct iwl_family_base_params iwl9000_base = {
.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,
.smem_offset = IWL9000_SMEM_OFFSET,

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2025 Intel Corporation
* Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/module.h>
#include <linux/stringify.h>
@ -15,21 +15,30 @@
/* Lowest firmware API version supported */
#define IWL_AX210_UCODE_API_MIN 89
/* Memory offsets and lengths */
#define IWL_AX210_SMEM_OFFSET 0x400000
#define IWL_AX210_SMEM_LEN 0xD0000
#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_MA_A_GF_A_FW_PRE "iwlwifi-ma-a0-gf-a0"
#define IWL_MA_B_GF_A_FW_PRE "iwlwifi-ma-b0-gf-a0"
#define IWL_SO_A_GF4_A_FW_PRE "iwlwifi-so-a0-gf4-a0"
#define IWL_MA_A_GF4_A_FW_PRE "iwlwifi-ma-a0-gf4-a0"
#define IWL_MA_B_GF4_A_FW_PRE "iwlwifi-ma-b0-gf4-a0"
#define IWL_SO_A_HR_B_FW_PRE "iwlwifi-so-a0-hr-b0"
#define IWL_MA_A_HR_B_FW_PRE "iwlwifi-ma-a0-hr-b0"
#define IWL_MA_B_HR_B_FW_PRE "iwlwifi-ma-b0-hr-b0"
#define IWL_SO_A_HR_B_MODULE_FIRMWARE(api) \
IWL_SO_A_HR_B_FW_PRE "-" __stringify(api) ".ucode"
#define IWL_MA_A_HR_B_MODULE_FIRMWARE(api) \
IWL_MA_A_HR_B_FW_PRE "-" __stringify(api) ".ucode"
#define IWL_MA_B_HR_B_MODULE_FIRMWARE(api) \
IWL_MA_B_HR_B_FW_PRE "-" __stringify(api) ".ucode"
static const struct iwl_family_base_params iwl_ax210_base = {
.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,
.smem_offset = IWL_AX210_SMEM_OFFSET,
.smem_len = IWL_AX210_SMEM_LEN,
.features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM,
.apmg_not_supported = true,
.mac_addr_from_csr = 0x380,
@ -121,3 +130,14 @@ const struct iwl_mac_cfg iwl_ma_mac_cfg = {
.integrated = true,
.umac_prph_offset = 0x300000
};
IWL_FW_AND_PNVM(IWL_SO_A_GF_A_FW_PRE, IWL_AX210_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_TY_A_GF_A_FW_PRE, IWL_AX210_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_MA_A_GF_A_FW_PRE, IWL_AX210_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_MA_B_GF_A_FW_PRE, IWL_AX210_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_SO_A_GF4_A_FW_PRE, IWL_AX210_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_MA_A_GF4_A_FW_PRE, IWL_AX210_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_MA_B_GF4_A_FW_PRE, IWL_AX210_UCODE_API_MAX);
MODULE_FIRMWARE(IWL_SO_A_HR_B_MODULE_FIRMWARE(IWL_AX210_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_MA_A_HR_B_MODULE_FIRMWARE(IWL_AX210_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_MA_B_HR_B_MODULE_FIRMWARE(IWL_AX210_UCODE_API_MAX));

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2025 Intel Corporation
* Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/module.h>
#include <linux/stringify.h>
@ -10,26 +10,17 @@
#include "fw/api/txq.h"
/* Highest firmware core release supported */
#define IWL_BZ_UCODE_CORE_MAX 102
#define IWL_BZ_UCODE_CORE_MAX 105
/* Lowest firmware API version supported */
#define IWL_BZ_UCODE_API_MIN 100
/* Memory offsets and lengths */
#define IWL_BZ_SMEM_OFFSET 0x400000
#define IWL_BZ_SMEM_LEN 0xD0000
/* Lowest firmware core release supported */
#define IWL_BZ_UCODE_CORE_MIN 101
static const struct iwl_family_base_params iwl_bz_base = {
.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,
.smem_offset = IWL_BZ_SMEM_OFFSET,
.smem_len = IWL_BZ_SMEM_LEN,
.apmg_not_supported = true,
.mac_addr_from_csr = 0x30,
.d3_debug_data_base_addr = 0x401000,
@ -69,7 +60,7 @@ static const struct iwl_family_base_params iwl_bz_base = {
},
.features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM,
.ucode_api_max = ENCODE_CORE_AS_API(IWL_BZ_UCODE_CORE_MAX),
.ucode_api_min = IWL_BZ_UCODE_API_MIN,
.ucode_api_min = ENCODE_CORE_AS_API(IWL_BZ_UCODE_CORE_MIN),
};
const struct iwl_mac_cfg iwl_bz_mac_cfg = {

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include <linux/module.h>
#include <linux/stringify.h>
@ -9,28 +9,19 @@
#include "fw/api/txq.h"
/* Highest firmware core release supported */
#define IWL_DR_UCODE_CORE_MAX 102
#define IWL_DR_UCODE_CORE_MAX 105
/* Lowest firmware API version supported */
#define IWL_DR_UCODE_API_MIN 100
/* Memory offsets and lengths */
#define IWL_DR_SMEM_OFFSET 0x400000
#define IWL_DR_SMEM_LEN 0xD0000
/* Lowest firmware core release supported */
#define IWL_DR_UCODE_CORE_MIN 101
#define IWL_DR_A_PE_A_FW_PRE "iwlwifi-dr-a0-pe-a0"
static const struct iwl_family_base_params iwl_dr_base = {
.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,
.smem_offset = IWL_DR_SMEM_OFFSET,
.smem_len = IWL_DR_SMEM_LEN,
.apmg_not_supported = true,
.mac_addr_from_csr = 0x30,
.d3_debug_data_base_addr = 0x401000,
@ -70,7 +61,7 @@ static const struct iwl_family_base_params iwl_dr_base = {
},
.features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM,
.ucode_api_max = ENCODE_CORE_AS_API(IWL_DR_UCODE_CORE_MAX),
.ucode_api_min = IWL_DR_UCODE_API_MIN,
.ucode_api_min = ENCODE_CORE_AS_API(IWL_DR_UCODE_CORE_MIN),
};
const struct iwl_mac_cfg iwl_dr_mac_cfg = {

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2025 Intel Corporation
* Copyright (C) 2018-2026 Intel Corporation
*/
#include "iwl-config.h"
@ -11,9 +11,6 @@
#define IWL_GL_B_FM_B_FW_PRE "iwlwifi-gl-b0-fm-b0"
#define IWL_GL_C_FM_C_FW_PRE "iwlwifi-gl-c0-fm-c0"
/* NVM versions */
#define IWL_FM_NVM_VERSION 0x0a1d
#define IWL_DEVICE_FM \
.ht_params = { \
.stbc = true, \
@ -27,7 +24,6 @@
.uhb_supported = true, \
.eht_supported = true, \
.num_rbds = IWL_NUM_RBDS_EHT, \
.nvm_ver = IWL_FM_NVM_VERSION, \
.nvm_type = IWL_NVM_EXT
const struct iwl_rf_cfg iwl_rf_fm = {
@ -51,6 +47,8 @@ const char iwl_killer_be1790s_name[] =
"Killer(R) Wi-Fi 7 BE1790s 320MHz Wireless Network Adapter (BE401D2W)";
const char iwl_killer_be1790i_name[] =
"Killer(R) Wi-Fi 7 BE1790i 320MHz Wireless Network Adapter (BE401NGW)";
const char iwl_killer_be1730x_name[] =
"Killer(TM) Wi-Fi 7 BE1730x 160MHz Wireless Network Adapter (BE202)";
const char iwl_be201_name[] = "Intel(R) Wi-Fi 7 BE201 320MHz";
const char iwl_be200_name[] = "Intel(R) Wi-Fi 7 BE200 320MHz";

View File

@ -11,13 +11,6 @@
/* Lowest firmware API version supported */
#define IWL_GF_UCODE_API_MIN 100
#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_MA_A_GF_A_FW_PRE "iwlwifi-ma-a0-gf-a0"
#define IWL_MA_B_GF_A_FW_PRE "iwlwifi-ma-b0-gf-a0"
#define IWL_SO_A_GF4_A_FW_PRE "iwlwifi-so-a0-gf4-a0"
#define IWL_MA_A_GF4_A_FW_PRE "iwlwifi-ma-a0-gf4-a0"
#define IWL_MA_B_GF4_A_FW_PRE "iwlwifi-ma-b0-gf4-a0"
#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_SC_A_GF_A_FW_PRE "iwlwifi-sc-a0-gf-a0"
@ -35,9 +28,6 @@
#define IWL_SC_A_GF4_A_MODULE_FIRMWARE(api) \
IWL_SC_A_GF4_A_FW_PRE "-" __stringify(api) ".ucode"
/* NVM versions */
#define IWL_GF_NVM_VERSION 0x0a1d
const struct iwl_rf_cfg iwl_rf_gf = {
.uhb_supported = true,
.led_mode = IWL_LED_RF_STATE,
@ -49,7 +39,6 @@ const struct iwl_rf_cfg iwl_rf_gf = {
.ht40_bands = BIT(NL80211_BAND_2GHZ) |
BIT(NL80211_BAND_5GHZ),
},
.nvm_ver = IWL_GF_NVM_VERSION,
.nvm_type = IWL_NVM_EXT,
.num_rbds = IWL_NUM_RBDS_HE,
.ucode_api_min = IWL_GF_UCODE_API_MIN,
@ -73,12 +62,6 @@ const char iwl_ax210_name[] = "Intel(R) Wi-Fi 6E AX210 160MHz";
const char iwl_ax211_name[] = "Intel(R) Wi-Fi 6E AX211 160MHz";
const char iwl_ax411_name[] = "Intel(R) Wi-Fi 6E AX411 160MHz";
IWL_FW_AND_PNVM(IWL_SO_A_GF_A_FW_PRE, IWL_GF_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_TY_A_GF_A_FW_PRE, IWL_GF_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_MA_A_GF_A_FW_PRE, IWL_GF_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_MA_B_GF_A_FW_PRE, IWL_GF_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_MA_A_GF4_A_FW_PRE, IWL_GF_UCODE_API_MAX);
IWL_FW_AND_PNVM(IWL_MA_B_GF4_A_FW_PRE, IWL_GF_UCODE_API_MAX);
MODULE_FIRMWARE(IWL_BZ_A_GF_A_MODULE_FIRMWARE(IWL_GF_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_BZ_A_GF4_A_MODULE_FIRMWARE(IWL_GF_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_SC_A_GF_A_MODULE_FIRMWARE(IWL_GF_UCODE_API_MAX));

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2025 Intel Corporation
* Copyright (C) 2018-2026 Intel Corporation
*/
#include "iwl-config.h"
@ -11,28 +11,10 @@
/* Lowest firmware API version supported */
#define IWL_HR_UCODE_API_MIN 100
#define IWL_QU_B_HR_B_FW_PRE "iwlwifi-Qu-b0-hr-b0"
#define IWL_QU_C_HR_B_FW_PRE "iwlwifi-Qu-c0-hr-b0"
#define IWL_QUZ_A_HR_B_FW_PRE "iwlwifi-QuZ-a0-hr-b0"
#define IWL_SO_A_HR_B_FW_PRE "iwlwifi-so-a0-hr-b0"
#define IWL_MA_A_HR_B_FW_PRE "iwlwifi-ma-a0-hr-b0"
#define IWL_MA_B_HR_B_FW_PRE "iwlwifi-ma-b0-hr-b0"
#define IWL_BZ_A_HR_B_FW_PRE "iwlwifi-bz-a0-hr-b0"
#define IWL_SC_A_HR_A_FW_PRE "iwlwifi-sc-a0-hr-b0"
#define IWL_SC_A_HR_B_FW_PRE "iwlwifi-sc-a0-hr-b0"
#define IWL_QU_B_HR_B_MODULE_FIRMWARE(api) \
IWL_QU_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_QU_C_HR_B_MODULE_FIRMWARE(api) \
IWL_QU_C_HR_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_MA_A_HR_B_FW_MODULE_FIRMWARE(api) \
IWL_MA_A_HR_B_FW_PRE "-" __stringify(api) ".ucode"
#define IWL_MA_B_HR_B_FW_MODULE_FIRMWARE(api) \
IWL_MA_B_HR_B_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_SC_A_HR_A_FW_MODULE_FIRMWARE(api) \
@ -40,9 +22,6 @@
#define IWL_SC_A_HR_B_FW_MODULE_FIRMWARE(api) \
IWL_SC_A_HR_B_FW_PRE "-" __stringify(api) ".ucode"
/* NVM versions */
#define IWL_HR_NVM_VERSION 0x0a1d
#define IWL_DEVICE_HR \
.led_mode = IWL_LED_RF_STATE, \
.non_shared_ant = ANT_B, \
@ -54,7 +33,6 @@
BIT(NL80211_BAND_5GHZ), \
}, \
.num_rbds = IWL_NUM_RBDS_HE, \
.nvm_ver = IWL_HR_NVM_VERSION, \
.nvm_type = IWL_NVM_EXT, \
.ucode_api_min = IWL_HR_UCODE_API_MIN, \
.ucode_api_max = IWL_HR_UCODE_API_MAX
@ -78,12 +56,6 @@ 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";
MODULE_FIRMWARE(IWL_QU_B_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_QU_C_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_QUZ_A_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_SO_A_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_MA_A_HR_B_FW_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_MA_B_HR_B_FW_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_BZ_A_HR_B_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_SC_A_HR_A_FW_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX));
MODULE_FIRMWARE(IWL_SC_A_HR_B_FW_MODULE_FIRMWARE(IWL_HR_UCODE_API_MAX));

View File

@ -1,10 +1,28 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2025 Intel Corporation
* Copyright (C) 2025-2026 Intel Corporation
*/
#include "iwl-config.h"
/* currently iwl_rf_wh/iwl_rf_wh_160mhz are just defines for the FM ones */
#define IWL_DEVICE_PE \
.ht_params = { \
.stbc = true, \
.ldpc = true, \
.ht40_bands = BIT(NL80211_BAND_2GHZ) | \
BIT(NL80211_BAND_5GHZ), \
}, \
.led_mode = IWL_LED_RF_STATE, \
.non_shared_ant = ANT_B, \
.vht_mu_mimo_supported = true, \
.uhb_supported = true, \
.eht_supported = true, \
.uhr_supported = true, \
.num_rbds = IWL_NUM_RBDS_EHT, \
.nvm_type = IWL_NVM_EXT
const struct iwl_rf_cfg iwl_rf_pe = {
IWL_DEVICE_PE,
};
const char iwl_killer_bn1850w2_name[] =
"Killer(R) Wi-Fi 8 BN1850w2 320MHz Wireless Network Adapter (BN201.D2W)";

View File

@ -1,12 +1,9 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2025 Intel Corporation
* Copyright (C) 2025-2026 Intel Corporation
*/
#include "iwl-config.h"
/* NVM versions */
#define IWL_WH_NVM_VERSION 0x0a1d
#define IWL_DEVICE_WH \
.ht_params = { \
.stbc = true, \
@ -19,7 +16,6 @@
.vht_mu_mimo_supported = true, \
.uhb_supported = true, \
.num_rbds = IWL_NUM_RBDS_EHT, \
.nvm_ver = IWL_WH_NVM_VERSION, \
.nvm_type = IWL_NVM_EXT
/* currently iwl_rf_wh/iwl_rf_wh_160mhz are just defines for the FM ones */
@ -33,6 +29,8 @@ const char iwl_killer_be1775s_name[] =
"Killer(R) Wi-Fi 7 BE1775s 320MHz Wireless Network Adapter (BE211D2W)";
const char iwl_killer_be1775i_name[] =
"Killer(R) Wi-Fi 7 BE1775i 320MHz Wireless Network Adapter (BE211NGW)";
const char iwl_killer_be1735x_name[] =
"Killer(TM) Wi-Fi 7 BE1735x 160MHz Wireless Network Adapter (BE213)";
const char iwl_be211_name[] = "Intel(R) Wi-Fi 7 BE211 320MHz";
const char iwl_be213_name[] = "Intel(R) Wi-Fi 7 BE213 160MHz";

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2025 Intel Corporation
* Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/module.h>
#include <linux/stringify.h>
@ -10,17 +10,10 @@
#include "fw/api/txq.h"
/* Highest firmware core release supported */
#define IWL_SC_UCODE_CORE_MAX 102
#define IWL_SC_UCODE_CORE_MAX 105
/* Lowest firmware API version supported */
#define IWL_SC_UCODE_API_MIN 100
/* NVM versions */
#define IWL_SC_NVM_VERSION 0x0a1d
/* Memory offsets and lengths */
#define IWL_SC_SMEM_OFFSET 0x400000
#define IWL_SC_SMEM_LEN 0xD0000
/* Lowest firmware core release supported */
#define IWL_SC_UCODE_CORE_MIN 101
#define IWL_SC_A_FM_B_FW_PRE "iwlwifi-sc-a0-fm-b0"
#define IWL_SC_A_FM_C_FW_PRE "iwlwifi-sc-a0-fm-c0"
@ -31,14 +24,9 @@
static const struct iwl_family_base_params iwl_sc_base = {
.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,
.smem_offset = IWL_SC_SMEM_OFFSET,
.smem_len = IWL_SC_SMEM_LEN,
.apmg_not_supported = true,
.mac_addr_from_csr = 0x30,
.d3_debug_data_base_addr = 0x401000,
@ -78,7 +66,7 @@ static const struct iwl_family_base_params iwl_sc_base = {
},
.features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM,
.ucode_api_max = ENCODE_CORE_AS_API(IWL_SC_UCODE_CORE_MAX),
.ucode_api_min = IWL_SC_UCODE_API_MIN,
.ucode_api_min = ENCODE_CORE_AS_API(IWL_SC_UCODE_CORE_MIN),
};
const struct iwl_mac_cfg iwl_sc_mac_cfg = {

View File

@ -2,7 +2,7 @@
/*
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2022, 2024-2025 Intel Corporation
* Copyright (C) 2018-2022, 2024-2026 Intel Corporation
*/
#ifndef __iwl_fw_api_commands_h__
#define __iwl_fw_api_commands_h__
@ -22,7 +22,6 @@
* &enum iwl_data_path_subcmd_ids
* @SCAN_GROUP: scan group, uses command IDs from
* &enum iwl_scan_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
* @BT_COEX_GROUP: bt coex group, uses command IDs from
@ -43,7 +42,6 @@ enum iwl_mvm_command_groups {
PHY_OPS_GROUP = 0x4,
DATA_PATH_GROUP = 0x5,
SCAN_GROUP = 0x6,
NAN_GROUP = 0x7,
LOCATION_GROUP = 0x8,
BT_COEX_GROUP = 0x9,
PROT_OFFLOAD_GROUP = 0xb,
@ -59,8 +57,7 @@ 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 or &struct iwl_alive_ntf_v7.
* &struct iwl_alive_ntf_v3 or &struct iwl_alive_ntf_v7.
*/
UCODE_ALIVE_NTFY = 0x1,
@ -386,7 +383,7 @@ enum iwl_legacy_cmds {
* @STATISTICS_NOTIFICATION:
* one of &struct iwl_notif_statistics_v10,
* &struct iwl_notif_statistics_v11,
* &struct iwl_notif_statistic,
* &struct iwl_notif_statistics,
* &struct iwl_statistics_operational_ntfy_ver_14
* &struct iwl_statistics_operational_ntfy
*/
@ -560,7 +557,7 @@ enum iwl_legacy_cmds {
WOWLAN_CONFIGURATION = 0xe1,
/**
* @WOWLAN_TSC_RSC_PARAM: &struct iwl_wowlan_rsc_tsc_params_cmd_v4,
* @WOWLAN_TSC_RSC_PARAM: &struct iwl_wowlan_rsc_tsc_params_cmd_ver_2,
* &struct iwl_wowlan_rsc_tsc_params_cmd
*/
WOWLAN_TSC_RSC_PARAM = 0xe2,
@ -653,7 +650,7 @@ enum iwl_system_subcmd_ids {
enum iwl_statistics_subcmd_ids {
/**
* @STATISTICS_OPER_NOTIF: Notification about operational
* statistics &struct iwl_system_statistics_notif_oper
* statistics &struct iwl_system_statistics_notif_oper_v3
*/
STATISTICS_OPER_NOTIF = 0x0,

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
* Copyright (C) 2012-2014, 2018-2022 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
@ -91,6 +91,13 @@ enum iwl_data_path_subcmd_ids {
*/
SEC_KEY_CMD = 0x18,
/**
* @RSC_NOTIF: notification to update each Rx queue with the RSC. This
* notification is sent after resume and uses
* &struct iwl_wowlan_all_rsc_tsc_v5.
*/
RSC_NOTIF = 0xF1,
/**
* @ESR_MODE_NOTIF: notification to recommend/force a wanted esr mode,
* uses &struct iwl_esr_mode_notif or &struct iwl_esr_mode_notif_v1

View File

@ -2,7 +2,7 @@
/*
* Copyright (C) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2022 Intel Corporation
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#ifndef __iwl_fw_api_location_h__
#define __iwl_fw_api_location_h__
@ -81,12 +81,53 @@ enum iwl_location_subcmd_ids {
* @TOF_RANGE_RESPONSE_NOTIF: ranging response, using one of
* &struct iwl_tof_range_rsp_ntfy_v5,
* &struct iwl_tof_range_rsp_ntfy_v6,
* &struct iwl_tof_range_rsp_ntfy_v7 or
* &struct iwl_tof_range_rsp_ntfy_v8
* &struct iwl_tof_range_rsp_ntfy_v7,
* &struct iwl_tof_range_rsp_ntfy_v9 or
* &struct iwl_tof_range_rsp_ntfy
*/
TOF_RANGE_RESPONSE_NOTIF = 0xFF,
};
/**
* 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: 20 MHz
* @IWL_LOCATION_BW_40MHZ: 40 MHz
* @IWL_LOCATION_BW_80MHZ: 80 MHz
* @IWL_LOCATION_BW_160MHZ: 160 MHz
* @IWL_LOCATION_BW_320MHZ: 320 MHz
*/
enum iwl_location_bw {
IWL_LOCATION_BW_20MHZ,
IWL_LOCATION_BW_40MHZ,
IWL_LOCATION_BW_80MHZ,
IWL_LOCATION_BW_160MHZ,
IWL_LOCATION_BW_320MHZ,
};
/**
* enum iwl_location_format_bw - format/BW encoding
* @IWL_LOCATION_FMT_BW_FORMAT: &enum iwl_location_frame_format
* @IWL_LOCATION_FMT_BW_BANDWIDTH: &enum iwl_location_bw
*/
enum iwl_location_format_bw {
IWL_LOCATION_FMT_BW_FORMAT = 0x0f,
IWL_LOCATION_FMT_BW_BANDWIDTH = 0xf0,
};
/**
* struct iwl_tof_config_cmd - ToF configuration
* @tof_disabled: indicates if ToF is disabled (or not)
@ -263,8 +304,7 @@ struct iwl_tof_responder_config_cmd_v6 {
* 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.
* @format_bw: &enum iwl_location_format_bw
* @rate: current AP rate
* @channel_num: current AP Channel
* @ctrl_ch_position: coding of the control channel position relative to
@ -302,8 +342,7 @@ struct iwl_tof_responder_config_cmd_v7 {
* 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.
* @format_bw: &enum iwl_location_format_bw
* @rate: current AP rate
* @channel_num: current AP Channel
* @ctrl_ch_position: coding of the control channel position relative to
@ -348,8 +387,7 @@ struct iwl_tof_responder_config_cmd_v8 {
* 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.
* @format_bw: &enum iwl_location_format_bw
* @bss_color: current AP bss_color
* @channel_num: current AP Channel
* @ctrl_ch_position: coding of the control channel position relative to
@ -400,8 +438,7 @@ struct iwl_tof_responder_config_cmd_v9 {
* struct iwl_tof_responder_config_cmd - ToF AP mode
* @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.
* @format_bw: &enum iwl_location_format_bw
* @bss_color: current AP bss_color
* @channel_num: current AP Channel
* @ctrl_ch_position: coding of the control channel position relative to
@ -676,44 +713,13 @@ struct iwl_tof_range_req_ap_entry_v3 {
__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
* @IWL_LOCATION_BW_160MHZ: 160MHz
*/
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.
* @format_bw: &enum iwl_location_format_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
@ -763,8 +769,7 @@ enum iwl_location_cipher {
* 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.
* @format_bw: &enum iwl_location_format_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
@ -810,8 +815,7 @@ struct iwl_tof_range_req_ap_entry_v6 {
* 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.
* @format_bw: &enum iwl_location_format_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
@ -868,8 +872,7 @@ struct iwl_tof_range_req_ap_entry_v7 {
* 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.
* @format_bw: &enum iwl_location_format_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
@ -939,8 +942,7 @@ struct iwl_tof_range_req_ap_entry_v8 {
* 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.
* @format_bw: &enum iwl_location_format_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
@ -1024,8 +1026,7 @@ struct iwl_tof_range_req_ap_entry_v9 {
* @initiator_ap_flags: see &enum iwl_initiator_ap_flags.
* @band: 0 for 5.2 GHz, 1 for 2.4 GHz, 2 for 6GHz
* @channel_num: AP Channel number
* @format_bw: bits 0 - 3: &enum iwl_location_frame_format.
* bits 4 - 7: &enum iwl_location_bw.
* @format_bw: &enum iwl_location_format_bw
* @ctrl_ch_position: Coding of the control channel position relative to the
* center frequency, see iwl_mvm_get_ctrl_pos().
* @bssid: AP's BSSID

View File

@ -8,6 +8,7 @@
#define __iwl_fw_api_mac_cfg_h__
#include "mac.h"
#include "phy-ctxt.h"
/**
* enum iwl_mac_conf_subcmd_ids - mac configuration command IDs
@ -71,6 +72,23 @@ enum iwl_mac_conf_subcmd_ids {
* @NAN_CFG_CMD: &struct iwl_nan_config_cmd
*/
NAN_CFG_CMD = 0x12,
/**
* @NAN_SCHEDULE_CMD: &struct iwl_nan_schedule_cmd
*/
NAN_SCHEDULE_CMD = 0x13,
/**
* @NAN_PEER_CMD: &struct iwl_nan_peer_cmd
*/
NAN_PEER_CMD = 0x14,
/**
* @NAN_ULW_ATTR_NOTIF: &struct iwl_nan_ulw_attr_notif
*/
NAN_ULW_ATTR_NOTIF = 0xf2,
/**
* @NAN_SCHED_UPDATE_COMPLETED_NOTIF:
* &struct iwl_nan_sched_update_completed_notif
*/
NAN_SCHED_UPDATE_COMPLETED_NOTIF = 0xf3,
/**
* @NAN_DW_END_NOTIF: &struct iwl_nan_dw_end_notif
*/
@ -536,12 +554,20 @@ enum iwl_link_ctx_protection_flags {
* radar pulses).
* @LINK_FLG_NDP_FEEDBACK_ENABLED: mark support for NDP feedback and change
* of threshold
* @LINK_FLG_NPCA: NPCA enabled
* @LINK_FLG_DPS: AP is a DPS assisting AP
* @LINK_FLG_MLPM: AP supports UHR multi-link PM
* @LINK_FLG_DUO: AP supports UHR DUO
*/
enum iwl_link_ctx_flags {
LINK_FLG_BSS_COLOR_DIS = BIT(0),
LINK_FLG_MU_EDCA_CW = BIT(1),
LINK_FLG_RU_2MHZ_BLOCK = BIT(2),
LINK_FLG_NDP_FEEDBACK_ENABLED = BIT(3),
LINK_FLG_NPCA = BIT(4),
LINK_FLG_DPS = BIT(6),
LINK_FLG_MLPM = BIT(7),
LINK_FLG_DUO = BIT(8),
}; /* LINK_CONTEXT_FLAG_E_VER_1 */
/**
@ -578,7 +604,7 @@ enum iwl_npca_flags {
* @initial_qsrc: Indicates the value that is used to initialize the
* EDCAF QSRC[AC] variables
* @min_dur_threshold: minimum PPDU time to switch to the non-primary
* NPCA channel (usec)
* NPCA channel (spec representation)
* @flags: NPCA flags, see &enum iwl_npca_flags
* @reserved: reserved for alignment purposes
*/
@ -726,6 +752,12 @@ struct iwl_link_config_cmd {
* @STATION_TYPE_NAN_PEER_NDI: NAN data peer station type. A station
* of this type can have any number of links (even none) set in the
* link_mask. (Supported since version 3.)
* @STATION_TYPE_NAN_BCAST: NAN station used for synchronization and
* discovery. No queue is associated with this station.
* @STATION_TYPE_NAN_MGMT: NAN station used for NAN management frames, e.g.,
* SDFs and NAFs.
* @STATION_TYPE_NAN_MCAST_DATA: NAN station used for multicast NAN data
* frames.
* @STATION_TYPE_MAX: maximum number of FW station types
* @STATION_TYPE_AUX: aux sta. In the FW there is no need for a special type
* for the aux sta, so this type is only for driver - internal use.
@ -736,6 +768,9 @@ enum iwl_fw_sta_type {
STATION_TYPE_MCAST,
STATION_TYPE_NAN_PEER_NMI,
STATION_TYPE_NAN_PEER_NDI,
STATION_TYPE_NAN_BCAST,
STATION_TYPE_NAN_MGMT,
STATION_TYPE_NAN_MCAST_DATA,
STATION_TYPE_MAX,
STATION_TYPE_AUX = STATION_TYPE_MAX /* this doesn't exist in FW */
}; /* STATION_TYPE_E_VER_1, _VER_2 */
@ -873,7 +908,9 @@ struct iwl_sta_cfg_cmd_v2 {
* ( STA_CONFIG_CMD = 0xA )
*
* @sta_id: index of station in uCode's station table
* @link_mask: bitmap of link FW IDs used with this STA
* @link_mask: bitmap of link FW IDs used with this STA. Should be set to 0
* for STATION_TYPE_NAN_BCAST and STATION_TYPE_NAN_MGMT as they are not
* associated with any link added by the driver.
* @peer_mld_address: the peers mld address
* @reserved_for_peer_mld_address: reserved
* @peer_link_address: the address of the link that is used to communicate
@ -908,7 +945,8 @@ struct iwl_sta_cfg_cmd_v2 {
* @mic_prep_pad_delay: MIC prep time padding
* @mic_compute_pad_delay: MIC compute time padding
* @nmi_sta_id: for an NDI peer STA, the NMI peer STA ID it relates to
* @ndi_local_addr: for an NDI peer STA, the local NDI interface MAC address
* @ndi_local_addr: for an NDI peer STA or NAN multicast data station,
* the local NDI interface MAC address
* @reserved: Reserved for alignment
*/
struct iwl_sta_cfg_cmd {
@ -1204,7 +1242,8 @@ enum iwl_nan_flags {
* @discovery_beacon_interval: discovery beacon interval in TUs
* @cluster_id: lower last two bytes of the cluster ID, in case the local
* device starts a cluster
* @sta_id: station ID of the NAN station
* @sta_id: station ID of the NAN station. Used only in version 1, in version 2
* it is reserved.
* @hb_channel: channel for 5 GHz if the device supports operation on 5 GHz.
* Valid values are 44 and 149, which correspond to the 5 GHz channel, and
* 0 which means that NAN operation on the 5 GHz band is disabled.
@ -1242,7 +1281,102 @@ struct iwl_nan_config_cmd {
__le32 nan_attr_len;
__le32 nan_vendor_elems_len;
u8 beacon_data[];
} __packed; /* NAN_CONFIG_CMD_API_S_VER_1 */
} __packed; /* NAN_CONFIG_CMD_API_S_VER_1, NAN_CONFIG_CMD_API_S_VER_2 */
/**
* struct iwl_nan_schedule_cmd_v1 - NAN schedule command
* @channels: per channel information
* @channels.availability_map: bitmap of slots this channel is advertising
* availability on, will be ULW'ed out if no link/inactive link is
* referenced by the link ID below
* @channels.channel_entry: NAN channel entry descriptor
* @channels.link_id: FW link ID, or %0xFF for unset
* @channels.reserved: (reserved)
*/
struct iwl_nan_schedule_cmd_v1 {
struct {
__le32 availability_map;
u8 channel_entry[6];
u8 link_id;
u8 reserved;
} __packed channels[NUM_PHY_CTX];
} __packed; /* NAN_SCHEDULE_CMD_API_S_VER_1 */
#define IWL_MAX_AVAILABILITY_ATTR_LEN 54
/**
* struct iwl_nan_schedule_cmd - NAN schedule command
* @channels: per channel information
* @channels.availability_map: bitmap of slots this channel is advertising
* availability on, will be ULW'ed out if no link/inactive link is
* referenced by the link ID below
* @channels.channel_entry: NAN channel entry descriptor
* @channels.link_id: FW link ID, or %0xFF for unset
* @channels.reserved: (reserved)
* @avail_attr: NAN availability attribute information
* @avail_attr.attr_len: length of the availability attribute
* @avail_attr.reserved: reserved
* @avail_attr.attr: the availability attribute including the attribute header
* @deferred: true if the firmware should defer applying the schedule until
* notifying all peers. For a deferred schedule update, the firmware should
* send a notification to the driver after the new schedule is applied.
* @reserved: reserved
*/
struct iwl_nan_schedule_cmd {
struct {
__le32 availability_map;
u8 channel_entry[6];
u8 link_id;
u8 reserved;
} __packed channels[NUM_PHY_CTX];
struct {
u8 attr_len;
u8 reserved;
u8 attr[IWL_MAX_AVAILABILITY_ATTR_LEN];
} __packed avail_attr;
u8 deferred;
u8 reserved[3];
} __packed; /* NAN_SCHEDULE_CMD_API_S_VER_2 */
/**
* struct iwl_nan_peer_cmd - NAN peer command
* @nmi_sta_id: NAN management station ID
* @sequence_id: NAN Availability attribute sequence ID
* @committed_dw_info: committed DW info from the NAN Device
* Capability attribute
* @max_channel_switch_time: maximum channel switch time
* (in microseconds); 0 means unavailable
* @reserved: (reserved)
* @per_phy: per-PHY information for this peer, indexed by PHY ID
* @per_phy.availability_map: bitmap of which slots this peer
* is available in on this PHY. 0 indicates the this per-PHY entry
* is unused.
* @per_phy.channel_entry: the channel description the peer is using,
* used for comparisons in ULW management
* @per_phy.link_id: FW link ID, should be a valid id.
* @per_phy.map_id: map ID from peer's NAN Availability attributec
* @initial_ulw_size: size of the initial ULW blob
* @initial_ulw: initial ULW data from the peer
*/
struct iwl_nan_peer_cmd {
u8 nmi_sta_id;
u8 sequence_id;
__le16 committed_dw_info;
__le16 max_channel_switch_time;
__le16 reserved;
struct {
__le32 availability_map;
u8 channel_entry[6];
u8 link_id;
u8 map_id;
} __packed per_phy[NUM_PHY_CTX];
__le32 initial_ulw_size;
u8 initial_ulw[];
} __packed; /* NAN_PEER_SCHEDULE_CMD_API_S_VER_1 */
/**
* enum iwl_nan_cluster_notif_flags - flags for the cluster notification
@ -1280,4 +1414,44 @@ struct iwl_nan_dw_end_notif {
u8 reserved[3];
} __packed; /* NAN_DW_END_NTF_API_S_VER_1 */
#define IWL_NAN_MAX_ENDLESS_ULW_ATTR_LEN 48
/**
* struct iwl_nan_ulw_attr_notif - sent to notify the host of a change in the
* ULW attribute
*
* @attr_len: length of the ULW attribute in bytes
* @reserved: reserved
* @attr: the ULW attribute including the attribute header
*/
struct iwl_nan_ulw_attr_notif {
u8 attr_len;
u8 reserved[3];
u8 attr[IWL_NAN_MAX_ENDLESS_ULW_ATTR_LEN];
} __packed; /* NAN_ULW_ATTR_NOTIF_API_S_VER_1 */
/**
* enum iwl_nan_sched_update_status - NAN schedule update status
*
* @IWL_NAN_SCHED_UPDATE_SUCCESS: schedule update completed successfully
* @IWL_NAN_SCHED_UPDATE_FAILURE: schedule update failed. Currently not expected
* to happen, but reserved for future use.
*/
enum iwl_nan_sched_update_status {
IWL_NAN_SCHED_UPDATE_SUCCESS = 0,
IWL_NAN_SCHED_UPDATE_FAILURE = 1,
};
/**
* struct iwl_nan_sched_update_completed_notif - NAN schedule update completed
*
* @status: status of the schedule update operation. See
* &enum iwl_nan_sched_update_status
* @reserved: reserved
*/
struct iwl_nan_sched_update_completed_notif {
u8 status;
u8 reserved[3];
} __packed; /* NAN_SCHED_UPDATE_COMPLETED_NTF_API_S_VER_1 */
#endif /* __iwl_fw_api_mac_cfg_h__ */

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2012-2014, 2018-2025 Intel Corporation
* Copyright (C) 2012-2014, 2018-2026 Intel Corporation
* Copyright (C) 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
*/
@ -85,12 +85,13 @@ struct iwl_ltr_config_cmd {
* '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.
* (Not supported since version 3)
* @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
* detection enablement (Not supported since version 3)
* @POWER_FLAGS_ENABLE_SMPS_MSK: SMPS is allowed for this vif
*/
enum iwl_power_flags {
@ -175,9 +176,9 @@ struct iwl_device_power_cmd {
} __packed;
/**
* struct iwl_mac_power_cmd - New power command containing uAPSD support
* struct iwl_mac_power_cmd_v2 - power command V2 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
* @id_and_color: MAC context 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
@ -216,7 +217,7 @@ struct iwl_device_power_cmd {
* @limited_ps_threshold: (unused)
* @reserved: reserved (padding)
*/
struct iwl_mac_power_cmd {
struct iwl_mac_power_cmd_v2 {
/* CONTEXT_DESC_API_T_VER_1 */
__le32 id_and_color;
@ -242,6 +243,43 @@ struct iwl_mac_power_cmd {
u8 reserved;
} __packed; /* CLIENT_PM_POWER_TABLE_S_VER_1, VER_2 */
/**
* struct iwl_mac_power_cmd - power command
* MAC_PM_POWER_TABLE = 0xA9 (command, has simple generic response)
* @id_and_color: MAC context 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
* @lprx_rssi_threshold: Signal strength up to which LP RX can be enabled.
* Default: 80dbm
* @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).
* @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.
*/
struct iwl_mac_power_cmd {
/* CONTEXT_DESC_API_T_VER_1 */
__le32 id_and_color;
__le16 flags;
__le16 keep_alive_seconds;
__le32 rx_data_timeout;
__le32 tx_data_timeout;
u8 lprx_rssi_threshold;
u8 skip_dtim_periods;
u8 qndp_tid;
u8 uapsd_ac_flags;
} __packed; /* CLIENT_PM_POWER_TABLE_S_VER_3 */
/*
* struct iwl_uapsd_misbehaving_ap_notif - FW sends this notification when
* associated AP is identified as improperly implementing uAPSD protocol.

View File

@ -263,6 +263,9 @@ enum iwl_rx_mpdu_reorder_data {
enum iwl_rx_mpdu_phy_info {
IWL_RX_MPDU_PHY_EOF_INDICATION = BIT(0),
IWL_RX_MPDU_PHY_UEQM = BIT(1) | BIT(2),
IWL_RX_MPDU_PHY_2X_LDPC = BIT(3),
IWL_RX_MPDU_PHY_NPCA = BIT(4),
IWL_RX_MPDU_PHY_AMPDU = BIT(5),
IWL_RX_MPDU_PHY_AMPDU_TOGGLE = BIT(6),
IWL_RX_MPDU_PHY_SHORT_PREAMBLE = BIT(7),
@ -732,6 +735,8 @@ struct iwl_rx_mpdu_desc {
* RX_MPDU_RES_START_API_S_VER_4,
* RX_MPDU_RES_START_API_S_VER_5,
* RX_MPDU_RES_START_API_S_VER_6
* RX_MPDU_RES_START_API_S_VER_7
* RX_MPDU_RES_START_API_S_VER_8
*/
#define IWL_RX_DESC_SIZE_V1 offsetofend(struct iwl_rx_mpdu_desc, v1)
@ -1169,8 +1174,14 @@ struct iwl_he_tb_sigs {
#define OFDM_UCODE_TRIG_BASE_RX_BANDWIDTH 0x00007000
#define OFDM_UCODE_TRIG_BASE_PS160 0x00008000
#define OFDM_UCODE_EHT_TRIG_CONTROL_CHANNEL 0x000f0000
/* for UHR, since PE RF: */
#define OFDM_UCODE_TRIG_BASE_RX_MCS_UHR_AND_LATER 0x01f00000
#define OFDM_UCODE_RX_IS_DRU 0x02000000
#define OFDM_UCODE_RX_IS_2XLDPC_ENABLED 0x04000000
#define OFDM_UCODE_RX_DRU_DBW 0x60000000
__le32 tb_rx0;
#define OFDM_UCODE_TRIG_BASE_RX_MCS 0x0000000f
/* prior to PE RF, cannot have 5 bits needed for UHR: */
#define OFDM_UCODE_TRIG_BASE_RX_MCS_PRE_UHR 0x0000000f
#define OFDM_UCODE_TRIG_BASE_RX_DCM 0x00000010
#define OFDM_UCODE_TRIG_BASE_RX_GI_LTF_TYPE 0x00000060
#define OFDM_UCODE_TRIG_BASE_RX_NSTS 0x00000380
@ -1275,15 +1286,16 @@ struct iwl_uhr_sigs {
__le32 usig_a1;
#define OFDM_RX_FRAME_UHR_BSS_COLOR2 0x0000003f
__le32 usig_a1_uhr;
#define OFDM_RX_FRAME_UHR_PPDU_TYPE 0x00000003
#define OFDM_RX_FRAME_UHR_COBF_CSR_DISABLE 0x00000004
#define OFDM_RX_FRAME_UHR_PUNC_CHANNEL 0x000000f8
#define OFDM_RX_FRAME_UHR_USIG2_VALIDATE_B8 0x00000100
#define OFDM_RX_FRAME_UHR_SIG_MCS 0x00000600
#define OFDM_RX_FRAME_UHR_SIG_SYM_NUM 0x0000f800
#define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_1 0x000f0000
#define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_2 0x00f00000
#define OFDM_RX_FRAME_UHR_TRIG_USIG2_DISREGARD 0x1f000000
#define OFDM_RX_FRAME_UHR_PPDU_TYPE 0x00000003
#define OFDM_RX_FRAME_UHR_COBF_CSR_DISABLE 0x00000004
#define OFDM_RX_FRAME_UHR_PUNC_CHANNEL 0x000000f8
#define OFDM_RX_FRAME_UHR_USIG2_VALIDATE_B8 0x00000100
#define OFDM_RX_FRAME_UHR_SIG_MCS 0x00000600
#define OFDM_RX_FRAME_UHR_SIG_SYM_NUM 0x0000f800
#define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_1 0x000f0000
#define OFDM_RX_FRAME_UHR_TRIG_SPATIAL_REUSE_2 0x00f00000
#define OFDM_RX_FRAME_UHR_TRIG_USIG2_DISREGARD 0x1f000000
#define OFDM_RX_FRAME_UHR_USIG_CRC_OK 0x40000000
__le32 usig_a2_uhr;
#define OFDM_RX_FRAME_UHR_SPATIAL_REUSE 0x0000000f
#define OFDM_RX_FRAME_UHR_GI_LTF_TYPE 0x00000030
@ -1305,6 +1317,7 @@ struct iwl_uhr_sigs {
#define OFDM_RX_FRAME_UHR_CODING 0x00000200
#define OFDM_RX_FRAME_UHR_SPATIAL_CONFIG 0x00003c00
#define OFDM_RX_FRAME_UHR_STA_RU 0x003fc000
#define OFDM_RX_FRAME_UHR_STA_RU_P80 0x00004000
#define OFDM_RX_FRAME_UHR_STA_RU_PS160 0x00400000
#define OFDM_RX_FRAME_UHR_UEQM 0x00800000
#define OFDM_RX_FRAME_UHR_2XLDPC 0x01000000
@ -1330,7 +1343,12 @@ struct iwl_uhr_tb_sigs {
struct iwl_uhr_elr_sigs {
/* same as UHR above */
__le32 usig_a1, usig_a2_uhr;
__le32 usig_a1;
#define OFDM_RX_VECTOR_UHR_ELR_PPDU_TYPE 0x00000003
#define OFDM_RX_VECTOR_UHR_ELR_USIG_STA_ID 0x00001ffc
#define OFDM_RX_VECTOR_UHR_ELR_VALIDATE 0x0000e000
#define OFDM_RX_VECTOR_UHR_ELR_CRC_OK 0x00010000
__le32 usig_a2_uhr_elr;
#define OFDM_RX_VECTOR_UHR_ELR_VER_ID 0x00000007
#define OFDM_RX_VECTOR_UHR_ELR_UPLINK_FLAG 0x00000008
#define OFDM_RX_VECTOR_UHR_ELR_MCS 0x00000010

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2012-2014, 2018-2021, 2023, 2025 Intel Corporation
* Copyright (C) 2012-2014, 2018-2021, 2023, 2025-2026 Intel Corporation
* Copyright (C) 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@ -191,7 +191,6 @@ enum iwl_sta_sleep_flag {
#define STA_KEY_IDX_INVALID (0xff)
#define STA_KEY_MAX_DATA_KEY_NUM (4)
#define IWL_MAX_GLOBAL_KEYS (4)
#define IWL_MAX_NUM_IGTKS 2
#define STA_KEY_LEN_WEP40 (5)
#define STA_KEY_LEN_WEP104 (13)

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2012-2014, 2018, 2020-2021, 2023-2025 Intel Corporation
* Copyright (C) 2012-2014, 2018, 2020-2021, 2023-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@ -557,7 +557,7 @@ struct iwl_stats_ntfy_per_mac {
#define IWL_STATS_MAX_BW_INDEX 5
/**
* struct iwl_stats_ntfy_per_phy - per PHY statistics
* struct iwl_stats_ntfy_per_phy_v1 - per PHY statistics
* @channel_load: channel load
* @channel_load_by_us: device contribution to MCLM
* @channel_load_not_by_us: other devices' contribution to MCLM
@ -572,7 +572,7 @@ struct iwl_stats_ntfy_per_mac {
* per channel BW. note BACK counted as 1
* @last_tx_ch_width_indx: last txed frame channel width index
*/
struct iwl_stats_ntfy_per_phy {
struct iwl_stats_ntfy_per_phy_v1 {
__le32 channel_load;
__le32 channel_load_by_us;
__le32 channel_load_not_by_us;
@ -585,6 +585,67 @@ struct iwl_stats_ntfy_per_phy {
__le32 last_tx_ch_width_indx;
} __packed; /* STATISTICS_NTFY_PER_PHY_API_S_VER_1 */
/**
* struct iwl_stats_ntfy_coex - coex statistics
*
* @wifi_kill_cnt: count of wifi frames killed by BT
* @wifi_tx_cts_kill_cnt: count of wifi Tx CTS frames killed by BT
* @ttc2_ppdu_error_count: Count PPDU errors on TTC2 - BT Tx indication rises
* within wifi Tx packet on non-shared antenna and wifi is NOT killed by
* PTA/TCL.
* @trc2_ppdu_error_count: count PPDU errors on TRC2 - BT Rx indication rises
* within wifi Tx packet on non-shared antenna and wifi is NOT killed by
* PTA/TCL
* @rrc1_collision_count: count RRC1 - BT Rx indication rises within wifi Rx
* packet on shared antenna
* @rrc2_collision_count: count RRC2 - BT Rx indication rises within wifi Rx
* packet on non-shared antenna
* @rtc2_collision_count: count RTC2 - BT Tx indication rises within wifi Rx
* packet on non-shared antenna
* @reserved: reserved
*/
struct iwl_stats_ntfy_coex {
__le16 wifi_kill_cnt;
__le16 wifi_tx_cts_kill_cnt;
__le16 ttc2_ppdu_error_count;
__le16 trc2_ppdu_error_count;
__le16 rrc1_collision_count;
__le16 rrc2_collision_count;
__le16 rtc2_collision_count;
__le16 reserved;
} __packed; /* STATISTICS_FW_NTFY_COEX_TELEMETRY_API_S_VER_1 */
/**
* struct iwl_stats_ntfy_per_phy - per PHY statistics
* @channel_load: channel load
* @channel_load_by_us: device contribution to MCLM
* @channel_load_not_by_us: other devices' contribution to MCLM
* @clt: CLT HW timer (TIM_CH_LOAD2)
* @act: active accumulator SW
* @elp: elapsed time accumulator SW
* @rx_detected_per_ch_width: number of deferred TX per channel width,
* 0 - 20, 1/2/3 - 40/80/160
* @success_per_ch_width: number of frames that got ACK/BACK/CTS
* per channel BW. note, BACK counted as 1
* @fail_per_ch_width: number of frames that didn't get ACK/BACK/CTS
* per channel BW. note BACK counted as 1
* @last_tx_ch_width_indx: last txed frame channel width index
* @coex: coex related data
*/
struct iwl_stats_ntfy_per_phy {
__le32 channel_load;
__le32 channel_load_by_us;
__le32 channel_load_not_by_us;
__le32 clt;
__le32 act;
__le32 elp;
__le32 rx_detected_per_ch_width[IWL_STATS_MAX_BW_INDEX];
__le32 success_per_ch_width[IWL_STATS_MAX_BW_INDEX];
__le32 fail_per_ch_width[IWL_STATS_MAX_BW_INDEX];
__le32 last_tx_ch_width_indx;
struct iwl_stats_ntfy_coex coex;
} __packed; /* STATISTICS_NTFY_PER_PHY_API_S_VER_2 */
/* unknown channel load (due to not being active on channel) */
#define IWL_STATS_UNKNOWN_CHANNEL_LOAD 0xffffffff
@ -599,12 +660,27 @@ struct iwl_stats_ntfy_per_sta {
#define IWL_STATS_MAX_PHY_OPERATIONAL 3
/**
* struct iwl_system_statistics_notif_oper_v3 - statistics notification
*
* @time_stamp: time when the notification is sent from firmware
* @per_link: per link statistics, &struct iwl_stats_ntfy_per_link
* @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy_v1
* @per_sta: per sta statistics, &struct iwl_stats_ntfy_per_sta
*/
struct iwl_system_statistics_notif_oper_v3 {
__le32 time_stamp;
struct iwl_stats_ntfy_per_link per_link[IWL_FW_MAX_LINKS];
struct iwl_stats_ntfy_per_phy_v1 per_phy[IWL_STATS_MAX_PHY_OPERATIONAL];
struct iwl_stats_ntfy_per_sta per_sta[IWL_STATION_COUNT_MAX];
} __packed; /* STATISTICS_FW_NTFY_OPERATIONAL_API_S_VER_3 */
/**
* struct iwl_system_statistics_notif_oper - statistics notification
*
* @time_stamp: time when the notification is sent from firmware
* @per_link: per link statistics, &struct iwl_stats_ntfy_per_link
* @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy
* @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy_v1
* @per_sta: per sta statistics, &struct iwl_stats_ntfy_per_sta
*/
struct iwl_system_statistics_notif_oper {
@ -612,7 +688,7 @@ struct iwl_system_statistics_notif_oper {
struct iwl_stats_ntfy_per_link per_link[IWL_FW_MAX_LINKS];
struct iwl_stats_ntfy_per_phy per_phy[IWL_STATS_MAX_PHY_OPERATIONAL];
struct iwl_stats_ntfy_per_sta per_sta[IWL_STATION_COUNT_MAX];
} __packed; /* STATISTICS_FW_NTFY_OPERATIONAL_API_S_VER_3 */
} __packed; /* STATISTICS_FW_NTFY_OPERATIONAL_API_S_VER_4 */
/**
* struct iwl_system_statistics_part1_notif_oper - part1 stats notification
@ -642,7 +718,7 @@ struct iwl_system_statistics_end_notif {
* @hdr: general statistics header
* @flags: bitmap of possible notification structures
* @per_mac: per mac statistics, &struct iwl_stats_ntfy_per_mac
* @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy
* @per_phy: per phy statistics, &struct iwl_stats_ntfy_per_phy_v1
* @per_sta: per sta statistics, &struct iwl_stats_ntfy_per_sta
* @rx_time: rx time
* @tx_time: usec the radio is transmitting.
@ -653,7 +729,7 @@ struct iwl_statistics_operational_ntfy {
struct iwl_statistics_ntfy_hdr hdr;
__le32 flags;
struct iwl_stats_ntfy_per_mac per_mac[MAC_INDEX_AUX];
struct iwl_stats_ntfy_per_phy per_phy[IWL_STATS_MAX_PHY_OPERATIONAL];
struct iwl_stats_ntfy_per_phy_v1 per_phy[IWL_STATS_MAX_PHY_OPERATIONAL];
struct iwl_stats_ntfy_per_sta per_sta[IWL_STATION_COUNT_MAX];
__le64 rx_time;
__le64 tx_time;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2005-2014, 2018-2019, 2021-2025 Intel Corporation
* Copyright (C) 2005-2014, 2018-2019, 2021-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
*/
@ -38,6 +38,11 @@ struct iwl_fw_dbg_params {
u32 out_ctrl;
};
/* old-style dump entry point */
void iwl_fw_error_dump(struct iwl_fw_runtime *fwrt,
struct iwl_fwrt_dump_data *dump_data);
struct scatterlist *iwl_fw_dbg_alloc_sgtable(ssize_t size);
extern const struct iwl_fw_dump_desc iwl_dump_desc_assert;
int iwl_fw_dbg_collect_desc(struct iwl_fw_runtime *fwrt,

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2012-2014, 2018-2024 Intel Corporation
* Copyright (C) 2012-2014, 2018-2024, 2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@ -275,16 +275,19 @@ static ssize_t iwl_dbgfs_send_hcmd_write(struct iwl_fw_runtime *fwrt, char *buf,
goto out;
}
/* ignore this flag, we cannot use the response */
hcmd.flags &= ~CMD_WANT_SKB;
/* reject flags other than async, they cannot be used this way */
if (hcmd.flags & ~CMD_ASYNC) {
ret = -EINVAL;
goto out;
}
if (fwrt->ops && fwrt->ops->send_hcmd)
ret = fwrt->ops->send_hcmd(fwrt->ops_ctx, &hcmd);
else
ret = -EPERM;
if (ret < 0)
goto out;
if (hcmd.flags & CMD_WANT_SKB)
iwl_free_resp(&hcmd);
out:
kfree(data);
return ret ?: count;

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2014, 2018-2025 Intel Corporation
* Copyright (C) 2014, 2018-2026 Intel Corporation
* Copyright (C) 2014-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@ -265,18 +265,6 @@ struct iwl_fw_ini_error_dump_data {
__u8 data[];
} __packed;
/**
* struct iwl_fw_ini_dump_entry - dump entry descriptor
* @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_ini_dump_file_hdr - header of dump file
* @barker: must be %IWL_FW_INI_ERROR_DUMP_BARKER

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2008-2014, 2018-2024 Intel Corporation
* Copyright (C) 2008-2014, 2018-2024, 2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@ -111,6 +111,7 @@ enum iwl_ucode_tlv_type {
IWL_UCODE_TLV_FW_NUM_STATIONS = IWL_UCODE_TLV_CONST_BASE + 0,
IWL_UCODE_TLV_FW_NUM_LINKS = IWL_UCODE_TLV_CONST_BASE + 1,
IWL_UCODE_TLV_FW_NUM_BEACONS = IWL_UCODE_TLV_CONST_BASE + 2,
IWL_UCODE_TLV_FW_NUM_MCAST_KEY_ENTRIES = IWL_UCODE_TLV_CONST_BASE + 3,
IWL_UCODE_TLV_TYPE_DEBUG_INFO = IWL_UCODE_TLV_DEBUG_BASE + 0,
IWL_UCODE_TLV_TYPE_BUFFER_ALLOCATION = IWL_UCODE_TLV_DEBUG_BASE + 1,
@ -1063,10 +1064,15 @@ struct iwl_fw_dump_exclude {
__le32 addr, size;
};
struct iwl_fw_fseq_bin_version {
struct iwl_fw_fseq_bin_version_v1 {
__le32 major, minor;
}; /* FW_TLV_FSEQ_BIN_VERSION_S */
struct iwl_fw_fseq_bin_version {
/* rf_id is currently unused and always zero */
__le32 mac_id, rf_id, major, minor;
}; /* FW_TLV_FSEQ_BIN_VERSION_S */
static inline size_t _iwl_tlv_array_len(const struct iwl_ucode_tlv *tlv,
size_t fixed_size, size_t var_size)
{

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2005-2014, 2018-2024 Intel Corporation
* Copyright (C) 2005-2014, 2018-2024, 2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016 Intel Deutschland GmbH
*/
@ -53,6 +53,7 @@ struct iwl_ucode_capabilities {
u32 num_stations;
u32 num_links;
u32 num_beacons;
u32 num_mcast_key_entries;
DECLARE_BITMAP(_api, NUM_IWL_UCODE_TLV_API);
DECLARE_BITMAP(_capa, NUM_IWL_UCODE_TLV_CAPA);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2023, 2025 Intel Corporation
* Copyright (C) 2023, 2025-2026 Intel Corporation
*/
#include <linux/dmi.h>
#include "iwl-drv.h"
@ -112,6 +112,11 @@ static const struct dmi_system_id dmi_ppag_approved_list[] = {
DMI_MATCH(DMI_SYS_VENDOR, "WIKO"),
},
},
{ .ident = "XIAOMI",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "XIAOMI"),
},
},
{}
};

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2021-2022, 2025 Intel Corporation
* Copyright (C) 2021-2022, 2025-2026 Intel Corporation
*/
#include <net/mac80211.h>
@ -124,6 +124,9 @@ int rs_pretty_print_rate(char *buf, int bufsz, const u32 rate)
case RATE_MCS_MOD_TYPE_EHT:
type = "EHT";
break;
case RATE_MCS_MOD_TYPE_UHR:
type = "UHR";
break;
default:
type = "Unknown"; /* shouldn't happen */
}

View File

@ -416,6 +416,7 @@ struct iwl_mac_cfg {
* @nvm_type: see &enum iwl_nvm_type
* @uhb_supported: ultra high band channels supported
* @eht_supported: EHT supported
* @uhr_supported: UHR supported
* @num_rbds: number of receive buffer descriptors to use
* (only used for multi-queue capable devices)
*
@ -449,7 +450,8 @@ struct iwl_rf_cfg {
lp_xtal_workaround:1,
vht_mu_mimo_supported:1,
uhb_supported:1,
eht_supported:1;
eht_supported:1,
uhr_supported:1;
u8 valid_tx_ant;
u8 valid_rx_ant;
u8 non_shared_ant;
@ -674,6 +676,7 @@ extern const char iwl_killer_be1750w_name[];
extern const char iwl_killer_be1750x_name[];
extern const char iwl_killer_be1790s_name[];
extern const char iwl_killer_be1790i_name[];
extern const char iwl_killer_be1730x_name[];
extern const char iwl_be201_name[];
extern const char iwl_be200_name[];
extern const char iwl_be202_name[];
@ -681,6 +684,7 @@ extern const char iwl_be401_name[];
extern const char iwl_be213_name[];
extern const char iwl_killer_be1775s_name[];
extern const char iwl_killer_be1775i_name[];
extern const char iwl_killer_be1735x_name[];
extern const char iwl_be211_name[];
extern const char iwl_killer_bn1850w2_name[];
extern const char iwl_killer_bn1850i_name[];
@ -744,7 +748,8 @@ extern const struct iwl_rf_cfg iwl_rf_fm_160mhz;
#define iwl_rf_wh iwl_rf_fm
#define iwl_rf_wh_160mhz iwl_rf_fm_160mhz
extern const struct iwl_rf_cfg iwl_rf_wh_non_eht;
#define iwl_rf_pe iwl_rf_fm
extern const struct iwl_rf_cfg iwl_rf_pe;
#define iwl_rf_pe_no_uhr iwl_rf_fm
#endif /* CONFIG_IWLMLD */
#endif /* __IWL_CONFIG_H__ */

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2005-2014, 2018-2025 Intel Corporation
* Copyright (C) 2005-2014, 2018-2026 Intel Corporation
* Copyright (C) 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2016 Intel Deutschland GmbH
*/
@ -366,6 +366,7 @@ enum {
#define CSR_HW_RF_ID_TYPE_GF4 (0x0010E000)
#define CSR_HW_RF_ID_TYPE_FM (0x00112000)
#define CSR_HW_RF_ID_TYPE_WP (0x00113000)
#define CSR_HW_RF_ID_TYPE_PE (0x00114000)
/* HW_RF CHIP STEP */
#define CSR_HW_RF_STEP(_val) (((_val) >> 8) & 0xF)

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2005-2014, 2018-2025 Intel Corporation
* Copyright (C) 2005-2014, 2018-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@ -1294,8 +1294,9 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv,
if (tlv_len != sizeof(*fseq_ver))
goto invalid_tlv_len;
IWL_DEBUG_INFO(drv, "TLV_FW_FSEQ_VERSION: %.32s\n",
fseq_ver->version);
IWL_DEBUG_INFO(drv,
"TLV_FW_FSEQ_VERSION: %.32s (sha1: %.20s)\n",
fseq_ver->version, fseq_ver->sha1);
}
break;
case IWL_UCODE_TLV_FW_NUM_STATIONS:
@ -1330,6 +1331,12 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv,
capa->num_beacons =
le32_to_cpup((const __le32 *)tlv_data);
break;
case IWL_UCODE_TLV_FW_NUM_MCAST_KEY_ENTRIES:
if (tlv_len != sizeof(u32))
goto invalid_tlv_len;
capa->num_mcast_key_entries =
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;
@ -1640,6 +1647,7 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)
fw->ucode_capa.n_scan_channels = IWL_DEFAULT_SCAN_CHANNELS;
fw->ucode_capa.num_stations = IWL_STATION_COUNT_MAX;
fw->ucode_capa.num_beacons = 1;
fw->ucode_capa.num_mcast_key_entries = 2;
/* dump all fw memory areas by default */
fw->dbg.dump_mask = 0xffffffff;

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2003-2014, 2018-2022, 2024-2025 Intel Corporation
* Copyright (C) 2003-2014, 2018-2022, 2024-2026 Intel Corporation
* Copyright (C) 2015-2016 Intel Deutschland GmbH
*/
#include <linux/device.h>
@ -12,7 +12,6 @@
#include "iwl-debug.h"
#include "iwl-prph.h"
#include "iwl-fh.h"
#include "pcie/gen1_2/internal.h"
void iwl_write8(struct iwl_trans *trans, u32 ofs, u8 val)
{
@ -168,6 +167,22 @@ int iwl_poll_prph_bit(struct iwl_trans *trans, u32 addr,
return -ETIMEDOUT;
}
int iwl_poll_umac_prph_bits_no_grab(struct iwl_trans *trans, u32 addr,
u32 bits, u32 mask, int timeout)
{
int t = 0;
do {
if ((iwl_read_umac_prph_no_grab(trans, addr) & mask) ==
(bits & mask))
return 0;
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)) {
@ -396,12 +411,6 @@ int iwl_dump_fh(struct iwl_trans *trans, char **buf)
return 0;
}
int iwl_trans_activate_nic(struct iwl_trans *trans)
{
return iwl_pcie_gen1_2_activate_nic(trans);
}
IWL_EXPORT_SYMBOL(iwl_trans_activate_nic);
void iwl_trans_sync_nmi_with_addr(struct iwl_trans *trans, u32 inta_addr,
u32 sw_err_bit)
{

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2018-2021, 2025 Intel Corporation
* Copyright (C) 2018-2021, 2025-2026 Intel Corporation
*/
#ifndef __iwl_io_h__
#define __iwl_io_h__
@ -51,14 +51,14 @@ static inline void iwl_write_prph(struct iwl_trans *trans, u32 ofs, u32 val)
int iwl_poll_prph_bit(struct iwl_trans *trans, u32 addr,
u32 bits, u32 mask, int timeout);
int iwl_poll_umac_prph_bits_no_grab(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_trans_activate_nic(struct iwl_trans *trans);
/* Error handling */
int iwl_dump_fh(struct iwl_trans *trans, char **buf);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2005-2014, 2018-2023, 2025 Intel Corporation
* Copyright (C) 2005-2014, 2018-2023, 2025-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@ -18,7 +18,6 @@
#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"
@ -206,28 +205,30 @@ enum iwl_reg_capa_flags_v2 {
}; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_2 */
/**
* enum iwl_reg_capa_flags_v4 - global flags applied for the whole regulatory
* enum iwl_reg_capa_flags_v5 - global flags applied for the whole regulatory
* domain.
* @REG_CAPA_V4_160MHZ_ALLOWED: 11ac channel with a width of 160Mhz is allowed
* @REG_CAPA_V5_160MHZ_ALLOWED: 11ac channel with a width of 160Mhz is allowed
* for this regulatory domain (valid only in 5Ghz).
* @REG_CAPA_V4_80MHZ_ALLOWED: 11ac channel with a width of 80Mhz is allowed
* @REG_CAPA_V5_80MHZ_ALLOWED: 11ac channel with a width of 80Mhz is allowed
* for this regulatory domain (valid only in 5Ghz).
* @REG_CAPA_V4_MCS_12_ALLOWED: 11ac with MCS 12 is allowed.
* @REG_CAPA_V4_MCS_13_ALLOWED: 11ac with MCS 13 is allowed.
* @REG_CAPA_V4_11BE_DISABLED: 11be is forbidden for this regulatory domain.
* @REG_CAPA_V4_11AX_DISABLED: 11ax is forbidden for this regulatory domain.
* @REG_CAPA_V4_320MHZ_ALLOWED: 11be channel with a width of 320Mhz is allowed
* @REG_CAPA_V5_MCS_12_ALLOWED: 11ac with MCS 12 is allowed.
* @REG_CAPA_V5_MCS_13_ALLOWED: 11ac with MCS 13 is allowed.
* @REG_CAPA_V5_11BE_DISABLED: 11be is forbidden for this regulatory domain.
* @REG_CAPA_V5_11AX_DISABLED: 11ax is forbidden for this regulatory domain.
* @REG_CAPA_V5_320MHZ_ALLOWED: 11be channel with a width of 320Mhz is allowed
* for this regulatory domain (valid only in 5GHz).
* @REG_CAPA_V5_11BN_DISABLED: UHR is not allowed for this regulatory domain
*/
enum iwl_reg_capa_flags_v4 {
REG_CAPA_V4_160MHZ_ALLOWED = BIT(3),
REG_CAPA_V4_80MHZ_ALLOWED = BIT(4),
REG_CAPA_V4_MCS_12_ALLOWED = BIT(5),
REG_CAPA_V4_MCS_13_ALLOWED = BIT(6),
REG_CAPA_V4_11BE_DISABLED = BIT(8),
REG_CAPA_V4_11AX_DISABLED = BIT(13),
REG_CAPA_V4_320MHZ_ALLOWED = BIT(16),
}; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_4 */
enum iwl_reg_capa_flags_v5 {
REG_CAPA_V5_160MHZ_ALLOWED = BIT(3),
REG_CAPA_V5_80MHZ_ALLOWED = BIT(4),
REG_CAPA_V5_MCS_12_ALLOWED = BIT(5),
REG_CAPA_V5_MCS_13_ALLOWED = BIT(6),
REG_CAPA_V5_11BE_DISABLED = BIT(8),
REG_CAPA_V5_11AX_DISABLED = BIT(13),
REG_CAPA_V5_320MHZ_ALLOWED = BIT(16),
REG_CAPA_V5_11BN_DISABLED = BIT(17),
}; /* GEO_CHANNEL_CAPABILITIES_API_S_VER_4, 5 */
/*
* API v2 for reg_capa_flags is relevant from version 6 and onwards of the
@ -546,7 +547,7 @@ static const u8 iwl_vendor_caps[] = {
0x00
};
static const struct ieee80211_sband_iftype_data iwl_he_eht_capa[] = {
static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = {
{
.types_mask = BIT(NL80211_IFTYPE_STATION) |
BIT(NL80211_IFTYPE_P2P_CLIENT),
@ -690,6 +691,16 @@ static const struct ieee80211_sband_iftype_data iwl_he_eht_capa[] = {
*/
.eht_ppe_thres = {0xc1, 0x0e, 0xe0 }
},
.uhr_cap = {
.has_uhr = true,
.phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
IEEE80211_UHR_PHY_CAP_ELR_TX,
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP |
IEEE80211_UHR_MAC_CAP0_DPS_SUPP,
[1] = IEEE80211_UHR_MAC_CAP1_DUO_SUPP,
},
},
},
{
.types_mask = BIT(NL80211_IFTYPE_AP) |
@ -788,6 +799,11 @@ static const struct ieee80211_sband_iftype_data iwl_he_eht_capa[] = {
*/
.eht_ppe_thres = {0xc1, 0x0e, 0xe0 }
},
.uhr_cap = {
.has_uhr = true,
.phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
IEEE80211_UHR_PHY_CAP_ELR_TX,
},
},
};
@ -855,6 +871,9 @@ iwl_nvm_fixup_sband_iftd(struct iwl_trans *trans,
fips_enabled)
iftype_data->eht_cap.has_eht = false;
if (!data->sku_cap_11bn_enable || !iftype_data->eht_cap.has_eht)
iftype_data->uhr_cap.has_uhr = false;
/* Advertise an A-MPDU exponent extension based on
* operating band
*/
@ -1023,9 +1042,9 @@ static void iwl_init_he_hw_capab(struct iwl_trans *trans,
struct ieee80211_sband_iftype_data *iftype_data;
int i;
BUILD_BUG_ON(sizeof(data->iftd.low) != sizeof(iwl_he_eht_capa));
BUILD_BUG_ON(sizeof(data->iftd.high) != sizeof(iwl_he_eht_capa));
BUILD_BUG_ON(sizeof(data->iftd.uhb) != sizeof(iwl_he_eht_capa));
BUILD_BUG_ON(sizeof(data->iftd.low) != sizeof(iwl_iftype_cap));
BUILD_BUG_ON(sizeof(data->iftd.high) != sizeof(iwl_iftype_cap));
BUILD_BUG_ON(sizeof(data->iftd.uhb) != sizeof(iwl_iftype_cap));
switch (sband->band) {
case NL80211_BAND_2GHZ:
@ -1042,10 +1061,10 @@ static void iwl_init_he_hw_capab(struct iwl_trans *trans,
return;
}
memcpy(iftype_data, iwl_he_eht_capa, sizeof(iwl_he_eht_capa));
memcpy(iftype_data, iwl_iftype_cap, sizeof(iwl_iftype_cap));
_ieee80211_set_sband_iftype_data(sband, iftype_data,
ARRAY_SIZE(iwl_he_eht_capa));
ARRAY_SIZE(iwl_iftype_cap));
for (i = 0; i < sband->n_iftype_data; i++)
iwl_nvm_fixup_sband_iftd(trans, data, sband, &iftype_data[i],
@ -1054,6 +1073,46 @@ static void iwl_init_he_hw_capab(struct iwl_trans *trans,
iwl_init_he_6ghz_capa(trans, data, sband, tx_chains, rx_chains);
}
static void
iwl_init_nan_phy_capa(const struct iwl_fw *fw, struct iwl_nvm_data *data)
{
const struct ieee80211_sta_he_cap *he_cap;
if (!fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT))
return;
data->nan_phy_capa.ht = data->bands[NL80211_BAND_2GHZ].ht_cap;
data->nan_phy_capa.vht = data->bands[NL80211_BAND_5GHZ].vht_cap;
he_cap = ieee80211_get_he_iftype_cap(&data->bands[NL80211_BAND_2GHZ],
NL80211_IFTYPE_STATION);
if (he_cap) {
data->nan_phy_capa.he = *he_cap;
data->nan_phy_capa.he.he_cap_elem.phy_cap_info[0] |=
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G |
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
}
/*
* FIXME: we copied HE capabilities from the 2.4 GHz band,
* but there are bits that are band-dependent:
*
* IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_1 - 2.4 GHz - set
* IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_3 - 5 GHz - not set
* IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G - set
* IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G - set
* IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G - set
*
* We copied from STA iftype - so we have the following bits set:
* IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS
* IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS
* IEEE80211_HE_PHY_CAP7_MAX_NC_1
* IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO
*
* Need to check which one should actually be set for NAN.
*/
}
void iwl_reinit_cab(struct iwl_trans *trans, struct iwl_nvm_data *data,
u8 tx_chains, u8 rx_chains, const struct iwl_fw *fw)
{
@ -1082,6 +1141,8 @@ void iwl_reinit_cab(struct iwl_trans *trans, struct iwl_nvm_data *data,
if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax)
iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains,
fw);
iwl_init_nan_phy_capa(fw, data);
}
IWL_EXPORT_SYMBOL(iwl_reinit_cab);
@ -1611,6 +1672,9 @@ u32 iwl_nvm_get_regdom_bw_flags(const u16 *nvm_chan,
if (reg_capa.disable_11be)
flags |= NL80211_RRF_NO_EHT;
if (reg_capa.disable_11bn)
flags |= NL80211_RRF_NO_UHR;
return flags;
}
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_nvm_get_regdom_bw_flags);
@ -1621,11 +1685,13 @@ static struct iwl_reg_capa iwl_get_reg_capa(u32 flags, u8 resp_ver)
if (resp_ver >= REG_CAPA_V4_RESP_VER) {
reg_capa.allow_40mhz = true;
reg_capa.allow_80mhz = flags & REG_CAPA_V4_80MHZ_ALLOWED;
reg_capa.allow_160mhz = flags & REG_CAPA_V4_160MHZ_ALLOWED;
reg_capa.allow_320mhz = flags & REG_CAPA_V4_320MHZ_ALLOWED;
reg_capa.disable_11ax = flags & REG_CAPA_V4_11AX_DISABLED;
reg_capa.disable_11be = flags & REG_CAPA_V4_11BE_DISABLED;
reg_capa.allow_80mhz = flags & REG_CAPA_V5_80MHZ_ALLOWED;
reg_capa.allow_160mhz = flags & REG_CAPA_V5_160MHZ_ALLOWED;
reg_capa.allow_320mhz = flags & REG_CAPA_V5_320MHZ_ALLOWED;
reg_capa.disable_11ax = flags & REG_CAPA_V5_11AX_DISABLED;
reg_capa.disable_11be = flags & REG_CAPA_V5_11BE_DISABLED;
/* can check: was reserved and irrelevant for pre-UHR devices */
reg_capa.disable_11bn = flags & REG_CAPA_V5_11BN_DISABLED;
} else 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;
@ -1683,8 +1749,16 @@ iwl_parse_nvm_mcc_info(struct iwl_trans *trans,
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_flex(*regd, reg_rules, num_of_ch);
/* build a regdomain rule for every valid channel.
* 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, we need
* to allocate space for at least one rule to add a dummy
* (unusable) rule in this case so we can init.
*/
regd = kzalloc_flex(*regd, reg_rules, num_of_ch ?: 1);
if (!regd)
return ERR_PTR(-ENOMEM);
@ -1758,14 +1832,7 @@ iwl_parse_nvm_mcc_info(struct iwl_trans *trans,
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 no valid rules were found, add a dummy rule */
if (!valid_rules) {
valid_rules = 1;
rule = &regd->reg_rules[valid_rules - 1];
@ -2079,6 +2146,7 @@ struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans,
!!(mac_flags & NVM_MAC_SKU_FLAGS_MIMO_DISABLED);
if (trans->cfg->eht_supported)
nvm->sku_cap_11be_enable = true;
nvm->sku_cap_11bn_enable = trans->cfg->uhr_supported;
/* Initialize PHY sku data */
nvm->valid_tx_ant = (u8)le32_to_cpu(rsp->phy_sku.tx_chains);
@ -2106,6 +2174,8 @@ struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans,
iwl_init_sbands(trans, nvm, channel_profile, tx_ant, rx_ant,
sbands_flags, v4, fw);
iwl_init_nan_phy_capa(fw, nvm);
iwl_free_resp(&hcmd);
return nvm;

View File

@ -35,6 +35,7 @@ enum iwl_nvm_sbands_flags {
* for this regulatory domain (valid only in 6 Ghz).
* @disable_11ax: 11ax is forbidden for this regulatory domain.
* @disable_11be: 11be is forbidden for this regulatory domain.
* @disable_11bn: UHR/11bn is not allowed for this regulatory domain
*/
struct iwl_reg_capa {
bool allow_40mhz;
@ -43,6 +44,7 @@ struct iwl_reg_capa {
bool allow_320mhz;
bool disable_11ax;
bool disable_11be;
bool disable_11bn;
};
/**

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2005-2014, 2018, 2020-2023 Intel Corporation
* Copyright (C) 2005-2014, 2018, 2020-2023, 2026 Intel Corporation
* Copyright (C) 2015 Intel Mobile Communications GmbH
*/
#ifndef __iwl_eeprom_parse_h__
@ -32,6 +32,7 @@ struct iwl_nvm_data {
bool sku_cap_ipan_enable;
bool sku_cap_mimo_disabled;
bool sku_cap_11be_enable;
bool sku_cap_11bn_enable;
u16 radio_cfg_type;
u8 radio_cfg_step;
@ -55,6 +56,12 @@ struct iwl_nvm_data {
struct ieee80211_sband_iftype_data uhb[2];
} iftd;
struct {
struct ieee80211_sta_ht_cap ht;
struct ieee80211_sta_vht_cap vht;
struct ieee80211_sta_he_cap he;
} nan_phy_capa;
struct ieee80211_channel channels[];
};

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2005-2014, 2018-2025 Intel Corporation
* Copyright (C) 2005-2014, 2018-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016 Intel Deutschland GmbH
*/
@ -411,6 +411,11 @@ enum {
#define HPM_SECONDARY_DEVICE_STATE 0xa03404
#define WFPM_MAC_OTP_CFG7_ADDR 0xa03338
#define WFPM_MAC_OTP_CFG7_DATA 0xa0333c
#define WFPM_RSRCS_4PHS_REQ_STTS 0xa033f8
#define WFPM_RSRCS_4PHS_ACK_STTS 0xa033fc
#define RSRC_REQ_CNVR_TOP BIT(6)
#define RSRC_ACK_CNVR_TOP BIT(6)
/* For UMAG_GEN_HW_STATUS reg check */

View File

@ -2,16 +2,14 @@
/*
* Copyright (C) 2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
* Copyright (C) 2019-2021, 2023-2025 Intel Corporation
* Copyright (C) 2019-2021, 2023-2026 Intel Corporation
*/
#include <linux/kernel.h>
#include <linux/bsearch.h>
#include <linux/list.h>
#include "fw/api/tx.h"
#include "iwl-trans.h"
#include "iwl-drv.h"
#include "iwl-fh.h"
#include <linux/dmapool.h>
#include "fw/api/commands.h"
#include "pcie/gen1_2/internal.h"
@ -461,6 +459,12 @@ int iwl_trans_read_mem(struct iwl_trans *trans, u32 addr,
}
IWL_EXPORT_SYMBOL(iwl_trans_read_mem);
int iwl_trans_read_mem_no_grab(struct iwl_trans *trans, u32 addr,
void *buf, u32 dwords)
{
return iwl_trans_pcie_read_mem_no_grab(trans, addr, buf, dwords);
}
int iwl_trans_write_mem(struct iwl_trans *trans, u32 addr,
const void *buf, int dwords)
{
@ -822,3 +826,10 @@ bool iwl_trans_is_ltr_enabled(struct iwl_trans *trans)
return iwl_pcie_gen1_2_is_ltr_enabled(trans);
}
IWL_EXPORT_SYMBOL(iwl_trans_is_ltr_enabled);
int iwl_trans_activate_nic(struct iwl_trans *trans)
{
return iwl_pcie_gen1_2_activate_nic(trans);
}
IWL_EXPORT_SYMBOL(iwl_trans_activate_nic);

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2005-2014, 2018-2025 Intel Corporation
* Copyright (C) 2005-2014, 2018-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@ -700,106 +700,6 @@ struct iwl_cmd_meta {
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
* @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
* @reclaim_lock: reclaim 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
* @block: queue is blocked
* @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
* @overflow_q: overflow queue for handling frames that didn't fit on HW queue
* @overflow_tx: need to transmit from overflow
*
* 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;
/* lock to prevent concurrent reclaim */
spinlock_t reclaim_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_info - transport info for outside use
* @name: the device name
@ -1019,6 +919,14 @@ void iwl_trans_write_prph(struct iwl_trans *trans, u32 ofs, u32 val);
int iwl_trans_read_mem(struct iwl_trans *trans, u32 addr,
void *buf, int dwords);
/*
* Note the special calling convention - it's allowed to drop the
* internal transport lock and re-enable BHs temporarily, but will
* not release NIC access.
*/
int iwl_trans_read_mem_no_grab(struct iwl_trans *trans, u32 addr,
void *buf, u32 dwords);
int iwl_trans_read_config32(struct iwl_trans *trans, u32 ofs,
u32 *val);
@ -1034,6 +942,14 @@ void iwl_trans_debugfs_cleanup(struct iwl_trans *trans);
(bufsize) / sizeof(u32)); \
})
static inline int
iwl_trans_read_mem_bytes_no_grab(struct iwl_trans *trans,
u32 addr, void *buf, u32 bufsize)
{
return iwl_trans_read_mem_no_grab(trans, addr, buf,
bufsize / sizeof(u32));
}
int iwl_trans_write_imr_mem(struct iwl_trans *trans, u32 dst_addr,
u64 src_addr, u32 byte_cnt);
@ -1153,6 +1069,8 @@ static inline bool iwl_trans_dbg_ini_valid(struct iwl_trans *trans)
void iwl_trans_interrupts(struct iwl_trans *trans, bool enable);
int iwl_trans_activate_nic(struct iwl_trans *trans);
static inline void iwl_trans_finish_sw_reset(struct iwl_trans *trans)
{
clear_bit(STATUS_IN_SW_RESET, &trans->status);
@ -1236,9 +1154,6 @@ enum iwl_reset_mode {
void iwl_trans_pcie_reset(struct iwl_trans *trans, enum iwl_reset_mode mode);
void iwl_trans_pcie_fw_reset_handshake(struct iwl_trans *trans);
int iwl_trans_pcie_send_hcmd(struct iwl_trans *trans,
struct iwl_host_cmd *cmd);
/* Internal helper */
static inline void iwl_trans_set_info(struct iwl_trans *trans,
struct iwl_trans_info *info)

View File

@ -64,6 +64,9 @@ static void iwl_mld_release_frames_from_notif(struct iwl_mld *mld,
}
/* pick any STA ID to find the pointer */
if (WARN_ON_ONCE(!ba_data->sta_mask))
goto out_unlock;
sta_id = ffs(ba_data->sta_mask) - 1;
link_sta = rcu_dereference(mld->fw_id_to_link_sta[sta_id]);
if (WARN_ON_ONCE(IS_ERR_OR_NULL(link_sta) || !link_sta->sta))
@ -166,6 +169,9 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue,
goto out_unlock;
/* pick any STA ID to find the pointer */
if (WARN_ON_ONCE(!ba_data->sta_mask))
goto out_unlock;
sta_id = ffs(ba_data->sta_mask) - 1;
link_sta = rcu_dereference(mld->fw_id_to_link_sta[sta_id]);
if (WARN_ON_ONCE(IS_ERR_OR_NULL(link_sta) || !link_sta->sta))
@ -347,6 +353,9 @@ static void iwl_mld_rx_agg_session_expired(struct timer_list *t)
}
/* timer expired, pick any STA ID to find the pointer */
if (WARN_ON_ONCE(!ba_data->sta_mask))
goto unlock;
sta_id = ffs(ba_data->sta_mask) - 1;
link_sta = rcu_dereference(ba_data->mld->fw_id_to_link_sta[sta_id]);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024 Intel Corporation
* Copyright (C) 2024, 2026 Intel Corporation
*/
#include <linux/crc32.h>
@ -239,6 +239,58 @@ int iwl_mld_store_ap_early_key(struct iwl_mld *mld,
return -ENOSPC;
}
static void iwl_mld_stop_beacon(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);
struct iwl_mac_beacon_cmd cmd = {};
int cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, BEACON_TEMPLATE_CMD, 14);
if (WARN_ON(!mld_link))
return;
if (cmd_ver < 15)
return;
/* leave byte_cnt 0 */
cmd.link_id = cpu_to_le32(mld_link->fw_id);
iwl_mld_send_cmd_pdu(mld, BEACON_TEMPLATE_CMD, &cmd);
}
void
iwl_mld_link_info_changed_ap_ibss(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link,
u64 changes)
{
u32 link_changes = 0;
if (changes & BSS_CHANGED_ERP_SLOT)
link_changes |= LINK_CONTEXT_MODIFY_RATES_INFO;
if (changes & (BSS_CHANGED_ERP_CTS_PROT | BSS_CHANGED_HT))
link_changes |= LINK_CONTEXT_MODIFY_PROTECT_FLAGS;
if (changes & (BSS_CHANGED_QOS | BSS_CHANGED_BANDWIDTH))
link_changes |= LINK_CONTEXT_MODIFY_QOS_PARAMS;
if (changes & BSS_CHANGED_HE_BSS_COLOR)
link_changes |= LINK_CONTEXT_MODIFY_HE_PARAMS;
if (link_changes)
iwl_mld_change_link_in_fw(mld, link, link_changes);
if (changes & BSS_CHANGED_BEACON) {
WARN_ON(!link->enable_beacon);
iwl_mld_update_beacon_template(mld, vif, link);
}
/* Enabling beacons was already covered above */
if ((changes & BSS_CHANGED_BEACON_ENABLED) && !link->enable_beacon)
iwl_mld_stop_beacon(mld, vif, link);
}
static int iwl_mld_send_ap_early_keys(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link)
@ -276,10 +328,6 @@ int iwl_mld_start_ap_ibss(struct ieee80211_hw *hw,
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
*/

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2024 Intel Corporation
* Copyright (C) 2024, 2026 Intel Corporation
*/
#ifndef __iwl_ap_h__
#define __iwl_ap_h__
@ -14,6 +14,12 @@ int iwl_mld_update_beacon_template(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf);
void
iwl_mld_link_info_changed_ap_ibss(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link,
u64 changes);
int iwl_mld_start_ap_ibss(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include "mld.h"
@ -43,6 +43,12 @@ struct iwl_mld_resume_key_iter_data {
struct iwl_mld_wowlan_status *wowlan_status;
};
struct iwl_mld_rsc_resume_iter_data {
struct iwl_mld *mld;
const struct iwl_wowlan_all_rsc_tsc_v5 *notif;
int queue;
};
struct iwl_mld_suspend_key_iter_data {
struct iwl_wowlan_rsc_tsc_params_cmd *rsc;
bool have_rsc;
@ -282,7 +288,8 @@ static void
iwl_mld_convert_gtk_resume_data(struct iwl_mld *mld,
struct iwl_mld_wowlan_status *wowlan_status,
const struct iwl_wowlan_gtk_status *gtk_data,
const struct iwl_wowlan_all_rsc_tsc_v5 *sc)
const struct iwl_wowlan_all_rsc_tsc_v5 *sc,
int rsc_notif_ver)
{
int status_idx = 0;
@ -305,14 +312,18 @@ iwl_mld_convert_gtk_resume_data(struct iwl_mld *mld,
wowlan_status->gtk[status_idx].id =
wowlan_status->gtk[status_idx].flags &
IWL_WOWLAN_GTK_IDX_MASK;
/* The rsc for both gtk keys are stored in gtk[0]->sc->mcast_rsc
* The gtk ids can be any two numbers between 0 and 3,
* the id_map maps between the key id and the index in sc->mcast
*/
rsc_idx =
sc->mcast_key_id_map[wowlan_status->gtk[status_idx].id];
iwl_mld_convert_gtk_resume_seq(&wowlan_status->gtk[status_idx],
sc, rsc_idx);
/* If RSC_NOTIF is not supported */
if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN) {
/* The rsc for both gtk keys are stored in
* gtk[0]->sc->mcast_rsc. The gtk ids can be any two
* numbers between 0 and 3, the id_map maps between the
* key id and the index in sc->mcast
*/
rsc_idx =
sc->mcast_key_id_map[wowlan_status->gtk[status_idx].id];
iwl_mld_convert_gtk_resume_seq(&wowlan_status->gtk[status_idx],
sc, rsc_idx);
}
if (key_status == IWL_WOWLAN_STATUS_NEW_KEY) {
memcpy(wowlan_status->gtk[status_idx].key,
@ -598,6 +609,10 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld,
PROT_OFFLOAD_GROUP,
WOWLAN_INFO_NOTIFICATION,
IWL_FW_CMD_VER_UNKNOWN);
int rsc_notif_ver = iwl_fw_lookup_notif_ver(mld->fw,
DATA_PATH_GROUP,
RSC_NOTIF,
IWL_FW_CMD_VER_UNKNOWN);
if (wowlan_info_ver == 5) {
/* v5 format - validate before conversion */
@ -642,8 +657,10 @@ iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld,
return true;
iwl_mld_convert_gtk_resume_data(mld, wowlan_status, notif->gtk,
&notif->gtk[0].sc);
iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, &notif->gtk[0].sc);
&notif->gtk[0].sc, rsc_notif_ver);
if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN)
iwl_mld_convert_ptk_resume_seq(mld, wowlan_status,
&notif->gtk[0].sc);
/* only one igtk is passed by FW */
iwl_mld_convert_igtk_resume_data(wowlan_status, &notif->igtk[0]);
iwl_mld_convert_bigtk_resume_data(wowlan_status, notif->bigtk);
@ -902,8 +919,14 @@ iwl_mld_resume_keys_iter(struct ieee80211_hw *hw,
struct iwl_mld_resume_key_iter_data *data = _data;
struct iwl_mld_wowlan_status *wowlan_status = data->wowlan_status;
u8 status_idx;
int rsc_notif_ver = iwl_fw_lookup_notif_ver(data->mld->fw,
DATA_PATH_GROUP,
RSC_NOTIF,
IWL_FW_CMD_VER_UNKNOWN);
if (key->keyidx >= 0 && key->keyidx <= 3) {
/* If RSC_NOTIF is not supported */
if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN &&
key->keyidx >= 0 && key->keyidx <= 3) {
/* PTK */
if (sta) {
iwl_mld_update_ptk_rx_seq(data->mld, wowlan_status,
@ -932,6 +955,105 @@ iwl_mld_resume_keys_iter(struct ieee80211_hw *hw,
}
}
static void
iwl_mld_rsc_update_key_iter(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key,
void *_data)
{
struct iwl_mld_rsc_resume_iter_data *data = _data;
struct ieee80211_key_seq seq;
if (key->keyidx > 3)
return;
if (sta) {
/* PTK */
BUILD_BUG_ON(ARRAY_SIZE(data->notif->ucast_rsc) !=
IWL_MAX_TID_COUNT);
if (key->cipher == WLAN_CIPHER_SUITE_TKIP) {
/* TKIP: just update key sequences */
for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
iwl_mld_le64_to_tkip_seq(data->notif->ucast_rsc[tid],
&seq);
ieee80211_set_key_rx_seq(key, tid, &seq);
}
} else {
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
struct iwl_mld_ptk_pn *mld_ptk_pn =
rcu_dereference_wiphy(data->mld->wiphy,
mld_sta->ptk_pn[key->keyidx]);
if (WARN_ON(!mld_ptk_pn))
return;
if (WARN_ON(data->queue >=
data->mld->trans->info.num_rxqs))
return;
for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
iwl_mld_le64_to_aes_seq(data->notif->ucast_rsc[tid],
&seq);
ieee80211_set_key_rx_seq(key, tid, &seq);
memcpy(mld_ptk_pn->q[data->queue].pn[tid],
seq.ccmp.pn,
IEEE80211_CCMP_PN_LEN);
}
}
IWL_DEBUG_WOWLAN(data->mld,
"Updated PTK RSC for key %d on queue %d\n",
key->keyidx, data->queue);
} else {
/* GTK */
int rsc_idx = data->notif->mcast_key_id_map[key->keyidx];
if (rsc_idx == IWL_MCAST_KEY_MAP_INVALID)
return;
if (IWL_FW_CHECK(data->mld,
rsc_idx >= ARRAY_SIZE(data->notif->mcast_rsc),
"Invalid mcast key mapping: %d for key %d\n",
rsc_idx, key->keyidx))
return;
for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
__le64 rsc =
data->notif->mcast_rsc[rsc_idx][tid];
if (key->cipher == WLAN_CIPHER_SUITE_TKIP)
iwl_mld_le64_to_tkip_seq(rsc, &seq);
else
iwl_mld_le64_to_aes_seq(rsc, &seq);
ieee80211_set_key_rx_seq(key, tid, &seq);
}
IWL_DEBUG_WOWLAN(data->mld,
"Updated GTK %d RSC (rsc_idx %d) on queue %d\n",
key->keyidx, rsc_idx, data->queue);
}
}
void
iwl_mld_process_rsc_notification(struct iwl_mld *mld,
struct ieee80211_vif *vif,
const struct iwl_wowlan_all_rsc_tsc_v5 *notif,
int queue)
{
struct iwl_mld_rsc_resume_iter_data iter_data = {
.mld = mld,
.notif = notif,
.queue = queue,
};
/* Iterate through all active keys and update RSC */
ieee80211_iter_keys_rcu(mld->hw, vif,
iwl_mld_rsc_update_key_iter,
&iter_data);
}
static void
iwl_mld_add_mcast_rekey(struct ieee80211_vif *vif,
struct iwl_mld *mld,
@ -951,19 +1073,25 @@ iwl_mld_add_mcast_rekey(struct ieee80211_vif *vif,
iwl_mld_update_mcast_rx_seq(key_config, key_data);
/* The FW holds only one igtk so we keep track of the valid one */
/* The FW holds only one IGTK so we keep track of the valid one */
if (key_config->keyidx == 4 || key_config->keyidx == 5) {
struct iwl_mld_link *mld_link =
iwl_mld_link_from_mac80211(link_conf);
struct iwl_mld_link_sta *mld_ap_link_sta;
mld_ap_link_sta = iwl_mld_get_ap_link_sta(vif,
link_conf->link_id);
if (WARN_ON(!mld_ap_link_sta))
return;
/* If we had more than one rekey, mac80211 will tell us to
* remove the old and add the new so we will update the IGTK in
* drv_set_key
*/
if (mld_link->igtk && mld_link->igtk != key_config) {
if (mld_ap_link_sta->rx_igtk &&
mld_ap_link_sta->rx_igtk != key_config) {
/* mark the old IGTK as not in FW */
mld_link->igtk->hw_key_idx = STA_KEY_IDX_INVALID;
mld_link->igtk = key_config;
mld_ap_link_sta->rx_igtk->hw_key_idx =
STA_KEY_IDX_INVALID;
mld_ap_link_sta->rx_igtk = key_config;
}
}

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2024 Intel Corporation
* Copyright (C) 2024, 2026 Intel Corporation
*/
#ifndef __iwl_mld_d3_h__
#define __iwl_mld_d3_h__
@ -42,6 +42,10 @@ 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);
void iwl_mld_process_rsc_notification(struct iwl_mld *mld,
struct ieee80211_vif *vif,
const struct iwl_wowlan_all_rsc_tsc_v5 *notif,
int queue);
#if IS_ENABLED(CONFIG_IPV6)
void iwl_mld_ipv6_addr_change(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include "mld.h"
@ -979,15 +979,39 @@ void iwl_mld_add_vif_debugfs(struct ieee80211_hw *hw,
VIF_DEBUGFS_ADD_FILE(twt_operation, mld_vif_dbgfs, 0200);
VIF_DEBUGFS_ADD_FILE(int_mlo_scan, mld_vif_dbgfs, 0200);
}
#define LINK_DEBUGFS_WRITE_FILE_OPS(name, bufsz) \
#define LINK_DEBUGFS_WIPHY_WRITE_FILE_OPS(name, bufsz) \
WIPHY_DEBUGFS_WRITE_FILE_OPS(link_##name, bufsz, bss_conf)
/*
* Note: no locking is provided, so the function must have its own,
* but it cannot acquire the wiphy mutex.
*/
#define LINK_DEBUGFS_READ_FILE_OPS(name, bufsz) \
_MLD_DEBUGFS_READ_FILE_OPS(link_##name, bufsz, struct ieee80211_bss_conf)
#define LINK_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \
debugfs_create_file(alias, mode, parent, link_conf, \
&iwl_dbgfs_link_##name##_ops)
#define LINK_DEBUGFS_ADD_FILE(name, parent, mode) \
LINK_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
static ssize_t iwl_dbgfs_link_fw_id_read(struct ieee80211_bss_conf *link_conf,
size_t buflen, void *buf)
{
struct iwl_mld_link *mld_link;
guard(rcu)();
mld_link = iwl_mld_link_from_mac80211(link_conf);
if (!mld_link)
return -EINVAL;
return scnprintf(buf, buflen, "%d\n", mld_link->fw_id);
}
LINK_DEBUGFS_READ_FILE_OPS(fw_id, 64);
void iwl_mld_add_link_debugfs(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf,
@ -1008,6 +1032,8 @@ void iwl_mld_add_link_debugfs(struct ieee80211_hw *hw,
/* Release the reference from debugfs_lookup */
dput(mld_link_dir);
}
LINK_DEBUGFS_ADD_FILE(fw_id, mld_link_dir, 0400);
}
static ssize_t _iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf,
@ -1035,7 +1061,6 @@ static ssize_t _iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf,
/* input is in FW format (v2 or v3) so convert to v3 */
rate = iwl_v3_rate_from_v2_v3(cpu_to_le32(rate), v3);
rate = le32_to_cpu(iwl_v3_rate_to_v2_v3(rate, mld->fw_rates_ver_3));
ret = iwl_mld_send_tlc_dhc(mld, fw_sta_id,
partial ? IWL_TLC_DEBUG_PARTIAL_FIXED_RATE :
@ -1050,20 +1075,22 @@ static ssize_t _iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf,
return ret ? : count;
}
static ssize_t iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf,
size_t count, void *data)
static ssize_t
iwl_dbgfs_link_sta_fixed_rate_write(struct iwl_mld *mld, char *buf,
size_t count, void *data)
{
return _iwl_dbgfs_fixed_rate_write(mld, buf, count, data, false);
}
static ssize_t iwl_dbgfs_fixed_rate_v3_write(struct iwl_mld *mld, char *buf,
size_t count, void *data)
static ssize_t
iwl_dbgfs_link_sta_fixed_rate_v3_write(struct iwl_mld *mld, char *buf,
size_t count, void *data)
{
return _iwl_dbgfs_fixed_rate_write(mld, buf, count, data, true);
}
static ssize_t iwl_dbgfs_tlc_dhc_write(struct iwl_mld *mld, char *buf,
size_t count, void *data)
static ssize_t iwl_dbgfs_link_sta_tlc_dhc_write(struct iwl_mld *mld, char *buf,
size_t count, void *data)
{
struct ieee80211_link_sta *link_sta = data;
struct iwl_mld_link_sta *mld_link_sta;
@ -1090,18 +1117,42 @@ static ssize_t iwl_dbgfs_tlc_dhc_write(struct iwl_mld *mld, char *buf,
return ret ? : count;
}
static ssize_t
iwl_dbgfs_link_sta_fw_id_read(struct ieee80211_link_sta *link_sta,
size_t buflen, void *buf)
{
struct iwl_mld_link_sta *mld_link_sta;
guard(rcu)();
mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);
if (!mld_link_sta)
return -EINVAL;
return scnprintf(buf, buflen, "%u\n", mld_link_sta->fw_id);
}
#define LINK_STA_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \
debugfs_create_file(alias, mode, parent, link_sta, \
&iwl_dbgfs_##name##_ops)
&iwl_dbgfs_link_sta_##name##_ops)
#define LINK_STA_DEBUGFS_ADD_FILE(name, parent, mode) \
LINK_STA_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
#define LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(name, bufsz) \
WIPHY_DEBUGFS_WRITE_FILE_OPS(name, bufsz, link_sta)
WIPHY_DEBUGFS_WRITE_FILE_OPS(link_sta_##name, bufsz, link_sta)
/*
* Note: no locking is provided, so the function must have its own,
* but it cannot acquire the wiphy mutex.
*/
#define LINK_STA_DEBUGFS_READ_OPS(name, bufsz) \
_MLD_DEBUGFS_READ_FILE_OPS(link_sta_##name, bufsz, \
struct ieee80211_link_sta)
LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(tlc_dhc, 64);
LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate, 64);
LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate_v3, 64);
LINK_STA_DEBUGFS_READ_OPS(fw_id, 64);
void iwl_mld_add_link_sta_debugfs(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
@ -1111,4 +1162,5 @@ void iwl_mld_add_link_sta_debugfs(struct ieee80211_hw *hw,
LINK_STA_DEBUGFS_ADD_FILE(fixed_rate, dir, 0200);
LINK_STA_DEBUGFS_ADD_FILE(fixed_rate_v3, dir, 0200);
LINK_STA_DEBUGFS_ADD_FILE(tlc_dhc, dir, 0200);
LINK_STA_DEBUGFS_ADD_FILE(fw_id, dir, 0400);
}

View File

@ -71,24 +71,34 @@ iwl_mld_ftm_set_target_chandef(struct iwl_mld *mld,
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;
target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_LEGACY,
IWL_LOCATION_FMT_BW_FORMAT);
target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ,
IWL_LOCATION_FMT_BW_BANDWIDTH);
break;
case NL80211_CHAN_WIDTH_20:
target->format_bw = IWL_LOCATION_FRAME_FORMAT_HT;
target->format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS;
target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT,
IWL_LOCATION_FMT_BW_FORMAT);
target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ,
IWL_LOCATION_FMT_BW_BANDWIDTH);
break;
case NL80211_CHAN_WIDTH_40:
target->format_bw = IWL_LOCATION_FRAME_FORMAT_HT;
target->format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS;
target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT,
IWL_LOCATION_FMT_BW_FORMAT);
target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_40MHZ,
IWL_LOCATION_FMT_BW_BANDWIDTH);
break;
case NL80211_CHAN_WIDTH_80:
target->format_bw = IWL_LOCATION_FRAME_FORMAT_VHT;
target->format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS;
target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_VHT,
IWL_LOCATION_FMT_BW_FORMAT);
target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_80MHZ,
IWL_LOCATION_FMT_BW_BANDWIDTH);
break;
case NL80211_CHAN_WIDTH_160:
target->format_bw = IWL_LOCATION_FRAME_FORMAT_HE;
target->format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS;
target->format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HE,
IWL_LOCATION_FMT_BW_FORMAT);
target->format_bw |= u8_encode_bits(IWL_LOCATION_BW_160MHZ,
IWL_LOCATION_FMT_BW_BANDWIDTH);
break;
default:
IWL_ERR(mld, "Unsupported BW in FTM request (%d)\n",

View File

@ -1,10 +1,11 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include <net/cfg80211.h>
#include "iface.h"
#include "nan.h"
#include "hcmd.h"
#include "key.h"
#include "mlo.h"
@ -55,6 +56,24 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
ieee80211_iter_keys(mld->hw, vif, iwl_mld_cleanup_keys_iter, NULL);
if (vif->type == NL80211_IFTYPE_NAN) {
mld_vif->nan.mac_added = false;
/* Clean up NAN links */
for (int i = 0; i < ARRAY_SIZE(mld_vif->nan.links); i++)
iwl_mld_cleanup_nan_link(&mld_vif->nan.links[i]);
if (mld_vif->nan.bcast_sta.sta_id != IWL_INVALID_STA)
iwl_mld_free_internal_sta(mld, &mld_vif->nan.bcast_sta);
if (mld_vif->nan.mgmt_sta.sta_id != IWL_INVALID_STA)
iwl_mld_free_internal_sta(mld, &mld_vif->nan.mgmt_sta);
mld_vif->nan.tx_igtk = NULL;
}
if (vif->type == NL80211_IFTYPE_NAN_DATA &&
mld_vif->nan.mcast_data_sta.sta_id != IWL_INVALID_STA)
iwl_mld_free_internal_sta(mld, &mld_vif->nan.mcast_data_sta);
CLEANUP_STRUCT(mld_vif);
}
@ -94,6 +113,8 @@ static int iwl_mld_mac80211_iftype_to_fw(const struct ieee80211_vif *vif)
return FW_MAC_TYPE_P2P_DEVICE;
case NL80211_IFTYPE_ADHOC:
return FW_MAC_TYPE_IBSS;
case NL80211_IFTYPE_NAN:
return FW_MAC_TYPE_NAN;
default:
WARN_ON_ONCE(1);
}
@ -362,6 +383,42 @@ static void iwl_mld_fill_mac_cmd_ibss(struct iwl_mld *mld,
MAC_CFG_FILTER_ACCEPT_GRP);
}
static int iwl_mld_fill_mac_cmd_nan(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_vif *ndi_being_added,
struct iwl_mac_config_cmd *cmd)
{
struct ieee80211_vif *iter;
u32 idx = 0;
cmd->filter_flags = cpu_to_le32(MAC_CFG_FILTER_ACCEPT_CONTROL_AND_MGMT);
/*
* A NAN_DATA vif might be in the process of being added - it won't
* be found by the iteration below since it's not yet active/in-driver.
* In hw restart, the iteration below will find the ndi_being_added.
*/
if (ndi_being_added && !mld->fw_status.in_hw_restart) {
memcpy(cmd->nan.ndi_addrs[idx].addr, ndi_being_added->addr, ETH_ALEN);
idx++;
}
for_each_active_interface(iter, mld->hw) {
if (iter->type != NL80211_IFTYPE_NAN_DATA)
continue;
if (WARN_ON_ONCE(idx >= ARRAY_SIZE(cmd->nan.ndi_addrs)))
return -EINVAL;
memcpy(cmd->nan.ndi_addrs[idx].addr, iter->addr, ETH_ALEN);
idx++;
}
cmd->nan.ndi_addrs_count = cpu_to_le32(idx);
return 0;
}
static int
iwl_mld_rm_mac_from_fw(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
@ -374,16 +431,23 @@ iwl_mld_rm_mac_from_fw(struct iwl_mld *mld, struct ieee80211_vif *vif)
return iwl_mld_send_mac_cmd(mld, &cmd);
}
int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
u32 action)
static int
__iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
u32 action, struct ieee80211_vif *ndi_being_added)
{
struct iwl_mac_config_cmd cmd = {};
int ret;
lockdep_assert_wiphy(mld->wiphy);
/* NAN interface type is not known to FW */
if (vif->type == NL80211_IFTYPE_NAN)
return 0;
/* NAN_DATA interface type is not known to FW */
if (WARN_ON(vif->type == NL80211_IFTYPE_NAN_DATA))
return -EINVAL;
/* ndi_being_added is only relevant for NAN and when adding a NAN_DATA interface */
if (WARN_ON(ndi_being_added &&
(vif->type != NL80211_IFTYPE_NAN || action != FW_CTXT_ACTION_MODIFY)))
return -EINVAL;
if (action == FW_CTXT_ACTION_REMOVE)
return iwl_mld_rm_mac_from_fw(mld, vif);
@ -411,6 +475,11 @@ int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
case NL80211_IFTYPE_ADHOC:
iwl_mld_fill_mac_cmd_ibss(mld, vif, &cmd);
break;
case NL80211_IFTYPE_NAN:
ret = iwl_mld_fill_mac_cmd_nan(mld, vif, ndi_being_added, &cmd);
if (ret)
return ret;
break;
default:
WARN(1, "not supported yet\n");
return -EOPNOTSUPP;
@ -419,6 +488,12 @@ int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
return iwl_mld_send_mac_cmd(mld, &cmd);
}
int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
u32 action)
{
return __iwl_mld_mac_fw_action(mld, vif, action, NULL);
}
static void iwl_mld_mlo_scan_start_wk(struct wiphy *wiphy,
struct wiphy_work *wk)
{
@ -430,7 +505,7 @@ static void iwl_mld_mlo_scan_start_wk(struct wiphy *wiphy,
iwl_mld_int_mlo_scan(mld, iwl_mld_vif_to_mac80211(mld_vif));
}
static IWL_MLD_ALLOC_FN(vif, vif)
IWL_MLD_ALLOC_FN_STATIC(vif, vif)
/* Constructor function for struct iwl_mld_vif */
static void
@ -456,9 +531,40 @@ iwl_mld_init_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
wiphy_delayed_work_init(&mld_vif->mlo_scan_start_wk,
iwl_mld_mlo_scan_start_wk);
}
if (vif->type == NL80211_IFTYPE_NAN) {
for (int i = 0; i < ARRAY_SIZE(mld_vif->nan.links); i++) {
memset(&mld_vif->nan.links[i], 0, sizeof(mld_vif->nan.links[i]));
mld_vif->nan.links[i].fw_id = FW_CTXT_ID_INVALID;
}
iwl_mld_init_internal_sta(&mld_vif->nan.bcast_sta);
iwl_mld_init_internal_sta(&mld_vif->nan.mgmt_sta);
} else if (vif->type == NL80211_IFTYPE_NAN_DATA) {
iwl_mld_init_internal_sta(&mld_vif->nan.mcast_data_sta);
}
iwl_mld_init_internal_sta(&mld_vif->aux_sta);
}
static int iwl_mld_update_nan_mac(struct iwl_mld *mld,
struct ieee80211_vif *ndi_being_added)
{
struct ieee80211_vif *vif = mld->nan_device_vif;
struct iwl_mld_vif *mld_vif;
if (WARN_ON_ONCE(!vif))
return -ENODEV;
mld_vif = iwl_mld_vif_from_mac80211(vif);
if (!iwl_mld_vif_fw_id_valid(mld_vif))
return 0;
return __iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_MODIFY,
ndi_being_added);
}
int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
@ -468,10 +574,14 @@ int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
iwl_mld_init_vif(mld, vif);
/* NAN interface type is not known to FW */
/* NAN MACs are added to FW only when a schedule is set */
if (vif->type == NL80211_IFTYPE_NAN)
return 0;
/* NAN_DATA interface type is not known to FW, but we need to update NAN MAC */
if (vif->type == NL80211_IFTYPE_NAN_DATA)
return iwl_mld_update_nan_mac(mld, vif);
ret = iwl_mld_allocate_vif_fw_id(mld, &mld_vif->fw_id, vif);
if (ret)
return ret;
@ -483,23 +593,52 @@ int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
return ret;
}
int iwl_mld_add_nan_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
int ret;
lockdep_assert_wiphy(mld->wiphy);
if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
return -EINVAL;
ret = iwl_mld_allocate_vif_fw_id(mld, &mld_vif->fw_id, vif);
if (ret)
return ret;
ret = iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_ADD);
if (ret) {
RCU_INIT_POINTER(mld->fw_id_to_vif[mld_vif->fw_id], NULL);
return ret;
}
mld_vif->nan.mac_added = true;
return 0;
}
void iwl_mld_rm_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
lockdep_assert_wiphy(mld->wiphy);
/* NAN interface type is not known to FW */
if (vif->type == NL80211_IFTYPE_NAN)
if (vif->type == NL80211_IFTYPE_NAN_DATA) {
iwl_mld_update_nan_mac(mld, NULL);
return;
}
if (!iwl_mld_vif_fw_id_valid(mld_vif))
return;
iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_REMOVE);
if (WARN_ON(mld_vif->fw_id >= ARRAY_SIZE(mld->fw_id_to_vif)))
return;
RCU_INIT_POINTER(mld->fw_id_to_vif[mld_vif->fw_id], NULL);
if (vif->type == NL80211_IFTYPE_NAN)
mld_vif->nan.mac_added = false;
iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_VIF,
mld_vif->fw_id);
}
@ -616,24 +755,6 @@ void iwl_mld_handle_probe_resp_data_notif(struct iwl_mld *mld,
kfree_rcu(old_data, rcu_head);
}
void iwl_mld_handle_uapsd_misbehaving_ap_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
struct iwl_uapsd_misbehaving_ap_notif *notif = (void *)pkt->data;
struct ieee80211_vif *vif;
if (IWL_FW_CHECK(mld, notif->mac_id >= ARRAY_SIZE(mld->fw_id_to_vif),
"mac id is invalid: %d\n", notif->mac_id))
return;
vif = wiphy_dereference(mld->wiphy, mld->fw_id_to_vif[notif->mac_id]);
if (WARN_ON(!vif) || ieee80211_vif_is_mld(vif))
return;
IWL_WARN(mld, "uapsd misbehaving AP: %pM\n", vif->bss_conf.bssid);
}
void iwl_mld_handle_datapath_monitor_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
@ -748,6 +869,6 @@ struct ieee80211_vif *iwl_mld_get_bss_vif(struct iwl_mld *mld)
fw_id = __ffs(fw_id_bitmap);
return wiphy_dereference(mld->wiphy,
mld->fw_id_to_vif[fw_id]);
return rcu_dereference_wiphy(mld->wiphy,
mld->fw_id_to_vif[fw_id]);
}

View File

@ -8,6 +8,7 @@
#include <net/mac80211.h>
#include "link.h"
#include "nan.h"
#include "session-protect.h"
#include "d3.h"
#include "fw/api/time-event.h"
@ -137,7 +138,6 @@ struct iwl_mld_emlsr {
* @beacon_inject_active: indicates an active debugfs beacon ie injection
* @low_latency_causes: bit flags, indicating the causes for low-latency,
* see @iwl_mld_low_latency_cause.
* @ps_disabled: indicates that PS is disabled for this interface
* @last_link_activation_time: last time a link was activated, for
* deferring MLO scans (to make them more reliable)
* @mld: pointer to the mld structure.
@ -152,6 +152,16 @@ struct iwl_mld_emlsr {
* p2p device only. Set to %ROC_NUM_ACTIVITIES when not in use.
* @aux_sta: station used for remain on channel. Used in P2P device.
* @mlo_scan_start_wk: worker to start a deferred MLO scan
* @nan: NAN parameters
* @nan.links: NAN links for FW (indexed by FW link ID)
* @nan.mac_added: track whether or not the MAC was added to FW
* @nan.bcast_sta: internal station used for NAN synchronization and discovery
* activities. No queue is associated with it.
* @nan.mgmt_sta: internal station used for NAN management frames, e.g., SDFs
* and NAFs.
* @nan.mcast_data_sta: internal station used for multicast NAN Data frames.
* @nan.tx_igtk: TX IGTK key for NAN, tracked separately since NAN does not
* use the vif links.
*/
struct iwl_mld_vif {
/* Add here fields that need clean up on restart */
@ -167,7 +177,6 @@ struct iwl_mld_vif {
bool beacon_inject_active;
#endif
u8 low_latency_causes;
bool ps_disabled;
time64_t last_link_activation_time;
);
/* And here fields that survive a fw restart */
@ -175,6 +184,16 @@ struct iwl_mld_vif {
struct iwl_mld_link deflink;
struct iwl_mld_link __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
struct {
/* use only with wiphy protection */
struct iwl_mld_nan_link links[IWL_FW_MAX_LINKS];
bool mac_added;
struct iwl_mld_int_sta bcast_sta;
struct iwl_mld_int_sta mgmt_sta;
struct iwl_mld_int_sta mcast_data_sta;
struct ieee80211_key_conf *tx_igtk;
} nan;
struct iwl_mld_emlsr emlsr;
#ifdef CONFIG_PM_SLEEP
@ -206,6 +225,20 @@ iwl_mld_vif_to_mac80211(struct iwl_mld_vif *mld_vif)
/* Call only for interfaces that were added to the driver! */
static inline bool iwl_mld_vif_fw_id_valid(struct iwl_mld_vif *mld_vif)
{
struct ieee80211_vif *vif = iwl_mld_vif_to_mac80211(mld_vif);
switch (vif->type) {
case NL80211_IFTYPE_NAN_DATA:
return false;
case NL80211_IFTYPE_NAN:
if (!mld_vif->nan.mac_added)
return false;
break;
default:
break;
}
/* Should be added to FW */
if (WARN_ON(mld_vif->fw_id >= ARRAY_SIZE(mld_vif->mld->fw_id_to_vif)))
return false;
@ -221,6 +254,12 @@ static inline bool iwl_mld_vif_fw_id_valid(struct iwl_mld_vif *mld_vif)
link_id++) \
if ((mld_link = iwl_mld_link_dereference_check(mld_vif, link_id)))
#define for_each_mld_nan_valid_link(mld_vif, nan_link) \
for (nan_link = &(mld_vif)->nan.links[0]; \
nan_link < &(mld_vif)->nan.links[ARRAY_SIZE((mld_vif)->nan.links)]; \
nan_link++) \
if (nan_link->fw_id != FW_CTXT_ID_INVALID)
/* Retrieve pointer to mld link from mac80211 structures */
static inline struct iwl_mld_link *
iwl_mld_link_from_mac80211(struct ieee80211_bss_conf *bss_conf)
@ -235,6 +274,7 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif);
int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
u32 action);
int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif);
int iwl_mld_add_nan_vif(struct iwl_mld *mld, struct ieee80211_vif *vif);
void iwl_mld_rm_vif(struct iwl_mld *mld, struct ieee80211_vif *vif);
void iwl_mld_set_vif_associated(struct iwl_mld *mld,
struct ieee80211_vif *vif);
@ -245,9 +285,6 @@ void iwl_mld_handle_probe_resp_data_notif(struct iwl_mld *mld,
void iwl_mld_handle_datapath_monitor_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt);
void iwl_mld_handle_uapsd_misbehaving_ap_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt);
void iwl_mld_reset_cca_40mhz_workaround(struct iwl_mld *mld,
struct ieee80211_vif *vif);
@ -258,4 +295,19 @@ static inline bool iwl_mld_vif_low_latency(const struct iwl_mld_vif *mld_vif)
struct ieee80211_vif *iwl_mld_get_bss_vif(struct iwl_mld *mld);
static inline struct iwl_mld_link_sta *
iwl_mld_get_ap_link_sta(struct ieee80211_vif *vif, int link_id)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct ieee80211_sta *ap_sta = mld_vif->ap_sta;
struct iwl_mld_sta *mld_ap_sta;
if (!ap_sta)
return NULL;
mld_ap_sta = iwl_mld_sta_from_mac80211(ap_sta);
return iwl_mld_link_sta_dereference_check(mld_ap_sta, link_id);
}
#endif /* __iwl_mld_iface_h__ */

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024 Intel Corporation
* Copyright (C) 2024, 2026 Intel Corporation
*/
#include "key.h"
#include "iface.h"
@ -12,7 +12,6 @@ static u32 iwl_mld_get_key_flags(struct iwl_mld *mld,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
bool pairwise = key->flags & IEEE80211_KEY_FLAG_PAIRWISE;
bool igtk = key->keyidx == 4 || key->keyidx == 5;
u32 flags = 0;
@ -38,9 +37,6 @@ static u32 iwl_mld_get_key_flags(struct iwl_mld *mld,
break;
}
if (!sta && vif->type == NL80211_IFTYPE_STATION)
sta = mld_vif->ap_sta;
/* If we are installing an iGTK (in AP or STA mode), we need to tell
* the firmware this key will en/decrypt MGMT frames.
* Same goes if we are installing a pairwise key for an MFP station.
@ -53,6 +49,12 @@ static u32 iwl_mld_get_key_flags(struct iwl_mld *mld,
if (key->flags & IEEE80211_KEY_FLAG_SPP_AMSDU)
flags |= IWL_SEC_KEY_FLAG_SPP_AMSDU;
/* When a GTK is configured for a station, it can only be
* used for Rx and never for Tx. Thus, set the NO TX flag.
*/
if (!pairwise && sta)
flags |= IWL_SEC_KEY_FLAG_NO_TX;
return flags;
}
@ -67,6 +69,40 @@ static u32 iwl_mld_get_key_sta_mask(struct iwl_mld *mld,
lockdep_assert_wiphy(mld->wiphy);
if (vif->type == NL80211_IFTYPE_NAN_DATA && !sta) {
/* Older firmware versions do not support transmission of
* multicast data frames.
*/
if (!iwl_mld_nan_use_nan_stations(mld))
return 0;
if (WARN_ON(mld_vif->nan.mcast_data_sta.sta_id ==
IWL_INVALID_STA))
return 0;
return BIT(mld_vif->nan.mcast_data_sta.sta_id);
}
if (vif->type == NL80211_IFTYPE_NAN && !sta) {
/* Older firmware versions do not support installation of
* IGTK/BIGTK keys.
*/
if (!iwl_mld_nan_use_nan_stations(mld))
return 0;
if (WARN_ON(mld_vif->nan.bcast_sta.sta_id == IWL_INVALID_STA ||
mld_vif->nan.mgmt_sta.sta_id == IWL_INVALID_STA))
return 0;
if (key->keyidx >= 4 && key->keyidx <= 5)
return BIT(mld_vif->nan.mgmt_sta.sta_id);
if (key->keyidx >= 6 && key->keyidx <= 7)
return BIT(mld_vif->nan.bcast_sta.sta_id);
return 0;
}
/* AP group keys are per link and should be on the mcast/bcast STA */
if (vif->type == NL80211_IFTYPE_AP &&
!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) {
@ -92,11 +128,7 @@ static u32 iwl_mld_get_key_sta_mask(struct iwl_mld *mld,
return BIT(link->mcast_sta.sta_id);
}
/* for client mode use the AP STA also for group keys */
if (!sta && vif->type == NL80211_IFTYPE_STATION)
sta = mld_vif->ap_sta;
/* STA should be non-NULL now */
/* STA should be non-NULL */
if (WARN_ON(!sta))
return 0;
@ -178,34 +210,69 @@ static void iwl_mld_remove_key_from_fw(struct iwl_mld *mld, u32 sta_mask,
iwl_mld_send_cmd_pdu(mld, WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD), &cmd);
}
static struct ieee80211_key_conf **
iwl_mld_get_igtk_ptr(struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
/* key's link ID is set to -1 for non-MLO */
int link_id = key->link_id < 0 ? 0 : key->link_id;
struct iwl_mld_link_sta *mld_ap_link_sta;
struct iwl_mld_link *mld_link;
struct iwl_mld_sta *mld_sta;
if (key->keyidx != 4 && key->keyidx != 5)
return NULL;
switch (vif->type) {
case NL80211_IFTYPE_STATION:
if (WARN_ON(!sta))
return NULL;
mld_sta = iwl_mld_sta_from_mac80211(sta);
mld_ap_link_sta = iwl_mld_link_sta_dereference_check(mld_sta,
link_id);
if (WARN_ON(!mld_ap_link_sta))
return NULL;
return &mld_ap_link_sta->rx_igtk;
case NL80211_IFTYPE_NAN:
if (sta) {
mld_sta = iwl_mld_sta_from_mac80211(sta);
return &mld_sta->deflink.rx_igtk;
}
return &mld_vif->nan.tx_igtk;
case NL80211_IFTYPE_AP:
mld_link = iwl_mld_link_dereference_check(mld_vif, link_id);
if (WARN_ON(!mld_link))
return NULL;
return &mld_link->tx_igtk;
default:
WARN_ONCE(1, "invalid iftype %d for IGTK\n", vif->type);
return NULL;
}
}
void iwl_mld_remove_key(struct iwl_mld *mld, struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
u32 sta_mask = iwl_mld_get_key_sta_mask(mld, vif, sta, key);
u32 key_flags = iwl_mld_get_key_flags(mld, vif, sta, key);
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct ieee80211_key_conf **igtk_ptr;
lockdep_assert_wiphy(mld->wiphy);
if (!sta_mask)
return;
if (key->keyidx == 4 || key->keyidx == 5) {
struct iwl_mld_link *mld_link;
unsigned int link_id = 0;
/* set to -1 for non-MLO right now */
if (key->link_id >= 0)
link_id = key->link_id;
mld_link = iwl_mld_link_dereference_check(mld_vif, link_id);
if (WARN_ON(!mld_link))
return;
if (mld_link->igtk == key)
mld_link->igtk = NULL;
igtk_ptr = iwl_mld_get_igtk_ptr(vif, sta, key);
if (igtk_ptr && *igtk_ptr == key) {
*igtk_ptr = NULL;
mld->num_igtks--;
}
@ -222,46 +289,43 @@ int iwl_mld_add_key(struct iwl_mld *mld,
{
u32 sta_mask = iwl_mld_get_key_sta_mask(mld, vif, sta, key);
u32 key_flags = iwl_mld_get_key_flags(mld, vif, sta, key);
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct iwl_mld_link *mld_link = NULL;
bool igtk = key->keyidx == 4 || key->keyidx == 5;
struct ieee80211_key_conf **igtk_ptr;
int ret;
lockdep_assert_wiphy(mld->wiphy);
if (!sta_mask)
return -EINVAL;
if (!sta_mask) {
/* for NAN (GTK) indicate SW-only, it's not used at all */
if (vif->type == NL80211_IFTYPE_NAN_DATA && !sta &&
!iwl_mld_nan_use_nan_stations(mld))
return 1;
if (igtk) {
if (mld->num_igtks == IWL_MAX_NUM_IGTKS)
/* otherwise that's not valid */
IWL_WARN(mld, "empty STA mask for key %d\n", key->keyidx);
return -EINVAL;
}
igtk_ptr = iwl_mld_get_igtk_ptr(vif, sta, key);
if (igtk_ptr) {
if (mld->num_igtks == mld->fw->ucode_capa.num_mcast_key_entries)
return -EOPNOTSUPP;
u8 link_id = 0;
/* set to -1 for non-MLO right now */
if (key->link_id >= 0)
link_id = key->link_id;
mld_link = iwl_mld_link_dereference_check(mld_vif, link_id);
if (WARN_ON(!mld_link))
return -EINVAL;
if (mld_link->igtk) {
if (*igtk_ptr) {
IWL_DEBUG_MAC80211(mld, "remove old IGTK %d\n",
mld_link->igtk->keyidx);
iwl_mld_remove_key(mld, vif, sta, mld_link->igtk);
(*igtk_ptr)->keyidx);
iwl_mld_remove_key(mld, vif, sta, *igtk_ptr);
}
WARN_ON(mld_link->igtk);
}
ret = iwl_mld_add_key_to_fw(mld, sta_mask, key_flags, key);
if (ret)
if (ret) {
IWL_WARN(mld, "failed to add key to FW (%d)\n", ret);
return ret;
}
if (mld_link) {
mld_link->igtk = key;
if (igtk_ptr) {
WARN_ON(*igtk_ptr);
*igtk_ptr = key;
mld->num_igtks++;
}

View File

@ -16,14 +16,34 @@
#include "fw/api/context.h"
#include "fw/dbg.h"
static int iwl_mld_send_link_cmd(struct iwl_mld *mld,
struct iwl_link_config_cmd *cmd,
enum iwl_ctxt_action action)
/**
* struct iwl_mld_link_chan_load_threshold - channel load thresholds
* @high_lim: level up transition thresholds, in percentage
* @low_lim: level down transition thresholds, in percentage
*/
struct iwl_mld_link_chan_load_threshold {
u8 high_lim;
u8 low_lim;
};
static const struct iwl_mld_link_chan_load_threshold
link_chan_load_thresh_tbl[] = {
[LINK_CHAN_LOAD_LVL1] = { .high_lim = 45, .low_lim = 40 },
[LINK_CHAN_LOAD_LVL2] = { .high_lim = 70, .low_lim = 65 },
[LINK_CHAN_LOAD_LVL3] = { .high_lim = 85, .low_lim = 80 },
};
int iwl_mld_send_link_cmd(struct iwl_mld *mld,
struct iwl_link_config_cmd *cmd,
enum iwl_ctxt_action action)
{
int ret;
lockdep_assert_wiphy(mld->wiphy);
if (WARN_ON_ONCE(cmd->link_id == cpu_to_le32(FW_CTXT_ID_INVALID)))
return -EINVAL;
cmd->action = cpu_to_le32(action);
ret = iwl_mld_send_cmd_pdu(mld,
WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD),
@ -315,10 +335,28 @@ iwl_mld_change_link_in_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link,
link_sta_dereference_check(mld_vif->ap_sta,
link->link_id);
if (!WARN_ON(!link_sta) && link_sta->he_cap.has_he &&
if (WARN_ON(!link_sta))
return -EINVAL;
if (link_sta->he_cap.has_he &&
link_sta->he_cap.he_cap_elem.mac_cap_info[5] &
IEEE80211_HE_MAC_CAP5_OM_CTRL_UL_MU_DATA_DIS_RX)
cmd.ul_mu_data_disable = 1;
if (link_sta->uhr_cap.has_uhr &&
link_sta->uhr_cap.mac.mac_cap[0] &
IEEE80211_UHR_MAC_CAP0_DPS_ASSIST_SUPP)
flags |= LINK_FLG_DPS;
if (link_sta->uhr_cap.has_uhr &&
link_sta->uhr_cap.mac.mac_cap[1] &
IEEE80211_UHR_MAC_CAP1_DUO_SUPP)
flags |= LINK_FLG_DUO;
if (link_sta->uhr_cap.has_uhr &&
mld_vif->ap_sta->ext_mld_capa_ops &
IEEE80211_UHR_ML_EXT_MLD_CAPA_ML_PM)
flags |= LINK_FLG_MLPM;
}
cmd.htc_trig_based_pkt_ext = link->htc_trig_based_pkt_ext;
@ -354,6 +392,21 @@ iwl_mld_change_link_in_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link,
if (WARN_ON(changes & LINK_CONTEXT_MODIFY_EHT_PARAMS))
changes &= ~LINK_CONTEXT_MODIFY_EHT_PARAMS;
if (link->uhr_support && link->npca.enabled) {
flags |= LINK_FLG_NPCA;
if (link->npca.moplen)
cmd.npca_params.flags |= IWL_NPCA_FLAG_MAC_HDR_BASED;
cmd.npca_params.dis_subch_bmap =
cpu_to_le16(link->chanreq.oper.npca_punctured);
cmd.npca_params.initial_qsrc = link->npca.init_qsrc;
cmd.npca_params.min_dur_threshold = link->npca.min_dur_thresh;
/* spec/mac80211 have these in units of 4 usec */
cmd.npca_params.switch_delay =
4 * link->npca.switch_delay;
cmd.npca_params.switch_back_delay =
4 * link->npca.switch_back_delay;
}
send_cmd:
cmd.modify_mask = cpu_to_le32(changes);
cmd.flags = cpu_to_le32(flags);
@ -437,7 +490,8 @@ iwl_mld_rm_link_from_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link)
iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE);
}
static IWL_MLD_ALLOC_FN(link, bss_conf)
IWL_MLD_ALLOC_FN(link, bss_conf)
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_allocate_link_fw_id);
/* Constructor function for struct iwl_mld_link */
static int
@ -608,15 +662,20 @@ void iwl_mld_handle_missed_beacon_notif(struct iwl_mld *mld,
* OR more than IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED
* on current link and the link's bss_param_ch_count has changed on
* the other link's beacon.
*
* When both links lose beacons, keep the primary (symmetric failure).
* When only the current link is sick, keep the other link.
*/
if ((missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS &&
scnd_lnk_bcn_lost >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS) ||
missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH ||
(bss_param_ch_cnt_link_id != link_id &&
missed_bcon >=
IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED)) {
if (missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS &&
scnd_lnk_bcn_lost >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS) {
iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_MISSED_BEACON,
iwl_mld_get_primary_link(vif));
} else if (missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH ||
(bss_param_ch_cnt_link_id != link_id &&
missed_bcon >=
IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED)) {
iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_MISSED_BEACON,
iwl_mld_get_other_link(vif, link_id));
}
}
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_handle_missed_beacon_notif);
@ -651,35 +710,131 @@ struct iwl_mld_rssi_to_grade {
u16 grade;
};
#define RSSI_TO_GRADE_LINE(_lb, _hb_uhb, _grade) \
#define RSSI_TO_GRADE_LINE_WITH_LB(_lb, _hb_uhb, _grade) \
{ \
.rssi = {_lb, _hb_uhb}, \
.grade = _grade \
}
/*
* This array must be sorted by increasing RSSI for proper functionality.
* The grades are actually estimated throughput, represented as fixed-point
* with a scale factor of 1/10.
*/
static const struct iwl_mld_rssi_to_grade rssi_to_grade_map[] = {
RSSI_TO_GRADE_LINE(-85, -89, 172),
RSSI_TO_GRADE_LINE(-83, -86, 344),
RSSI_TO_GRADE_LINE(-82, -85, 516),
RSSI_TO_GRADE_LINE(-80, -83, 688),
RSSI_TO_GRADE_LINE(-77, -79, 1032),
RSSI_TO_GRADE_LINE(-73, -76, 1376),
RSSI_TO_GRADE_LINE(-70, -74, 1548),
RSSI_TO_GRADE_LINE(-69, -72, 1720),
RSSI_TO_GRADE_LINE(-65, -68, 2064),
RSSI_TO_GRADE_LINE(-61, -66, 2294),
RSSI_TO_GRADE_LINE(-58, -61, 2580),
RSSI_TO_GRADE_LINE(-55, -58, 2868),
RSSI_TO_GRADE_LINE(-46, -55, 3098),
RSSI_TO_GRADE_LINE(-43, -54, 3442)
#define RSSI_TO_GRADE_LINE(_rssi, _grade) \
RSSI_TO_GRADE_LINE_WITH_LB(_rssi, _rssi, _grade)
/* Tables must be sorted by increasing RSSI */
/* 20 MHz Operational BW Grading Table */
static const struct iwl_mld_rssi_to_grade rssi_to_grade_20mhz[] = {
RSSI_TO_GRADE_LINE_WITH_LB(-94, -95, 9),
RSSI_TO_GRADE_LINE_WITH_LB(-92, -93, 17),
RSSI_TO_GRADE_LINE_WITH_LB(-90, -90, 34),
RSSI_TO_GRADE_LINE_WITH_LB(-87, -87, 52),
RSSI_TO_GRADE_LINE_WITH_LB(-83, -84, 69),
RSSI_TO_GRADE_LINE_WITH_LB(-79, -80, 103),
RSSI_TO_GRADE_LINE_WITH_LB(-75, -75, 137),
RSSI_TO_GRADE_LINE_WITH_LB(-72, -73, 155),
RSSI_TO_GRADE_LINE_WITH_LB(-70, -71, 172),
RSSI_TO_GRADE_LINE_WITH_LB(-67, -68, 206),
RSSI_TO_GRADE_LINE_WITH_LB(-64, -65, 230),
RSSI_TO_GRADE_LINE_WITH_LB(-59, -60, 258),
RSSI_TO_GRADE_LINE_WITH_LB(-57, -58, 287),
RSSI_TO_GRADE_LINE_WITH_LB(-52, -53, 310),
RSSI_TO_GRADE_LINE_WITH_LB(-50, -50, 345),
};
#define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade)
/* 40 MHz Operational BW Grading Table */
static const struct iwl_mld_rssi_to_grade rssi_to_grade_40mhz[] = {
RSSI_TO_GRADE_LINE_WITH_LB(-95, -95, 9),
RSSI_TO_GRADE_LINE_WITH_LB(-93, -93, 17),
RSSI_TO_GRADE_LINE_WITH_LB(-91, -92, 18),
RSSI_TO_GRADE_LINE_WITH_LB(-89, -90, 34),
RSSI_TO_GRADE_LINE_WITH_LB(-87, -87, 68),
RSSI_TO_GRADE_LINE_WITH_LB(-84, -84, 104),
RSSI_TO_GRADE_LINE_WITH_LB(-80, -81, 138),
RSSI_TO_GRADE_LINE_WITH_LB(-77, -77, 206),
RSSI_TO_GRADE_LINE_WITH_LB(-72, -72, 274),
RSSI_TO_GRADE_LINE_WITH_LB(-69, -70, 310),
RSSI_TO_GRADE_LINE_WITH_LB(-67, -68, 344),
RSSI_TO_GRADE_LINE_WITH_LB(-64, -65, 412),
RSSI_TO_GRADE_LINE_WITH_LB(-61, -62, 460),
RSSI_TO_GRADE_LINE_WITH_LB(-56, -57, 516),
RSSI_TO_GRADE_LINE_WITH_LB(-54, -55, 574),
RSSI_TO_GRADE_LINE_WITH_LB(-49, -50, 620),
RSSI_TO_GRADE_LINE_WITH_LB(-46, -47, 690),
};
/* 80 MHz Operational BW Grading Table */
static const struct iwl_mld_rssi_to_grade rssi_to_grade_80mhz[] = {
RSSI_TO_GRADE_LINE(-95, 9),
RSSI_TO_GRADE_LINE(-93, 17),
RSSI_TO_GRADE_LINE(-92, 18),
RSSI_TO_GRADE_LINE(-90, 34),
RSSI_TO_GRADE_LINE(-89, 36),
RSSI_TO_GRADE_LINE(-87, 68),
RSSI_TO_GRADE_LINE(-83, 136),
RSSI_TO_GRADE_LINE(-80, 208),
RSSI_TO_GRADE_LINE(-77, 276),
RSSI_TO_GRADE_LINE(-74, 412),
RSSI_TO_GRADE_LINE(-69, 548),
RSSI_TO_GRADE_LINE(-67, 620),
RSSI_TO_GRADE_LINE(-66, 688),
RSSI_TO_GRADE_LINE(-61, 824),
RSSI_TO_GRADE_LINE(-59, 920),
RSSI_TO_GRADE_LINE(-54, 1032),
RSSI_TO_GRADE_LINE(-52, 1148),
RSSI_TO_GRADE_LINE(-47, 1240),
RSSI_TO_GRADE_LINE(-44, 1380),
};
/* 160 MHz Operational BW Grading Table */
static const struct iwl_mld_rssi_to_grade rssi_to_grade_160mhz[] = {
RSSI_TO_GRADE_LINE(-95, 9),
RSSI_TO_GRADE_LINE(-93, 17),
RSSI_TO_GRADE_LINE(-92, 18),
RSSI_TO_GRADE_LINE(-90, 34),
RSSI_TO_GRADE_LINE(-89, 36),
RSSI_TO_GRADE_LINE(-87, 68),
RSSI_TO_GRADE_LINE(-86, 72),
RSSI_TO_GRADE_LINE(-84, 136),
RSSI_TO_GRADE_LINE(-81, 272),
RSSI_TO_GRADE_LINE(-78, 416),
RSSI_TO_GRADE_LINE(-75, 552),
RSSI_TO_GRADE_LINE(-71, 824),
RSSI_TO_GRADE_LINE(-67, 1096),
RSSI_TO_GRADE_LINE(-65, 1240),
RSSI_TO_GRADE_LINE(-63, 1376),
RSSI_TO_GRADE_LINE(-59, 1648),
RSSI_TO_GRADE_LINE(-57, 1840),
RSSI_TO_GRADE_LINE(-52, 2064),
RSSI_TO_GRADE_LINE(-50, 2296),
RSSI_TO_GRADE_LINE(-46, 2480),
RSSI_TO_GRADE_LINE(-42, 2760),
};
/* 320 MHz Operational BW Grading Table */
static const struct iwl_mld_rssi_to_grade rssi_to_grade_320mhz[] = {
RSSI_TO_GRADE_LINE(-95, 9),
RSSI_TO_GRADE_LINE(-93, 17),
RSSI_TO_GRADE_LINE(-92, 18),
RSSI_TO_GRADE_LINE(-90, 34),
RSSI_TO_GRADE_LINE(-89, 36),
RSSI_TO_GRADE_LINE(-87, 68),
RSSI_TO_GRADE_LINE(-86, 72),
RSSI_TO_GRADE_LINE(-84, 136),
RSSI_TO_GRADE_LINE(-83, 144),
RSSI_TO_GRADE_LINE(-81, 272),
RSSI_TO_GRADE_LINE(-78, 544),
RSSI_TO_GRADE_LINE(-75, 832),
RSSI_TO_GRADE_LINE(-72, 1104),
RSSI_TO_GRADE_LINE(-69, 1648),
RSSI_TO_GRADE_LINE(-64, 2192),
RSSI_TO_GRADE_LINE(-62, 2480),
RSSI_TO_GRADE_LINE(-61, 2752),
RSSI_TO_GRADE_LINE(-57, 3296),
RSSI_TO_GRADE_LINE(-55, 3680),
RSSI_TO_GRADE_LINE(-50, 4128),
RSSI_TO_GRADE_LINE(-47, 4592),
RSSI_TO_GRADE_LINE(-43, 4960),
RSSI_TO_GRADE_LINE(-40, 5520),
};
#define DEFAULT_CHAN_LOAD_2GHZ 30
#define DEFAULT_CHAN_LOAD_5GHZ 15
@ -689,31 +844,27 @@ static const struct iwl_mld_rssi_to_grade rssi_to_grade_map[] = {
#define SCALE_FACTOR 256
#define MAX_CHAN_LOAD 256
static unsigned int
iwl_mld_get_n_subchannels(const struct ieee80211_bss_conf *link_conf)
static void
iwl_mld_apply_puncturing_penalty(const struct ieee80211_bss_conf *link_conf,
unsigned int *grade, int bw_mhz)
{
enum nl80211_chan_width chan_width =
link_conf->chanreq.oper.width;
int mhz = nl80211_chan_width_to_mhz(chan_width);
unsigned int n_subchannels;
unsigned int n_punctured, n_subchannels;
if (WARN_ONCE(mhz < 20 || mhz > 320,
"Invalid channel width : (%d)\n", mhz))
return 1;
/* Puncturing only applicable for BW >= 80 MHz */
if (bw_mhz < 80)
return;
/* total number of subchannels */
n_subchannels = mhz / 20;
n_punctured = hweight16(link_conf->chanreq.oper.punctured);
if (n_punctured == 0)
return;
/* No puncturing if less than 80 MHz */
if (mhz >= 80)
n_subchannels -= hweight16(link_conf->chanreq.oper.punctured);
n_subchannels = bw_mhz / 20;
return n_subchannels;
*grade = *grade * (n_subchannels - n_punctured) / n_subchannels;
}
static int
iwl_mld_get_chan_load_from_element(struct iwl_mld *mld,
struct ieee80211_bss_conf *link_conf)
int iwl_mld_get_chan_load_from_element(struct iwl_mld *mld,
struct ieee80211_bss_conf *link_conf)
{
const struct cfg80211_bss_ies *ies;
const struct element *bss_load_elem = NULL;
@ -787,6 +938,51 @@ int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld,
return chan_load;
}
/* Returns whether internal MLO Scan needs to be triggered */
bool iwl_mld_chan_load_requires_scan(struct iwl_mld *mld,
struct ieee80211_bss_conf *link_conf,
u32 new_chan_load)
{
struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf);
enum iwl_mld_link_chan_load_level new_lvl;
bool scan_trig = false;
if (WARN_ON(!mld_link))
return false;
/* For each Level,
* First check if high limit threshold crosses
* If not then, check if low limit threshold crosses
* Set new level based on low limit thresh only if old level
* is not lower than level threshold
*/
for (new_lvl = LINK_CHAN_LOAD_LVL_MAX;
new_lvl > LINK_CHAN_LOAD_LVL_NONE; new_lvl--) {
if (new_chan_load >=
link_chan_load_thresh_tbl[new_lvl].high_lim)
break;
if (new_chan_load >=
link_chan_load_thresh_tbl[new_lvl].low_lim &&
mld_link->chan_load_lvl >= new_lvl)
break;
}
/* Trigger scan only for Level Up Transition */
if (new_lvl > mld_link->chan_load_lvl)
scan_trig = true;
IWL_DEBUG_EHT(mld,
"Link %d: chan_load=%d%%, old_lvl=%d, new_lvl=%d, scan_trig=%d\n",
link_conf->link_id, new_chan_load,
mld_link->chan_load_lvl, new_lvl, scan_trig);
/* Update computed new level */
mld_link->chan_load_lvl = new_lvl;
return scan_trig;
}
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_chan_load_requires_scan);
static unsigned int
iwl_mld_get_default_chan_load(struct ieee80211_bss_conf *link_conf)
{
@ -828,16 +1024,214 @@ iwl_mld_get_avail_chan_load(struct iwl_mld *mld,
return MAX_CHAN_LOAD - iwl_mld_get_chan_load(mld, link_conf);
}
static s8
iwl_mld_get_dup_beacon_rssi_adjust(struct iwl_mld *mld,
struct ieee80211_bss_conf *link_conf)
{
const struct ieee80211_he_6ghz_oper *he_6ghz_oper;
const struct cfg80211_bss_ies *beacon_ies;
const struct element *elem;
/* Duplicated beacon feature is only specific to 6 GHz */
if (WARN_ONCE(link_conf->chanreq.oper.chan->band != NL80211_BAND_6GHZ,
"Unexpected band %d\n",
link_conf->chanreq.oper.chan->band))
return 0;
lockdep_assert_wiphy(mld->wiphy);
beacon_ies = wiphy_dereference(mld->wiphy, link_conf->bss->beacon_ies);
if (!beacon_ies)
return 0;
elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION,
beacon_ies->data, beacon_ies->len);
if (!elem ||
elem->datalen < sizeof(struct ieee80211_he_operation) + 1 ||
elem->datalen < ieee80211_he_oper_size(&elem->data[1]))
return 0;
he_6ghz_oper = ieee80211_he_6ghz_oper((const void *)&elem->data[1]);
if (!he_6ghz_oper)
return 0;
if (!(he_6ghz_oper->control & IEEE80211_HE_6GHZ_OPER_CTRL_DUP_BEACON))
return 0;
/* Apply adjustment based on operational bandwidth */
switch (link_conf->chanreq.oper.width) {
case NL80211_CHAN_WIDTH_20:
case NL80211_CHAN_WIDTH_20_NOHT:
return 0;
case NL80211_CHAN_WIDTH_40:
return 3;
case NL80211_CHAN_WIDTH_80:
return 6;
case NL80211_CHAN_WIDTH_160:
return 9;
case NL80211_CHAN_WIDTH_320:
return 12;
default:
WARN_ONCE(1, "Unexpected channel width: %d\n",
link_conf->chanreq.oper.width);
return 0;
}
}
static s8 iwl_mld_get_primary_psd(const struct ieee80211_parsed_tpe_psd *psd,
const struct cfg80211_chan_def *chandef,
int bw_mhz)
{
int start_freq, primary_idx;
if (!psd->valid)
return S8_MAX;
start_freq = chandef->center_freq1 - (bw_mhz / 2);
primary_idx = (chandef->chan->center_freq - start_freq - 10) / 20;
if (primary_idx < 0 || primary_idx >= psd->count)
return S8_MAX;
/* TPE element stores PSD limit as value * 2 */
return psd->power[primary_idx] / 2;
}
static s8 iwl_mld_get_psd_eirp_rssi_adjust(struct ieee80211_bss_conf *link_conf)
{
const struct ieee80211_parsed_tpe *tpe = &link_conf->tpe;
s8 psd_20mhz, psd_oper, psd_local, psd_reg, psd_boost;
s8 min_20mhz, min_oper, adjustment, ap_power_limit;
s8 psd_avg_local = S8_MAX, psd_avg_reg = S8_MAX;
s8 eirp_20mhz, eirp_oper, eirp_local, eirp_reg;
int bw_mhz, num_subchans;
u8 bw_index;
/* PSD/EIRP adjustment is only specific to 6 GHz */
if (WARN_ONCE(link_conf->chanreq.oper.chan->band != NL80211_BAND_6GHZ,
"PSD/EIRP adjustment called for non-6 GHz band %d\n",
link_conf->chanreq.oper.chan->band))
return 0;
bw_mhz = nl80211_chan_width_to_mhz(link_conf->chanreq.oper.width);
switch (bw_mhz) {
case 20:
bw_index = 0;
break;
case 40:
bw_index = 1;
break;
case 80:
bw_index = 2;
break;
case 160:
bw_index = 3;
break;
case 320:
bw_index = 4;
break;
default:
WARN_ONCE(1, "Unexpected bandwidth: %d MHz\n", bw_mhz);
return 0;
}
if (link_conf->power_type == IEEE80211_REG_VLP_AP)
ap_power_limit = 14;
else
ap_power_limit = 23;
/* Primary 20 MHz PSD */
psd_local = iwl_mld_get_primary_psd(&tpe->psd_local[0],
&link_conf->chanreq.oper,
bw_mhz);
psd_reg = iwl_mld_get_primary_psd(&tpe->psd_reg_client[0],
&link_conf->chanreq.oper,
bw_mhz);
psd_20mhz = min(psd_local, psd_reg);
/* TPE element stores EIRP limit as value * 2 */
eirp_local = (tpe->max_local[0].valid && tpe->max_local[0].count > 0) ?
tpe->max_local[0].power[0] / 2 : S8_MAX;
eirp_reg = (tpe->max_reg_client[0].valid &&
tpe->max_reg_client[0].count > 0) ?
tpe->max_reg_client[0].power[0] / 2 : S8_MAX;
eirp_20mhz = min(eirp_local, eirp_reg);
num_subchans = bw_mhz / 20;
if (tpe->psd_local[0].valid) {
int sum_local = 0, valid_local = 0;
int count_local = min(num_subchans, tpe->psd_local[0].count);
for (int i = 0; i < count_local; i++) {
if (tpe->psd_local[0].power[i] != S8_MIN) {
sum_local += tpe->psd_local[0].power[i];
valid_local++;
}
}
/* TPE element stores PSD limit as value * 2 */
if (valid_local > 0)
psd_avg_local = sum_local / valid_local / 2;
}
if (tpe->psd_reg_client[0].valid) {
int sum_reg = 0, valid_reg = 0;
int count_reg = min(num_subchans, tpe->psd_reg_client[0].count);
for (int i = 0; i < count_reg; i++) {
if (tpe->psd_reg_client[0].power[i] != S8_MIN) {
sum_reg +=
tpe->psd_reg_client[0].power[i];
valid_reg++;
}
}
/* TPE element stores PSD limit as value * 2 */
if (valid_reg > 0)
psd_avg_reg = sum_reg / valid_reg / 2;
}
psd_oper = min(psd_avg_local, psd_avg_reg);
/* TPE element stores EIRP limit as value * 2 */
eirp_local = (tpe->max_local[0].valid &&
tpe->max_local[0].count > bw_index) ?
tpe->max_local[0].power[bw_index] / 2 : S8_MAX;
eirp_reg = (tpe->max_reg_client[0].valid &&
tpe->max_reg_client[0].count > bw_index) ?
tpe->max_reg_client[0].power[bw_index] / 2 : S8_MAX;
eirp_oper = min(eirp_local, eirp_reg);
min_20mhz = min(ap_power_limit, min(eirp_20mhz, psd_20mhz));
/* PSD boost: 10*log10(BW/20) approximated as 3*ilog2(BW/20) */
psd_boost = 3 * ilog2(bw_mhz / 20);
/* Use int for psd_oper + psd_boost to prevent s8 overflow */
min_oper = min(ap_power_limit,
min(eirp_oper,
(s8)min_t(int, psd_oper + psd_boost, S8_MAX)));
adjustment = max(min_oper - min_20mhz, 0);
return adjustment;
}
/* This function calculates the grade of a link. Returns 0 in error case */
unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld,
struct ieee80211_bss_conf *link_conf)
{
const struct iwl_mld_rssi_to_grade *grade_table;
enum nl80211_band band;
int rssi_idx;
int rssi_idx, table_size, bw_mhz;
s32 link_rssi;
unsigned int grade = MAX_GRADE;
unsigned int grade;
if (WARN_ON_ONCE(!link_conf))
if (WARN_ON_ONCE(!link_conf || !link_conf->bss))
return 0;
bw_mhz = nl80211_chan_width_to_mhz(link_conf->chanreq.oper.width);
if (bw_mhz < 0)
return 0;
band = link_conf->chanreq.oper.chan->band;
@ -852,25 +1246,64 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld,
* For 6 GHz the RSSI of the beacons is lower than
* the RSSI of the data.
*/
if (band == NL80211_BAND_6GHZ && link_rssi)
link_rssi += 4;
if (band == NL80211_BAND_6GHZ && link_rssi) {
s8 rssi_adj_6g =
iwl_mld_get_dup_beacon_rssi_adjust(mld, link_conf);
if (!rssi_adj_6g)
rssi_adj_6g =
iwl_mld_get_psd_eirp_rssi_adjust(link_conf);
if (!rssi_adj_6g)
rssi_adj_6g = 4;
link_rssi += rssi_adj_6g;
}
/* Select grading table based on operational bandwidth */
switch (bw_mhz) {
case 20:
grade_table = rssi_to_grade_20mhz;
table_size = ARRAY_SIZE(rssi_to_grade_20mhz);
break;
case 40:
grade_table = rssi_to_grade_40mhz;
table_size = ARRAY_SIZE(rssi_to_grade_40mhz);
break;
case 80:
grade_table = rssi_to_grade_80mhz;
table_size = ARRAY_SIZE(rssi_to_grade_80mhz);
break;
case 160:
grade_table = rssi_to_grade_160mhz;
table_size = ARRAY_SIZE(rssi_to_grade_160mhz);
break;
case 320:
grade_table = rssi_to_grade_320mhz;
table_size = ARRAY_SIZE(rssi_to_grade_320mhz);
break;
default:
WARN_ONCE(1, "Invalid bandwidth: %d MHz\n", bw_mhz);
return 0;
}
/* Initialize grade to maximum value from selected table */
grade = grade_table[table_size - 1].grade;
rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1;
/* No valid RSSI - take the lowest grade */
/* No valid RSSI - take the lowest grade from selected table */
if (!link_rssi)
link_rssi = rssi_to_grade_map[0].rssi[rssi_idx];
link_rssi = grade_table[0].rssi[rssi_idx];
IWL_DEBUG_EHT(mld,
"Calculating grade of link %d: band = %d, bandwidth = %d, punctured subchannels =0x%x RSSI = %d\n",
link_conf->link_id, band,
link_conf->chanreq.oper.width,
"Calculating grade of link %d: band = %d, BW = %d, punct subchannels = 0x%x RSSI = %d\n",
link_conf->link_id, band, bw_mhz,
link_conf->chanreq.oper.punctured, link_rssi);
/* Get grade based on RSSI */
for (int i = 0; i < ARRAY_SIZE(rssi_to_grade_map); i++) {
const struct iwl_mld_rssi_to_grade *line =
&rssi_to_grade_map[i];
/* Get grade based on RSSI from the bandwidth-specific table */
for (int i = 0; i < table_size; i++) {
const struct iwl_mld_rssi_to_grade *line = &grade_table[i];
if (link_rssi > line->rssi[rssi_idx])
continue;
@ -879,8 +1312,10 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld,
}
/* Apply the channel load and puncturing factors */
grade = grade * iwl_mld_get_avail_chan_load(mld, link_conf) / SCALE_FACTOR;
grade = grade * iwl_mld_get_n_subchannels(link_conf);
grade = grade * iwl_mld_get_avail_chan_load(mld, link_conf) /
SCALE_FACTOR;
iwl_mld_apply_puncturing_penalty(link_conf, &grade, bw_mhz);
IWL_DEBUG_EHT(mld, "Link %d's grade: %d\n", link_conf->link_id, grade);

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#ifndef __iwl_mld_link_h__
#define __iwl_mld_link_h__
@ -10,6 +10,14 @@
#include "mld.h"
#include "sta.h"
enum iwl_mld_link_chan_load_level {
LINK_CHAN_LOAD_LVL_NONE,
LINK_CHAN_LOAD_LVL1,
LINK_CHAN_LOAD_LVL2,
LINK_CHAN_LOAD_LVL3,
LINK_CHAN_LOAD_LVL_MAX = LINK_CHAN_LOAD_LVL3
};
/**
* struct iwl_probe_resp_data - data for NoA/CSA updates
* @rcu_head: used for freeing the data on update
@ -28,14 +36,17 @@ struct iwl_probe_resp_data {
* @rcu_head: RCU head for freeing this data.
* @fw_id: the fw id of the link.
* @active: if the link is active or not.
* @avg_signal: The current average signal of beacons [dBm] retrieved from
* firmware per-link periodic stats (STATISTICS_OPER_NOTIF).
* @queue_params: QoS data from mac80211. This is updated with a call to
* drv_conf_tx per each AC, and then notified once with BSS_CHANGED_QOS.
* So we store it here and then send one link cmd for all the ACs.
* @chan_ctx: pointer to the channel context assigned to the link. If a link
* has an assigned channel context it means that it is active.
* @he_ru_2mhz_block: 26-tone RU OFDMA transmissions should be blocked.
* @igtk: fw can only have one IGTK at a time, whereas mac80211 can have two.
* This tracks the one IGTK that currently exists in FW.
* @tx_igtk: FW can only have one IGTK per MAC at a time, whereas mac80211 can
* have two. This tracks the one IGTK that currently exists in FW, for TX
* purposes. The RX IGTKs are tracked per station.
* @bigtks: BIGTKs of the AP. Only valid for STA mode.
* @bcast_sta: station used for broadcast packets. Used in AP, GO and IBSS.
* @mcast_sta: station used for multicast packets. Used in AP, GO and IBSS.
@ -49,6 +60,8 @@ struct iwl_probe_resp_data {
* @silent_deactivation: next deactivation needs to be silent.
* @probe_resp_data: data from FW notification to store NOA related data to be
* inserted into probe response.
* @chan_load_lvl: current channel load level for a link, computed based on
* channel load by others on a link.
*/
struct iwl_mld_link {
struct rcu_head rcu_head;
@ -57,11 +70,13 @@ struct iwl_mld_link {
struct_group(zeroed_on_hw_restart,
u8 fw_id;
bool active;
s8 avg_signal;
struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS];
struct ieee80211_chanctx_conf __rcu *chan_ctx;
bool he_ru_2mhz_block;
struct ieee80211_key_conf *igtk;
struct ieee80211_key_conf *tx_igtk;
struct ieee80211_key_conf __rcu *bigtks[2];
enum iwl_mld_link_chan_load_level chan_load_lvl;
);
/* And here fields that survive a fw restart */
struct iwl_mld_int_sta bcast_sta;
@ -99,6 +114,13 @@ iwl_mld_cleanup_link(struct iwl_mld *mld, struct iwl_mld_link *link)
/* Convert a percentage from [0,100] to [0,255] */
#define NORMALIZE_PERCENT_TO_255(percentage) ((percentage) * 256 / 100)
int iwl_mld_allocate_link_fw_id(struct iwl_mld *mld, u8 *fw_id,
struct ieee80211_bss_conf *mac80211_ptr);
int iwl_mld_send_link_cmd(struct iwl_mld *mld,
struct iwl_link_config_cmd *cmd,
enum iwl_ctxt_action action);
int iwl_mld_add_link(struct iwl_mld *mld,
struct ieee80211_bss_conf *bss_conf);
void iwl_mld_remove_link(struct iwl_mld *mld,
@ -123,10 +145,17 @@ unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld,
unsigned int iwl_mld_get_chan_load(struct iwl_mld *mld,
struct ieee80211_bss_conf *link_conf);
int iwl_mld_get_chan_load_from_element(struct iwl_mld *mld,
struct ieee80211_bss_conf *link_conf);
int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld,
struct ieee80211_bss_conf *link_conf,
bool expect_active_link);
bool iwl_mld_chan_load_requires_scan(struct iwl_mld *mld,
struct ieee80211_bss_conf *link_conf,
u32 new_chan_load);
void iwl_mld_handle_beacon_filter_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include <net/mac80211.h>
@ -62,17 +62,16 @@ static const struct ieee80211_iface_limit iwl_mld_limits_ap[] = {
static const struct ieee80211_iface_limit iwl_mld_limits_nan[] = {
{
.max = 2,
.max = 1,
.types = BIT(NL80211_IFTYPE_STATION),
},
{
.max = 1,
.types = BIT(NL80211_IFTYPE_NAN),
},
/* Removed when two channels are permitted */
{
.max = 1,
.types = BIT(NL80211_IFTYPE_AP),
.max = 2,
.types = BIT(NL80211_IFTYPE_NAN_DATA),
},
};
@ -90,19 +89,13 @@ iwl_mld_iface_combinations[] = {
.limits = iwl_mld_limits_ap,
.n_limits = ARRAY_SIZE(iwl_mld_limits_ap),
},
/* NAN combinations follow, these exclude P2P */
/* NAN combination follow, this excludes P2P and AP */
{
.num_different_channels = 2,
.max_interfaces = 3,
.limits = iwl_mld_limits_nan,
.n_limits = ARRAY_SIZE(iwl_mld_limits_nan) - 1,
},
{
.num_different_channels = 1,
.num_different_channels = 3,
.max_interfaces = 4,
.limits = iwl_mld_limits_nan,
.n_limits = ARRAY_SIZE(iwl_mld_limits_nan),
}
},
};
static const u8 ext_capa_base[IWL_MLD_STA_EXT_CAPA_SIZE] = {
@ -272,6 +265,40 @@ static void iwl_mac_hw_set_flags(struct iwl_mld *mld)
ieee80211_hw_set(hw, TDLS_WIDER_BW);
}
static void iwl_mld_hw_set_nan(struct iwl_mld *mld)
{
struct ieee80211_hw *hw = mld->hw;
hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_NAN);
hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_NAN_DATA);
hw->wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ);
if (mld->nvm_data->bands[NL80211_BAND_5GHZ].n_channels)
hw->wiphy->nan_supported_bands |=
BIT(NL80211_BAND_5GHZ);
hw->wiphy->nan_capa.flags = WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC |
WIPHY_NAN_FLAGS_USERSPACE_DE;
hw->wiphy->nan_capa.op_mode = NAN_OP_MODE_PHY_MODE_VHT |
NAN_OP_MODE_PHY_MODE_HE |
NAN_OP_MODE_160MHZ;
hw->wiphy->nan_capa.n_antennas =
(hweight32(hw->wiphy->available_antennas_tx) &
NAN_DEV_CAPA_NUM_TX_ANT_MASK) |
((hweight32(hw->wiphy->available_antennas_rx) <<
NAN_DEV_CAPA_NUM_RX_ANT_POS) &
NAN_DEV_CAPA_NUM_RX_ANT_MASK);
/* Maximal channel switch time is 4 msec */
hw->wiphy->nan_capa.max_channel_switch_time = 4 * USEC_PER_MSEC;
hw->wiphy->nan_capa.phy.ht = mld->nvm_data->nan_phy_capa.ht;
hw->wiphy->nan_capa.phy.vht = mld->nvm_data->nan_phy_capa.vht;
hw->wiphy->nan_capa.phy.he = mld->nvm_data->nan_phy_capa.he;
}
static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld)
{
struct ieee80211_hw *hw = mld->hw;
@ -334,37 +361,16 @@ static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld)
wiphy->hw_timestamp_max_peers = 1;
wiphy->iface_combinations = iwl_mld_iface_combinations;
if (iwl_mld_nan_supported(mld)) {
hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_NAN);
hw->wiphy->iface_combinations = iwl_mld_iface_combinations;
hw->wiphy->n_iface_combinations =
ARRAY_SIZE(iwl_mld_iface_combinations);
hw->wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ);
if (mld->nvm_data->bands[NL80211_BAND_5GHZ].n_channels)
hw->wiphy->nan_supported_bands |=
BIT(NL80211_BAND_5GHZ);
hw->wiphy->nan_capa.flags = WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC |
WIPHY_NAN_FLAGS_USERSPACE_DE;
hw->wiphy->nan_capa.op_mode = NAN_OP_MODE_PHY_MODE_MASK |
NAN_OP_MODE_80P80MHZ |
NAN_OP_MODE_160MHZ;
/* Support 2 antenna's for Tx and Rx */
hw->wiphy->nan_capa.n_antennas = 0x22;
/* Maximal channel switch time is 4 msec */
hw->wiphy->nan_capa.max_channel_switch_time = 4;
hw->wiphy->nan_capa.dev_capabilities =
NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
NAN_DEV_CAPA_NDPE_SUPPORTED;
} else {
wiphy->iface_combinations = iwl_mld_iface_combinations;
/* Do not include NAN combinations */
wiphy->n_iface_combinations =
ARRAY_SIZE(iwl_mld_iface_combinations) - 2;
ARRAY_SIZE(iwl_mld_iface_combinations);
iwl_mld_hw_set_nan(mld);
} else {
/* Do not include NAN combination */
wiphy->n_iface_combinations =
ARRAY_SIZE(iwl_mld_iface_combinations) - 1;
}
wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_VHT_IBSS);
@ -414,6 +420,8 @@ static void iwl_mac_hw_set_wiphy(struct iwl_mld *mld)
mld->ext_capab[0].eml_capabilities = IWL_MLD_EMLSR_CAPA;
mld->ext_capab[0].mld_capa_and_ops = IWL_MLD_CAPA_OPS;
mld->ext_capab[0].ext_mld_capa_and_ops =
IEEE80211_UHR_ML_EXT_MLD_CAPA_ML_PM;
}
@ -449,22 +457,6 @@ static void iwl_mac_hw_set_misc(struct iwl_mld *mld)
static int iwl_mld_hw_verify_preconditions(struct iwl_mld *mld)
{
int ratecheck;
/* check for rates version 3 */
ratecheck =
(iwl_fw_lookup_cmd_ver(mld->fw, TX_CMD, 0) >= 11) +
(iwl_fw_lookup_notif_ver(mld->fw, DATA_PATH_GROUP,
TLC_MNG_UPDATE_NOTIF, 0) >= 4) +
(iwl_fw_lookup_notif_ver(mld->fw, LEGACY_GROUP,
REPLY_RX_MPDU_CMD, 0) >= 6) +
(iwl_fw_lookup_notif_ver(mld->fw, LONG_GROUP, TX_CMD, 0) >= 9);
if (ratecheck != 0 && ratecheck != 4) {
IWL_ERR(mld, "Firmware has inconsistent rates\n");
return -EINVAL;
}
/* 11ax is expected to be enabled for all supported devices */
if (WARN_ON(!mld->nvm_data->sku_cap_11ax_enable))
return -EINVAL;
@ -673,6 +665,28 @@ int iwl_mld_mac80211_add_interface(struct ieee80211_hw *hw,
if (ret)
return ret;
if (vif->type == NL80211_IFTYPE_NAN_DATA) {
if (WARN_ON(!mld->nan_device_vif)) {
ret = -EINVAL;
goto err;
}
if (iwl_mld_nan_use_nan_stations(mld)) {
struct iwl_mld_vif *mld_vif =
iwl_mld_vif_from_mac80211(vif);
struct iwl_mld_int_sta *sta =
&mld_vif->nan.mcast_data_sta;
ret = iwl_mld_add_nan_mcast_data_sta(mld,
vif->addr,
sta);
if (ret)
goto err;
}
return 0;
}
/*
* Add the default link, but not if this is an MLD vif as that implies
* the HW is restarting and it will be configured by change_vif_links.
@ -739,10 +753,17 @@ void iwl_mld_mac80211_remove_interface(struct ieee80211_hw *hw,
if (vif->type == NL80211_IFTYPE_P2P_DEVICE)
mld->p2p_device_vif = NULL;
if (vif->type == NL80211_IFTYPE_NAN)
if (vif->type == NL80211_IFTYPE_NAN) {
mld->nan_device_vif = NULL;
else
} else if (vif->type != NL80211_IFTYPE_NAN_DATA) {
iwl_mld_remove_link(mld, &vif->bss_conf);
} else if (iwl_mld_nan_use_nan_stations(mld)) {
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct iwl_mld_int_sta *sta = &mld_vif->nan.mcast_data_sta;
if (sta->sta_id != IWL_INVALID_STA)
iwl_mld_remove_nan_mcast_data_sta(mld, sta);
}
#ifdef CONFIG_IWLWIFI_DEBUGFS
debugfs_remove(iwl_mld_vif_from_mac80211(vif)->dbgfs_slink);
@ -988,6 +1009,30 @@ void iwl_mld_remove_chanctx(struct ieee80211_hw *hw,
mld->used_phy_ids &= ~BIT(phy->fw_id);
}
static void
iwl_mld_update_link_npca_puncturing(struct ieee80211_hw *hw,
struct ieee80211_chanctx_conf *ctx)
{
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
struct ieee80211_vif *vif;
for_each_active_interface(vif, hw) {
struct ieee80211_bss_conf *link;
int link_id;
for_each_vif_active_link(vif, link, link_id) {
if (rcu_access_pointer(link->chanctx_conf) != ctx)
continue;
if (!link->npca.enabled)
continue;
iwl_mld_change_link_in_fw(mld, link,
LINK_CONTEXT_MODIFY_UHR_PARAMS);
}
}
}
static
void iwl_mld_change_chanctx(struct ieee80211_hw *hw,
struct ieee80211_chanctx_conf *ctx, u32 changed)
@ -1003,9 +1048,19 @@ void iwl_mld_change_chanctx(struct ieee80211_hw *hw,
IEEE80211_CHANCTX_CHANGE_CHANNEL)))
return;
/* NPCA puncturing is in link API for FW */
if (changed & IEEE80211_CHANCTX_CHANGE_NPCA_PUNCT) {
iwl_mld_update_link_npca_puncturing(hw, ctx);
changed &= ~IEEE80211_CHANCTX_CHANGE_NPCA_PUNCT;
}
/* Check if a FW update is required */
if (changed & IEEE80211_CHANCTX_CHANGE_AP)
if (!changed)
return;
if (changed & IEEE80211_CHANCTX_CHANGE_AP ||
changed & IEEE80211_CHANCTX_CHANGE_NPCA)
goto update;
if (chandef->chan == phy->chandef.chan &&
@ -1231,33 +1286,6 @@ int iwl_mld_mac80211_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx,
return 0;
}
static void
iwl_mld_link_info_changed_ap_ibss(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link,
u64 changes)
{
u32 link_changes = 0;
if (changes & BSS_CHANGED_ERP_SLOT)
link_changes |= LINK_CONTEXT_MODIFY_RATES_INFO;
if (changes & (BSS_CHANGED_ERP_CTS_PROT | BSS_CHANGED_HT))
link_changes |= LINK_CONTEXT_MODIFY_PROTECT_FLAGS;
if (changes & (BSS_CHANGED_QOS | BSS_CHANGED_BANDWIDTH))
link_changes |= LINK_CONTEXT_MODIFY_QOS_PARAMS;
if (changes & BSS_CHANGED_HE_BSS_COLOR)
link_changes |= LINK_CONTEXT_MODIFY_HE_PARAMS;
if (link_changes)
iwl_mld_change_link_in_fw(mld, link, link_changes);
if (changes & BSS_CHANGED_BEACON)
iwl_mld_update_beacon_template(mld, vif, link);
}
static
u32 iwl_mld_link_changed_mapping(struct iwl_mld *mld,
struct ieee80211_vif *vif,
@ -1289,6 +1317,9 @@ u32 iwl_mld_link_changed_mapping(struct iwl_mld *mld,
link_changes |= LINK_CONTEXT_MODIFY_HE_PARAMS;
}
if (changes & BSS_CHANGED_NPCA)
link_changes |= LINK_CONTEXT_MODIFY_UHR_PARAMS;
return link_changes;
}
@ -1367,6 +1398,10 @@ iwl_mld_mac80211_link_info_changed(struct ieee80211_hw *hw,
if (changes & BSS_CHANGED_MU_GROUPS)
iwl_mld_update_mu_groups(mld, link_conf);
break;
case NL80211_IFTYPE_NAN:
case NL80211_IFTYPE_NAN_DATA:
/* NAN has no links */
break;
default:
/* shouldn't happen */
WARN_ON_ONCE(1);
@ -1382,28 +1417,6 @@ iwl_mld_mac80211_link_info_changed(struct ieee80211_hw *hw,
iwl_mld_set_tx_power(mld, link_conf, link_conf->txpower);
}
static void
iwl_mld_smps_workaround(struct iwl_mld *mld, struct ieee80211_vif *vif, bool enable)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
bool workaround_required =
iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0) < 2;
if (!workaround_required)
return;
/* Send the device-level power commands since the
* firmware checks the POWER_TABLE_CMD's POWER_SAVE_EN bit to
* determine SMPS mode.
*/
if (mld_vif->ps_disabled == !enable)
return;
mld_vif->ps_disabled = !enable;
iwl_mld_update_device_power(mld, false);
}
static
void iwl_mld_mac80211_vif_cfg_changed(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
@ -1414,6 +1427,11 @@ void iwl_mld_mac80211_vif_cfg_changed(struct ieee80211_hw *hw,
lockdep_assert_wiphy(mld->wiphy);
if (vif->type == NL80211_IFTYPE_NAN) {
iwl_mld_nan_vif_cfg_changed(mld, vif, changes);
return;
}
if (vif->type != NL80211_IFTYPE_STATION)
return;
@ -1436,10 +1454,8 @@ void iwl_mld_mac80211_vif_cfg_changed(struct ieee80211_hw *hw,
}
}
if (changes & BSS_CHANGED_PS) {
iwl_mld_smps_workaround(mld, vif, vif->cfg.ps);
if (changes & BSS_CHANGED_PS)
iwl_mld_update_mac_power(mld, vif, false);
}
/* TODO: task=MLO BSS_CHANGED_MLD_VALID_LINKS/CHANGED_MLD_TTLM */
}
@ -1609,7 +1625,7 @@ iwl_mld_mac80211_conf_tx(struct ieee80211_hw *hw,
lockdep_assert_wiphy(mld->wiphy);
if (vif->type == NL80211_IFTYPE_NAN)
if (vif->type == NL80211_IFTYPE_NAN || vif->type == NL80211_IFTYPE_NAN_DATA)
return 0;
link = iwl_mld_link_dereference_check(mld_vif, link_id);
@ -1825,7 +1841,7 @@ static int iwl_mld_move_sta_state_up(struct iwl_mld *mld,
new_state == IEEE80211_STA_AUTHORIZED) {
ret = 0;
if (!sta->tdls) {
if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls) {
mld_vif->authorized = true;
/* Ensure any block due to a non-BSS link is synced */
@ -1846,7 +1862,6 @@ static int iwl_mld_move_sta_state_up(struct iwl_mld *mld,
FW_CTXT_ACTION_MODIFY);
if (ret)
return ret;
iwl_mld_smps_workaround(mld, vif, vif->cfg.ps);
}
/* MFP is set by default before the station is authorized.
@ -1891,7 +1906,6 @@ static int iwl_mld_move_sta_state_down(struct iwl_mld *mld,
&mld_vif->mlo_scan_start_wk);
iwl_mld_reset_cca_40mhz_workaround(mld, vif);
iwl_mld_smps_workaround(mld, vif, true);
}
/* once we move into assoc state, need to update the FW to
@ -2236,7 +2250,6 @@ static int iwl_mld_set_key_add(struct iwl_mld *mld,
ret = iwl_mld_add_key(mld, vif, sta, key);
if (ret) {
IWL_WARN(mld, "set key failed (%d)\n", ret);
if (ptk_pn) {
RCU_INIT_POINTER(mld_sta->ptk_pn[keyidx], NULL);
kfree(ptk_pn);
@ -2278,7 +2291,9 @@ static void iwl_mld_set_key_remove(struct iwl_mld *mld,
}
/* if this key was stored to be added later to the FW - free it here */
if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE) &&
(vif->type == NL80211_IFTYPE_AP ||
vif->type == NL80211_IFTYPE_ADHOC))
iwl_mld_free_ap_early_key(mld, key, mld_vif);
/* We already removed it */
@ -2296,9 +2311,20 @@ static int iwl_mld_mac80211_set_key(struct ieee80211_hw *hw,
struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
int ret;
/*
* FW always needs the AP STA for client mode.
* Note that during removal this could already
* be NULL (mac80211 removes keys after STAs)
* but then we'll already have removed the key
* and set hw_key_idx = STA_KEY_IDX_INVALID.
*/
if (!sta && vif->type == NL80211_IFTYPE_STATION)
sta = mld_vif->ap_sta;
switch (cmd) {
case SET_KEY:
ret = iwl_mld_set_key_add(mld, vif, sta, key);
@ -2559,15 +2585,108 @@ iwl_mld_mac80211_mgd_protect_tdls_discover(struct ieee80211_hw *hw,
ret);
}
static int iwl_mld_count_free_link_ids(struct iwl_mld *mld)
{
int free_count = 0;
for (int i = 0; i < mld->fw->ucode_capa.num_links; i++) {
if (!rcu_access_pointer(mld->fw_id_to_bss_conf[i]))
free_count++;
}
return free_count;
}
static bool
iwl_mld_chanctx_used_by_other_vif(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_chanctx_conf *chanctx_conf)
{
struct ieee80211_bss_conf *iter_link;
struct ieee80211_vif *iter_vif;
int link_id;
for_each_active_interface(iter_vif, hw) {
if (vif == iter_vif)
continue;
/* NAN doesn't have active links, so we don't count NAN users */
for_each_vif_active_link(iter_vif, iter_link, link_id) {
if (rcu_access_pointer(iter_link->chanctx_conf) ==
chanctx_conf)
return true;
}
}
return false;
}
static bool iwl_mld_can_activate_links(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
u16 desired_links)
{
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
int n_links = hweight16(desired_links);
int n_add = hweight16(desired_links & ~vif->active_links);
unsigned long to_deactivate = vif->active_links & ~desired_links;
int free_link_ids;
int i;
/* Check if HW supports the wanted number of links */
return n_links <= iwl_mld_max_active_links(mld, vif);
if (n_links > iwl_mld_max_active_links(mld, vif))
return false;
/*
* During link switch, mac80211 first adds the new links, then removes
* the old ones. This means we temporarily need extra link objects
* during the transition. Check if we have enough free link IDs.
*/
free_link_ids = iwl_mld_count_free_link_ids(mld);
if (free_link_ids >= n_add)
return true;
if (!mld->nan_device_vif)
return false;
/*
* Not enough free link IDs. First try to evacuate NAN from the
* channel context of a link that is going to be deactivated.
*/
for_each_set_bit(i, &to_deactivate, IEEE80211_MLD_MAX_NUM_LINKS) {
struct ieee80211_bss_conf *link_conf;
struct ieee80211_chanctx_conf *chanctx_conf;
link_conf = link_conf_dereference_protected(vif, i);
if (!link_conf)
continue;
chanctx_conf = wiphy_dereference(mld->wiphy, link_conf->chanctx_conf);
if (!chanctx_conf)
continue;
if (iwl_mld_chanctx_used_by_other_vif(hw, vif, chanctx_conf))
continue;
if (ieee80211_nan_try_evacuate(hw, chanctx_conf)) {
free_link_ids = iwl_mld_count_free_link_ids(mld);
/*
* Evacuation of one channel should do the job. If not,
* something bad is happening. Don't try to evacuate more
*/
return free_link_ids >= n_add;
}
}
/* Couldn't find/evacuate any channel going to go unused, try any */
if (ieee80211_nan_try_evacuate(hw, NULL)) {
free_link_ids = iwl_mld_count_free_link_ids(mld);
if (free_link_ids >= n_add)
return true;
}
return false;
}
static int
@ -2847,4 +2966,5 @@ const struct ieee80211_ops iwl_mld_hw_ops = {
.start_nan = iwl_mld_start_nan,
.stop_nan = iwl_mld_stop_nan,
.nan_change_conf = iwl_mld_nan_change_config,
.nan_peer_sched_changed = iwl_mld_mac802111_nan_peer_sched_changed,
};

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include <net/cfg80211.h>
@ -129,7 +129,7 @@ iwl_mld_get_regdomain(struct iwl_mld *mld,
mld->mcc_src = resp->source_id;
/* FM is the earliest supported and later always do puncturing */
/* FM follows BIOS/MCC policy, WH disallows puncturing only in US/CA. */
if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_FM) {
if (!iwl_puncturing_is_allowed_in_bios(mld->bios_enable_puncturing,
le16_to_cpu(resp->mcc)))
@ -137,6 +137,15 @@ iwl_mld_get_regdomain(struct iwl_mld *mld,
else
__clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING,
mld->hw->flags);
} else if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) ==
IWL_CFG_RF_TYPE_WH) {
u16 mcc = le16_to_cpu(resp->mcc);
if (mcc == IWL_MCC_US || mcc == IWL_MCC_CANADA)
ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING);
else
__clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING,
mld->hw->flags);
}
out:

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include <linux/rtnetlink.h>
#include <net/mac80211.h>
@ -162,7 +162,6 @@ static const struct iwl_hcmd_names iwl_mld_legacy_names[] = {
HCMD_NAME(PHY_CONFIGURATION_CMD),
HCMD_NAME(SCAN_OFFLOAD_UPDATE_PROFILES_CMD),
HCMD_NAME(POWER_TABLE_CMD),
HCMD_NAME(PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION),
HCMD_NAME(BEACON_NOTIFICATION),
HCMD_NAME(BEACON_TEMPLATE_CMD),
HCMD_NAME(TX_ANT_CONFIGURATION_CMD),
@ -236,6 +235,10 @@ static const struct iwl_hcmd_names iwl_mld_mac_conf_names[] = {
HCMD_NAME(STA_REMOVE_CMD),
HCMD_NAME(ROC_CMD),
HCMD_NAME(NAN_CFG_CMD),
HCMD_NAME(NAN_SCHEDULE_CMD),
HCMD_NAME(NAN_PEER_CMD),
HCMD_NAME(NAN_ULW_ATTR_NOTIF),
HCMD_NAME(NAN_SCHED_UPDATE_COMPLETED_NOTIF),
HCMD_NAME(NAN_DW_END_NOTIF),
HCMD_NAME(NAN_JOINED_CLUSTER_NOTIF),
HCMD_NAME(MISSED_BEACONS_NOTIF),
@ -259,6 +262,7 @@ static const struct iwl_hcmd_names iwl_mld_data_path_names[] = {
HCMD_NAME(RX_BAID_ALLOCATION_CONFIG_CMD),
HCMD_NAME(SCD_QUEUE_CONFIG_CMD),
HCMD_NAME(SEC_KEY_CMD),
HCMD_NAME(RSC_NOTIF),
HCMD_NAME(ESR_MODE_NOTIF),
HCMD_NAME(MONITOR_NOTIF),
HCMD_NAME(TLC_MNG_UPDATE_NOTIF),
@ -411,9 +415,6 @@ iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg,
iwl_construct_mld(mld, trans, cfg, fw, hw, dbgfs_dir);
/* we'll verify later it matches between commands */
mld->fw_rates_ver_3 = iwl_fw_lookup_cmd_ver(mld->fw, TX_CMD, 0) >= 11;
iwl_mld_construct_fw_runtime(mld, trans, fw, dbgfs_dir);
iwl_mld_get_bios_tables(mld);
@ -675,6 +676,15 @@ iwl_mld_nic_error(struct iwl_op_mode *op_mode,
if (type != IWL_ERR_TYPE_RESET_HS_TIMEOUT &&
mld->fw_status.running)
mld->fw_status.in_hw_restart = true;
/* FW is dead. We don't want to process its notifications.
* Right, we cancel them also in iwl_mld_stop_fw, but
* iwl_mld_async_handlers_wk might be executed before
* ieee80211_restart_work.
* In addition, in case of an error during recovery,
* iwl_mld_stop_fw might be too late.
*/
iwl_mld_cancel_async_notifications(mld);
}
static void iwl_mld_dump_error(struct iwl_op_mode *op_mode,

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#ifndef __iwl_mld_h__
#define __iwl_mld_h__
@ -189,7 +189,6 @@
* TX rate_n_flags for non-STA mgmt frames (toggles on every TX failure).
* @set_tx_ant: stores the last TX antenna bitmask set by user space (if any)
* @set_rx_ant: stores the last RX antenna bitmask set by user space (if any)
* @fw_rates_ver_3: FW rates are in version 3
* @low_latency: low-latency manager.
* @tzone: thermal zone device's data
* @cooling_dev: cooling device's related data
@ -299,8 +298,6 @@ struct iwl_mld {
u8 set_tx_ant;
u8 set_rx_ant;
bool fw_rates_ver_3;
struct iwl_mld_low_latency low_latency;
bool ibss_manager;
@ -555,15 +552,24 @@ iwl_mld_allocate_##_type##_fw_id(struct iwl_mld *mld, \
return -ENOSPC; \
}
#define IWL_MLD_ALLOC_FN_STATIC(_type, _mac80211_type) \
static IWL_MLD_ALLOC_FN(_type, _mac80211_type)
static inline struct ieee80211_bss_conf *
iwl_mld_fw_id_to_link_conf(struct iwl_mld *mld, u8 fw_link_id)
{
struct ieee80211_bss_conf *link;
if (IWL_FW_CHECK(mld, fw_link_id >= mld->fw->ucode_capa.num_links,
"Invalid fw_link_id: %d\n", fw_link_id))
return NULL;
return wiphy_dereference(mld->wiphy,
link = wiphy_dereference(mld->wiphy,
mld->fw_id_to_bss_conf[fw_link_id]);
if (IS_ERR(link))
return NULL;
return link;
}
#define MSEC_TO_TU(_msec) ((_msec) * 1000 / 1024)

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include "mlo.h"
#include "phy.h"
@ -1081,8 +1081,13 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac,
container_of((const void *)phy, struct ieee80211_chanctx_conf,
drv_priv);
struct iwl_mld *mld = iwl_mld_vif_from_mac80211(vif)->mld;
struct ieee80211_bss_conf *prim_link;
u32 new_chan_load = phy->avg_channel_load_not_by_us;
struct ieee80211_bss_conf *prim_link, *link_conf;
unsigned int prim_link_id;
int link_id;
if (!ieee80211_vif_is_mld(vif) || hweight16(vif->valid_links) <= 1)
return;
prim_link_id = iwl_mld_get_primary_link(vif);
prim_link = link_conf_dereference_protected(vif, prim_link_id);
@ -1090,6 +1095,32 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac,
if (WARN_ON(!prim_link))
return;
/* Evaluate MLO Internal Scan for high chan load beyond thresholds */
for_each_vif_active_link(vif, link_conf, link_id) {
if (rcu_access_pointer(link_conf->chanctx_conf) != chanctx)
continue;
/* No QBSS IE - links will be selected based on default channel
* load values, so the same link will be selected again.
* No point in scan.
*/
if (iwl_mld_get_chan_load_from_element(mld, link_conf) < 0)
continue;
if (iwl_mld_chan_load_requires_scan(mld,
link_conf,
new_chan_load)) {
/* When EMLSR is active, only trigger scan based on
* primary link
*/
if (iwl_mld_emlsr_active(vif) && link_conf != prim_link)
continue;
iwl_mld_int_mlo_scan(mld, vif);
return;
}
}
if (chanctx != rcu_access_pointer(prim_link->chanctx_conf))
return;
@ -1107,7 +1138,6 @@ static void iwl_mld_chan_load_update_iter(void *_data, u8 *mac,
prim_link_id);
} else {
u32 old_chan_load = data->prev_chan_load_not_by_us;
u32 new_chan_load = phy->avg_channel_load_not_by_us;
u32 min_thresh = iwl_mld_get_min_chan_load_thresh(chanctx);
#define THRESHOLD_CROSSED(threshold) \

View File

@ -5,8 +5,12 @@
#include "mld.h"
#include "iface.h"
#include "link.h"
#include "mlo.h"
#include "tlc.h"
#include "fw/api/mac-cfg.h"
#include "fw/api/mac.h"
#include "fw/api/rs.h"
#define IWL_NAN_DISOVERY_BEACON_INTERNVAL_TU 512
#define IWL_NAN_RSSI_CLOSE 55
@ -14,8 +18,16 @@
bool iwl_mld_nan_supported(struct iwl_mld *mld)
{
return fw_has_capa(&mld->fw->ucode_capa,
IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT);
const struct iwl_fw *fw = mld->fw;
if (fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT) &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, NAN_SCHEDULE_CMD), 0) >= 1 &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, NAN_PEER_CMD), 0) >= 1 &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, STA_CONFIG_CMD), 0) >= 3 &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, MAC_CONFIG_CMD), 0) >= 4 &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(DATA_PATH_GROUP, TLC_MNG_CONFIG_CMD), 0) >= 6)
return true;
return false;
}
static int iwl_mld_nan_send_config_cmd(struct iwl_mld *mld,
@ -38,6 +50,98 @@ static int iwl_mld_nan_send_config_cmd(struct iwl_mld *mld,
return iwl_mld_send_cmd(mld, &hcmd);
}
bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld)
{
/*
* If the FW supports version 1 of the NAN config command, it means that
* it needs to receive the station ID of the auxiliary station in the
* NAN configuration command. Otherwise, use the NAN dedicated station
* types.
*/
return iwl_fw_lookup_cmd_ver(mld->fw,
WIDE_ID(MAC_CONF_GROUP,
NAN_CFG_CMD), 1) != 1;
}
static const struct iwl_mld_int_sta *
iwl_mld_nan_get_mgmt_sta(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
const struct iwl_mld_int_sta *sta;
if (iwl_mld_nan_use_nan_stations(mld))
sta = &mld_vif->nan.mgmt_sta;
else
sta = &mld_vif->aux_sta;
if (WARN_ON(sta->sta_id == IWL_INVALID_STA))
return NULL;
return sta;
}
int iwl_mld_nan_get_mgmt_queue(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
const struct iwl_mld_int_sta *sta = iwl_mld_nan_get_mgmt_sta(mld, vif);
if (!sta)
return IWL_MLD_INVALID_QUEUE;
return sta->queue_id;
}
static void iwl_mld_nan_flush(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
const struct iwl_mld_int_sta *sta = iwl_mld_nan_get_mgmt_sta(mld, vif);
if (!sta)
return;
if (WARN_ON(sta->queue_id == IWL_MLD_INVALID_QUEUE))
return;
IWL_DEBUG_INFO(mld, "NAN: flush queues for sta=%u\n",
sta->sta_id);
iwl_mld_flush_link_sta_txqs(mld, sta->sta_id);
}
static void iwl_mld_nan_remove_stations(struct iwl_mld *mld,
struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
iwl_mld_nan_flush(mld, vif);
if (!iwl_mld_nan_use_nan_stations(mld)) {
iwl_mld_remove_aux_sta(mld, vif);
return;
}
iwl_mld_remove_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta);
iwl_mld_remove_nan_mgmt_sta(mld, &mld_vif->nan.mgmt_sta);
}
static int iwl_mld_nan_add_stations(struct iwl_mld *mld,
struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
int ret;
if (!iwl_mld_nan_use_nan_stations(mld))
return iwl_mld_add_aux_sta(mld, &mld_vif->aux_sta);
ret = iwl_mld_add_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta);
if (ret)
return ret;
ret = iwl_mld_add_nan_mgmt_sta(mld, &mld_vif->nan.mgmt_sta);
if (ret)
iwl_mld_remove_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta);
return ret;
}
static int iwl_mld_nan_config(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct cfg80211_nan_conf *conf,
@ -114,7 +218,12 @@ static int iwl_mld_nan_config(struct iwl_mld *mld,
conf->vendor_elems_len);
}
cmd.sta_id = mld_vif->aux_sta.sta_id;
/* FW needs to know about the station ID only with version 1 of the
* NAN configuration command
*/
if (!iwl_mld_nan_use_nan_stations(mld))
cmd.sta_id = mld_vif->aux_sta.sta_id;
return iwl_mld_nan_send_config_cmd(mld, &cmd, data,
conf->extra_nan_attrs_len +
conf->vendor_elems_len);
@ -124,8 +233,6 @@ int iwl_mld_start_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct cfg80211_nan_conf *conf)
{
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct iwl_mld_int_sta *aux_sta = &mld_vif->aux_sta;
int ret;
IWL_DEBUG_MAC80211(mld, "NAN: start: bands=0x%x\n", conf->bands);
@ -134,19 +241,20 @@ int iwl_mld_start_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
if (ret)
return ret;
ret = iwl_mld_add_aux_sta(mld, aux_sta);
ret = iwl_mld_nan_add_stations(mld, vif);
if (ret)
goto unblock_emlsr;
ret = iwl_mld_nan_config(mld, vif, conf, FW_CTXT_ACTION_ADD);
if (ret) {
IWL_ERR(mld, "Failed to start NAN. ret=%d\n", ret);
goto remove_aux;
goto remove_stas;
}
return 0;
remove_aux:
iwl_mld_remove_aux_sta(mld, vif);
remove_stas:
iwl_mld_nan_remove_stations(mld, vif);
unblock_emlsr:
iwl_mld_update_emlsr_block(mld, false, IWL_MLD_EMLSR_BLOCKED_NAN);
@ -174,7 +282,6 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw,
struct ieee80211_vif *vif)
{
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct iwl_nan_config_cmd cmd = {
.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE),
};
@ -191,8 +298,7 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw,
/* assume that higher layer guarantees that no additional frames are
* added before calling this callback
*/
iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id);
iwl_mld_remove_aux_sta(mld, vif);
iwl_mld_nan_remove_stations(mld, vif);
/* cancel based on object type being NAN, as the NAN objects do
* not have a unique identifier associated with them
@ -247,6 +353,39 @@ bool iwl_mld_cancel_nan_dw_end_notif(struct iwl_mld *mld,
return true;
}
bool iwl_mld_cancel_nan_ulw_attr_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id)
{
return true;
}
void iwl_mld_handle_nan_ulw_attr_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
struct iwl_nan_ulw_attr_notif *notif = (void *)pkt->data;
struct wireless_dev *wdev;
IWL_DEBUG_INFO(mld, "NAN: ULW attr update: len=%u\n", notif->attr_len);
if (IWL_FW_CHECK(mld, !mld->nan_device_vif,
"NAN: ULW attr update without NAN vif\n"))
return;
if (IWL_FW_CHECK(mld, !ieee80211_vif_nan_started(mld->nan_device_vif),
"NAN: ULW attr update without NAN started\n"))
return;
if (IWL_FW_CHECK(mld,
notif->attr_len > IWL_NAN_MAX_ENDLESS_ULW_ATTR_LEN,
"NAN: ULW attr update invalid len %u\n",
notif->attr_len))
return;
wdev = ieee80211_vif_to_wdev(mld->nan_device_vif);
cfg80211_nan_ulw_update(wdev, notif->attr, notif->attr_len, GFP_KERNEL);
}
void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
@ -257,7 +396,7 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld,
struct wireless_dev *wdev;
struct ieee80211_channel *chan;
IWL_INFO(mld, "NAN: DW end: band=%u\n", notif->band);
IWL_DEBUG_INFO(mld, "NAN: DW end: band=%u\n", notif->band);
if (IWL_FW_CHECK(mld, !mld_vif, "NAN: DW end without mld_vif\n"))
return;
@ -266,13 +405,7 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld,
"NAN: DW end without NAN started\n"))
return;
if (WARN_ON(mld_vif->aux_sta.sta_id == IWL_INVALID_STA))
return;
IWL_DEBUG_INFO(mld, "NAN: flush queues for aux sta=%u\n",
mld_vif->aux_sta.sta_id);
iwl_mld_flush_link_sta_txqs(mld, mld_vif->aux_sta.sta_id);
iwl_mld_nan_flush(mld, mld->nan_device_vif);
/* TODO: currently the notification specified the band on which the DW
* ended. Need to change that to the actual channel on which the next DW
@ -293,6 +426,581 @@ void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld,
return;
}
if (WARN_ON_ONCE(!chan))
return;
wdev = ieee80211_vif_to_wdev(mld->nan_device_vif);
cfg80211_next_nan_dw_notif(wdev, chan, GFP_KERNEL);
}
static void iwl_mld_nan_fill_rates(struct iwl_link_config_cmd *cmd)
{
u32 ofdm = 0;
/* All OFDM rates - NAN uses OFDM only, no CCK */
ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(9) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(18) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(36) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(48) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(54) >> IWL_FIRST_OFDM_RATE;
cmd->ofdm_rates = cpu_to_le32(ofdm);
cmd->short_slot = cpu_to_le32(1);
}
static void iwl_mld_nan_fill_qos(struct iwl_ac_qos *ac, __le32 *qos_flags)
{
/* AC_BK: CWmin=15, CWmax=1023, AIFSN=7, TXOP=0 */
ac[AC_BK].cw_min = cpu_to_le16(15);
ac[AC_BK].cw_max = cpu_to_le16(1023);
ac[AC_BK].aifsn = 7;
ac[AC_BK].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_BK);
ac[AC_BK].edca_txop = 0;
/* AC_BE: CWmin=15, CWmax=1023, AIFSN=3, TXOP=0 */
ac[AC_BE].cw_min = cpu_to_le16(15);
ac[AC_BE].cw_max = cpu_to_le16(1023);
ac[AC_BE].aifsn = 3;
ac[AC_BE].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_BE);
ac[AC_BE].edca_txop = 0;
/* AC_VI: CWmin=7, CWmax=15, AIFSN=2, TXOP=3008us */
ac[AC_VI].cw_min = cpu_to_le16(7);
ac[AC_VI].cw_max = cpu_to_le16(15);
ac[AC_VI].aifsn = 2;
ac[AC_VI].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_VI);
ac[AC_VI].edca_txop = cpu_to_le16(3008);
/* AC_VO: CWmin=3, CWmax=7, AIFSN=2, TXOP=1504us */
ac[AC_VO].cw_min = cpu_to_le16(3);
ac[AC_VO].cw_max = cpu_to_le16(7);
ac[AC_VO].aifsn = 2;
ac[AC_VO].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_VO);
ac[AC_VO].edca_txop = cpu_to_le16(1504);
*qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA);
}
static void
iwl_mld_nan_link_prep_cmd(struct iwl_mld *mld,
struct iwl_mld_nan_link *nan_link,
struct iwl_link_config_cmd *cmd,
u32 modify_flags)
{
struct ieee80211_vif *vif = mld->nan_device_vif;
struct iwl_mld_vif *mld_vif;
if (WARN_ON_ONCE(!vif))
return;
mld_vif = iwl_mld_vif_from_mac80211(vif);
memset(cmd, 0, sizeof(*cmd));
if (!nan_link->chanctx) {
cmd->phy_id = cpu_to_le32(FW_CTXT_ID_INVALID);
} else {
struct iwl_mld_phy *mld_phy;
mld_phy = iwl_mld_phy_from_mac80211(nan_link->chanctx);
cmd->phy_id = cpu_to_le32(mld_phy->fw_id);
}
if (modify_flags & LINK_CONTEXT_MODIFY_RATES_INFO)
iwl_mld_nan_fill_rates(cmd);
if (modify_flags & LINK_CONTEXT_MODIFY_QOS_PARAMS)
iwl_mld_nan_fill_qos(cmd->ac, &cmd->qos_flags);
cmd->link_id = cpu_to_le32(nan_link->fw_id);
cmd->mac_id = cpu_to_le32(mld_vif->fw_id);
cmd->active = cpu_to_le32(nan_link->active);
ether_addr_copy(cmd->local_link_addr, vif->addr);
cmd->modify_mask = cpu_to_le32(modify_flags);
}
static struct iwl_mld_nan_link *
iwl_mld_nan_link_add(struct iwl_mld *mld,
struct iwl_mld_vif *mld_vif,
struct ieee80211_chanctx_conf *chanctx)
{
struct iwl_mld_nan_link *nan_link;
struct iwl_link_config_cmd cmd;
u8 fw_id;
int ret;
lockdep_assert_wiphy(mld->wiphy);
ret = iwl_mld_allocate_link_fw_id(mld, &fw_id, ERR_PTR(-ENODEV));
/*
* We should always have enough links. The schedule contains up to 3,
* and the BSS vif cannot do EMLSR - so can only have 1.
*/
if (WARN_ON(ret < 0))
return NULL;
nan_link = &mld_vif->nan.links[fw_id];
if (WARN_ON_ONCE(nan_link->fw_id != FW_CTXT_ID_INVALID))
goto err;
nan_link->fw_id = fw_id;
nan_link->chanctx = chanctx;
iwl_mld_nan_link_prep_cmd(mld, nan_link, &cmd,
LINK_CONTEXT_MODIFY_RATES_INFO |
LINK_CONTEXT_MODIFY_QOS_PARAMS);
ret = iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_ADD);
if (ret) {
nan_link->fw_id = FW_CTXT_ID_INVALID;
nan_link->chanctx = NULL;
goto err;
}
return nan_link;
err:
RCU_INIT_POINTER(mld->fw_id_to_bss_conf[fw_id], NULL);
return NULL;
}
static int iwl_mld_nan_link_set_active(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct iwl_mld_nan_link *nan_link,
bool active)
{
struct iwl_link_config_cmd cmd;
struct ieee80211_sta *sta;
int ret;
if (nan_link->active == active)
return 0;
if (active) {
for_each_station(sta, mld->hw) {
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI)
iwl_mld_config_tlc(mld, mld_sta->vif, sta);
}
}
nan_link->active = active;
iwl_mld_nan_link_prep_cmd(mld, nan_link, &cmd,
LINK_CONTEXT_MODIFY_ACTIVE);
ret = iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_MODIFY);
if (ret) {
nan_link->active = !nan_link->active;
return ret;
}
if (!active) {
nan_link->chanctx = NULL;
/* TODO: when FW is ready, Update phy in TLC to invalid after */
}
return 0;
}
static void iwl_mld_nan_link_remove(struct iwl_mld *mld,
struct iwl_mld_nan_link *nan_link,
u32 link_id)
{
struct iwl_link_config_cmd cmd = {
.link_id = cpu_to_le32(link_id),
.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID),
};
iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE);
RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link_id], NULL);
nan_link->fw_id = FW_CTXT_ID_INVALID;
nan_link->active = false;
nan_link->chanctx = NULL;
}
static bool iwl_mld_nan_have_links(struct iwl_mld_vif *mld_vif)
{
struct iwl_mld_nan_link *nan_link;
for_each_mld_nan_valid_link(mld_vif, nan_link)
return true;
return false;
}
static struct iwl_mld_nan_link *
iwl_mld_nan_find_link(struct iwl_mld_vif *mld_vif,
struct ieee80211_chanctx_conf *chanctx)
{
struct iwl_mld_nan_link *nan_link;
for_each_mld_nan_valid_link(mld_vif, nan_link) {
if (nan_link->chanctx == chanctx)
return nan_link;
}
return NULL;
}
static void iwl_mld_nan_set_mcast_data_links(struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
if (vif->type != NL80211_IFTYPE_NAN_DATA)
return;
/* Note that all errors are handled internally so nothing to do
* with the return value (used only to silence compilation warnings)
*/
iwl_mld_update_nan_mcast_data_sta(mld_vif->mld, vif->addr,
&mld_vif->nan.mcast_data_sta);
}
void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
struct ieee80211_vif *vif,
u64 changes)
{
struct iwl_nan_schedule_cmd cmd = {};
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
bool previously_empty_schedule = !iwl_mld_nan_have_links(mld_vif);
struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched;
struct iwl_mld_nan_link *links[ARRAY_SIZE(sched_cfg->channels)] = {};
struct ieee80211_nan_channel **slots = sched_cfg->schedule;
bool link_used[ARRAY_SIZE(mld_vif->nan.links)] = {};
struct iwl_mld_nan_link *nan_link;
unsigned long remove_link_ids = 0;
bool added_links = false;
bool empty_schedule = true;
int ret, i;
u16 cmd_size;
u32 cmd_id = WIDE_ID(MAC_CONF_GROUP, NAN_SCHEDULE_CMD);
u8 version = iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, 0);
if (!(changes & BSS_CHANGED_NAN_LOCAL_SCHED))
return;
switch (version) {
case 1:
if (sched_cfg->deferred) {
IWL_ERR(mld,
"NAN: deferred schedule not supported by FW\n");
return;
}
cmd_size = sizeof(struct iwl_nan_schedule_cmd_v1);
break;
case 2:
cmd_size = sizeof(struct iwl_nan_schedule_cmd);
if (sched_cfg->deferred)
cmd.deferred = 1;
if (sched_cfg->avail_blob_len &&
!WARN_ON(sched_cfg->avail_blob_len >
sizeof(cmd.avail_attr.attr))) {
cmd.avail_attr.attr_len = sched_cfg->avail_blob_len;
memcpy(cmd.avail_attr.attr, sched_cfg->avail_blob,
sched_cfg->avail_blob_len);
}
break;
default:
IWL_ERR(mld, "NAN: unsupported NAN schedule cmd version %d\n",
version);
return;
}
for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
if (!sched_cfg->channels[i].chanreq.oper.chan)
continue;
empty_schedule = false;
break;
}
/* add the MAC if needed (before adding links) */
if (!empty_schedule && previously_empty_schedule) {
WARN_ON(mld_vif->nan.mac_added);
ret = iwl_mld_add_nan_vif(mld, vif);
if (ret) {
IWL_ERR(mld, "NAN: failed to add MAC (%d)\n", ret);
return;
}
}
if (!mld_vif->nan.mac_added) {
/* nothing to do */
return;
}
/* this currently just uses the same index */
BUILD_BUG_ON(ARRAY_SIZE(sched_cfg->channels) !=
ARRAY_SIZE(cmd.channels));
/*
* mac80211 removes unused channels before adding new ones, so it may
* update an empty schedule with an availability attribute because it
* is going to add channels later. Since the firmware does not expect
* an availability attribute without channels, ignore it in that case.
*/
if (empty_schedule)
cmd.avail_attr.attr_len = 0;
/* find links we can keep (same chanctx/PHY) */
for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
struct ieee80211_chanctx_conf *chanctx;
struct iwl_mld_nan_link *link;
chanctx = sched_cfg->channels[i].chanctx_conf;
/* ULW */
if (!chanctx)
continue;
link = iwl_mld_nan_find_link(mld_vif, chanctx);
links[i] = link;
if (link)
link_used[link->fw_id] = true;
}
/* add/reassign links for new channels */
for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
struct ieee80211_chanctx_conf *chanctx;
/* already have an existing active link */
if (links[i])
continue;
chanctx = sched_cfg->channels[i].chanctx_conf;
/* ULW or unused slot */
if (!chanctx)
continue;
/*
* if this fails we still update the schedule, but
* without a valid link we'll always ULW it
*/
links[i] = iwl_mld_nan_link_add(mld, mld_vif, chanctx);
/* we have a link, activate it */
if (links[i]) {
added_links = true;
link_used[links[i]->fw_id] = true;
iwl_mld_nan_link_set_active(mld, vif, links[i], true);
}
}
/* fill the command */
for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
cmd.channels[i].link_id = FW_CTXT_ID_INVALID;
if (!sched_cfg->channels[i].chanreq.oper.chan)
continue;
memcpy(cmd.channels[i].channel_entry,
sched_cfg->channels[i].channel_entry, 6);
cmd.channels[i].link_id =
links[i] ? links[i]->fw_id : FW_CTXT_ID_INVALID;
}
for (i = 0; i < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; i++) {
int chan_idx;
if (!slots[i])
continue;
chan_idx = slots[i] - sched_cfg->channels;
if (WARN_ON_ONCE(chan_idx < 0 ||
chan_idx >= ARRAY_SIZE(cmd.channels)))
continue;
cmd.channels[chan_idx].availability_map |= cpu_to_le32(BIT(i));
}
ret = iwl_mld_send_cmd_pdu(mld, cmd_id, &cmd, cmd_size);
if (ret)
IWL_ERR(mld, "NAN: failed to update schedule (%d)\n", ret);
/* prepare stations for links we'll remove */
for_each_mld_nan_valid_link(mld_vif, nan_link) {
if (!link_used[nan_link->fw_id]) {
iwl_mld_nan_link_set_active(mld, vif, nan_link, false);
remove_link_ids |= BIT(nan_link->fw_id);
/* mark unused for STA updates */
nan_link->fw_id = FW_CTXT_ID_INVALID;
}
}
if (added_links || remove_link_ids) {
struct ieee80211_sta *sta;
for_each_station(sta, mld->hw) {
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NMI ||
mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI)
iwl_mld_add_modify_sta_cmd(mld, &sta->deflink);
}
/*
* Iterate over all the NAN Data interfaces and update the links
* for the internal multicast data station.
* In recovery - the station will be added later in
* drv_add_interface
*/
if (iwl_mld_nan_use_nan_stations(mld) && !mld->fw_status.in_hw_restart) {
struct ieee80211_vif *iter;
for_each_active_interface(iter, mld->hw)
iwl_mld_nan_set_mcast_data_links(iter);
}
}
/* delete unused links */
for_each_set_bit(i, &remove_link_ids, ARRAY_SIZE(mld_vif->nan.links))
iwl_mld_nan_link_remove(mld, &mld_vif->nan.links[i], i);
/* remove MAC if needed */
if (!previously_empty_schedule && empty_schedule) {
/* must have been added */
WARN_ON_ONCE(!mld_vif->nan.mac_added);
/* mac80211 should reconfigure same state */
if (!WARN_ON_ONCE(mld->fw_status.in_hw_restart &&
!iwl_mld_error_before_recovery(mld)))
iwl_mld_rm_vif(mld, vif);
}
}
bool iwl_mld_cancel_nan_sched_update_completed_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id)
{
return true;
}
void iwl_mld_handle_nan_sched_update_completed_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
struct iwl_nan_sched_update_completed_notif *notif = (void *)pkt->data;
struct ieee80211_vif *vif = mld->nan_device_vif;
if (IWL_FW_CHECK(mld, !vif,
"NAN: schedule update completed without NAN vif\n"))
return;
if (IWL_FW_CHECK(mld, !ieee80211_vif_nan_started(vif),
"NAN: schedule update completed without NAN started\n"))
return;
/*
* Deferred schedule update should not fail in firmware since all
* channels and links were added.
*/
IWL_FW_CHECK(mld, notif->status != IWL_NAN_SCHED_UPDATE_SUCCESS,
"NAN: deferred schedule update failed\n");
if (WARN_ON(!vif->cfg.nan_sched.deferred))
return;
ieee80211_nan_sched_update_done(vif);
}
int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw,
struct ieee80211_sta *sta)
{
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
struct ieee80211_nan_peer_sched *sched = sta->nan_sched;
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif);
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
struct iwl_mld_nan_link *nan_link;
struct iwl_nan_peer_cmd cmd = {
.nmi_sta_id = mld_sta->deflink.fw_id,
.sequence_id = sched->seq_id,
.committed_dw_info = cpu_to_le16(sched->committed_dw),
.max_channel_switch_time = cpu_to_le16(sched->max_chan_switch),
.initial_ulw_size = cpu_to_le32(sched->ulw_size),
.per_phy[0 ... NUM_PHY_CTX - 1] = {
/* unused by FW if availability_map == 0 */
.map_id = CFG80211_NAN_INVALID_MAP_ID,
.link_id = FW_CTXT_ID_INVALID,
},
/* .initial_ulw directly provided below by data[1]/len[1] */
};
struct iwl_host_cmd hcmd = {
.id = WIDE_ID(MAC_CONF_GROUP, NAN_PEER_CMD),
.data[0] = &cmd,
.len[0] = sizeof(cmd),
.data[1] = sched->init_ulw,
.len[1] = sched->ulw_size,
.dataflags[1] = IWL_HCMD_DFL_DUP,
};
struct ieee80211_sta *iter;
/* Update TLC in case peer channels were added/removed/updated */
for_each_station(iter, mld->hw) {
struct iwl_mld_sta *tmp = iwl_mld_sta_from_mac80211(iter);
if (tmp->sta_type == STATION_TYPE_NAN_PEER_NDI)
iwl_mld_config_tlc(mld, tmp->vif, iter);
}
for (int i = 0; i < ARRAY_SIZE(sched->maps); i++) {
if (sched->maps[i].map_id == CFG80211_NAN_INVALID_MAP_ID)
continue;
BUILD_BUG_ON(ARRAY_SIZE(sched->maps[i].slots) != 32);
for (int slot = 0;
slot < ARRAY_SIZE(sched->maps[i].slots);
slot++) {
struct ieee80211_chanctx_conf *ctx;
struct ieee80211_nan_channel *chan;
struct iwl_mld_phy *phy;
chan = sched->maps[i].slots[slot];
if (!chan)
continue;
ctx = chan->chanctx_conf;
if (!ctx)
continue;
phy = iwl_mld_phy_from_mac80211(ctx);
for_each_mld_nan_valid_link(mld_vif, nan_link) {
if (nan_link->chanctx == ctx) {
cmd.per_phy[phy->fw_id].link_id =
nan_link->fw_id;
break;
}
}
if (WARN_ON(cmd.per_phy[phy->fw_id].link_id ==
FW_CTXT_ID_INVALID))
continue;
/*
* each channel can only appear in one map,
* upper layers enforce that
*/
if (WARN_ON(cmd.per_phy[phy->fw_id].map_id != CFG80211_NAN_INVALID_MAP_ID &&
cmd.per_phy[phy->fw_id].map_id != sched->maps[i].map_id))
continue;
cmd.per_phy[phy->fw_id].map_id = sched->maps[i].map_id;
memcpy(cmd.per_phy[phy->fw_id].channel_entry,
chan->channel_entry,
sizeof(cmd.per_phy[phy->fw_id].channel_entry));
cmd.per_phy[phy->fw_id].availability_map |=
cpu_to_le32(BIT(slot));
}
}
return iwl_mld_send_cmd(mld, &hcmd);
}

View File

@ -1,12 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2025 Intel Corporation
* Copyright (C) 2025-2026 Intel Corporation
*/
#ifndef __iwl_mld_nan_h__
#define __iwl_mld_nan_h__
#include <net/cfg80211.h>
#include <linux/etherdevice.h>
/**
* struct iwl_mld_nan_link - struct representing a NAN link
* @chanctx: the channel context
* @active: indicates the NAN link is currently active
* @fw_id: FW link ID
*/
struct iwl_mld_nan_link {
struct ieee80211_chanctx_conf *chanctx;
bool active;
u8 fw_id;
};
/* Cleanup function for struct iwl_mld_nan_link, will be called in restart */
static inline void iwl_mld_cleanup_nan_link(struct iwl_mld_nan_link *nan_link)
{
memset(nan_link, 0, sizeof(*nan_link));
nan_link->fw_id = FW_CTXT_ID_INVALID;
}
bool iwl_mld_nan_supported(struct iwl_mld *mld);
int iwl_mld_start_nan(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
@ -19,13 +38,33 @@ int iwl_mld_stop_nan(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
void iwl_mld_handle_nan_cluster_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt);
void iwl_mld_handle_nan_ulw_attr_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt);
void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt);
void iwl_mld_handle_nan_sched_update_completed_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt);
bool iwl_mld_cancel_nan_cluster_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id);
bool iwl_mld_cancel_nan_ulw_attr_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id);
bool iwl_mld_cancel_nan_dw_end_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id);
bool iwl_mld_cancel_nan_sched_update_completed_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id);
void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
struct ieee80211_vif *vif,
u64 changes);
int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw,
struct ieee80211_sta *sta);
int iwl_mld_nan_get_mgmt_queue(struct iwl_mld *mld, struct ieee80211_vif *vif);
bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld);
#endif /* __iwl_mld_nan_h__ */

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include "mld.h"
@ -307,11 +307,8 @@ CMD_VERSIONS(tx_resp_notif,
CMD_VER_ENTRY(8, iwl_tx_resp)
CMD_VER_ENTRY(9, iwl_tx_resp))
CMD_VERSIONS(compressed_ba_notif,
CMD_VER_ENTRY(5, iwl_compressed_ba_notif)
CMD_VER_ENTRY(6, iwl_compressed_ba_notif)
CMD_VER_ENTRY(7, iwl_compressed_ba_notif))
CMD_VERSIONS(tlc_notif,
CMD_VER_ENTRY(3, iwl_tlc_update_notif)
CMD_VER_ENTRY(4, iwl_tlc_update_notif))
CMD_VERSIONS(mu_mimo_grp_notif,
CMD_VER_ENTRY(1, iwl_mu_group_mgmt_notif))
@ -330,7 +327,8 @@ CMD_VERSIONS(probe_resp_data_notif,
CMD_VERSIONS(datapath_monitor_notif,
CMD_VER_ENTRY(1, iwl_datapath_monitor_notif))
CMD_VERSIONS(stats_oper_notif,
CMD_VER_ENTRY(3, iwl_system_statistics_notif_oper))
CMD_VER_ENTRY(3, iwl_system_statistics_notif_oper_v3)
CMD_VER_ENTRY(4, iwl_system_statistics_notif_oper))
CMD_VERSIONS(stats_oper_part1_notif,
CMD_VER_ENTRY(4, iwl_system_statistics_part1_notif_oper))
CMD_VERSIONS(bt_coex_notif,
@ -341,8 +339,6 @@ CMD_VERSIONS(emlsr_mode_notif,
CMD_VER_ENTRY(2, iwl_esr_mode_notif))
CMD_VERSIONS(emlsr_trans_fail_notif,
CMD_VER_ENTRY(1, iwl_esr_trans_fail_notif))
CMD_VERSIONS(uapsd_misbehaving_ap_notif,
CMD_VER_ENTRY(1, iwl_uapsd_misbehaving_ap_notif))
CMD_VERSIONS(time_msmt_notif,
CMD_VER_ENTRY(1, iwl_time_msmt_notify))
CMD_VERSIONS(time_sync_confirm_notif,
@ -350,7 +346,10 @@ CMD_VERSIONS(time_sync_confirm_notif,
CMD_VERSIONS(ftm_resp_notif, CMD_VER_ENTRY(10, iwl_tof_range_rsp_ntfy))
CMD_VERSIONS(beacon_filter_notif, CMD_VER_ENTRY(2, iwl_beacon_filter_notif))
CMD_VERSIONS(nan_cluster_notif, CMD_VER_ENTRY(1, iwl_nan_cluster_notif))
CMD_VERSIONS(nan_ulw_attr_notif, CMD_VER_ENTRY(1, iwl_nan_ulw_attr_notif))
CMD_VERSIONS(nan_dw_end_notif, CMD_VER_ENTRY(1, iwl_nan_dw_end_notif))
CMD_VERSIONS(nan_sched_update_completed_notif,
CMD_VER_ENTRY(1, iwl_nan_sched_update_completed_notif))
DEFINE_SIMPLE_CANCELLATION(session_prot, iwl_session_prot_notif, mac_link_id)
DEFINE_SIMPLE_CANCELLATION(tlc, iwl_tlc_update_notif, sta_id)
@ -365,8 +364,6 @@ DEFINE_SIMPLE_CANCELLATION(scan_complete, iwl_umac_scan_complete, uid)
DEFINE_SIMPLE_CANCELLATION(scan_start, iwl_umac_scan_start, uid)
DEFINE_SIMPLE_CANCELLATION(probe_resp_data, iwl_probe_resp_data_notif,
mac_id)
DEFINE_SIMPLE_CANCELLATION(uapsd_misbehaving_ap, iwl_uapsd_misbehaving_ap_notif,
mac_id)
DEFINE_SIMPLE_CANCELLATION(ftm_resp, iwl_tof_range_rsp_ntfy, request_id)
DEFINE_SIMPLE_CANCELLATION(beacon_filter, iwl_beacon_filter_notif, link_id)
@ -457,8 +454,6 @@ const struct iwl_rx_handler iwl_mld_rx_handlers[] = {
emlsr_mode_notif, RX_HANDLER_ASYNC)
RX_HANDLER_NO_OBJECT(MAC_CONF_GROUP, EMLSR_TRANS_FAIL_NOTIF,
emlsr_trans_fail_notif, RX_HANDLER_ASYNC)
RX_HANDLER_OF_VIF(LEGACY_GROUP, PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION,
uapsd_misbehaving_ap_notif)
RX_HANDLER_NO_OBJECT(LEGACY_GROUP,
WNM_80211V_TIMING_MEASUREMENT_NOTIFICATION,
time_msmt_notif, RX_HANDLER_SYNC)
@ -471,8 +466,12 @@ const struct iwl_rx_handler iwl_mld_rx_handlers[] = {
ftm_resp_notif)
RX_HANDLER_OF_NAN(MAC_CONF_GROUP, NAN_JOINED_CLUSTER_NOTIF,
nan_cluster_notif)
RX_HANDLER_OF_NAN(MAC_CONF_GROUP, NAN_ULW_ATTR_NOTIF,
nan_ulw_attr_notif)
RX_HANDLER_OF_NAN(MAC_CONF_GROUP, NAN_DW_END_NOTIF,
nan_dw_end_notif)
RX_HANDLER_OF_NAN(MAC_CONF_GROUP, NAN_SCHED_UPDATE_COMPLETED_NOTIF,
nan_sched_update_completed_notif)
};
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_rx_handlers);
@ -607,6 +606,11 @@ void iwl_mld_rx(struct iwl_op_mode *op_mode, struct napi_struct *napi,
else if (unlikely(cmd_id == WIDE_ID(DATA_PATH_GROUP,
RX_QUEUES_NOTIFICATION)))
iwl_mld_handle_rx_queues_sync_notif(mld, napi, pkt, 0);
#ifdef CONFIG_PM_SLEEP
else if (unlikely(cmd_id == WIDE_ID(DATA_PATH_GROUP,
RSC_NOTIF)))
iwl_mld_handle_rsc_notif(mld, pkt, 0);
#endif
else if (cmd_id == WIDE_ID(DATA_PATH_GROUP, PHY_AIR_SNIFFER_NOTIF))
iwl_mld_handle_phy_air_sniffer_notif(mld, napi, pkt);
else
@ -630,6 +634,11 @@ void iwl_mld_rx_rss(struct iwl_op_mode *op_mode, struct napi_struct *napi,
iwl_mld_handle_rx_queues_sync_notif(mld, napi, pkt, queue);
else if (unlikely(cmd_id == WIDE_ID(LEGACY_GROUP, FRAME_RELEASE)))
iwl_mld_handle_frame_release_notif(mld, napi, pkt, queue);
#ifdef CONFIG_PM_SLEEP
else if (unlikely(cmd_id == WIDE_ID(DATA_PATH_GROUP,
RSC_NOTIF)))
iwl_mld_handle_rsc_notif(mld, pkt, queue);
#endif
}
void iwl_mld_delete_handlers(struct iwl_mld *mld, const u16 *cmds, int n_cmds)
@ -685,10 +694,6 @@ void iwl_mld_cancel_async_notifications(struct iwl_mld *mld)
{
struct iwl_async_handler_entry *entry, *tmp;
lockdep_assert_wiphy(mld->wiphy);
wiphy_work_cancel(mld->wiphy, &mld->async_handlers_wk);
spin_lock_bh(&mld->async_handlers_lock);
list_for_each_entry_safe(entry, tmp, &mld->async_handlers_list, list) {
iwl_mld_log_async_handler_op(mld, "Purged", &entry->rxb);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include <net/mac80211.h>
@ -99,9 +99,9 @@ iwl_mld_nl80211_width_to_fw(enum nl80211_chan_width width)
/* Maps the driver specific control channel position (relative to the center
* freq) definitions to the fw values
*/
u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef)
static u8 _iwl_mld_get_fw_ctrl_pos(u32 control, u32 cf1)
{
int offs = chandef->chan->center_freq - chandef->center_freq1;
int offs = control - cf1;
int abs_offs = abs(offs);
u8 ret;
@ -127,6 +127,12 @@ u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef)
return ret;
}
u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef)
{
return _iwl_mld_get_fw_ctrl_pos(chandef->chan->center_freq,
chandef->center_freq1);
}
int iwl_mld_phy_fw_action(struct iwl_mld *mld,
struct ieee80211_chanctx_conf *ctx, u32 action)
{
@ -150,6 +156,18 @@ int iwl_mld_phy_fw_action(struct iwl_mld *mld,
cmd.sbb_ctrl_channel_loc = iwl_mld_get_fw_ctrl_pos(&ctx->ap);
}
/*
* Set NPCA channel if NPCA is used; if not used, just set it to an
* arbitrary channel on the other side to help firmware.
*/
if (chandef->npca_chan)
cmd.secondary_ctrl_chnl_loc =
_iwl_mld_get_fw_ctrl_pos(chandef->npca_chan->center_freq,
chandef->center_freq1);
else
cmd.secondary_ctrl_chnl_loc =
cmd.ci.ctrl_pos ^ IWL_PHY_CTRL_POS_ABOVE;
ret = iwl_mld_send_cmd_pdu(mld, PHY_CONTEXT_CMD, &cmd);
if (ret)
IWL_ERR(mld, "Failed to send PHY_CONTEXT_CMD ret = %d\n", ret);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include <net/mac80211.h>
@ -11,36 +11,11 @@
#include "link.h"
#include "constants.h"
static void iwl_mld_vif_ps_iterator(void *data, u8 *mac,
struct ieee80211_vif *vif)
{
bool *ps_enable = (bool *)data;
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
if (vif->type != NL80211_IFTYPE_STATION)
return;
*ps_enable &= !mld_vif->ps_disabled;
}
int iwl_mld_update_device_power(struct iwl_mld *mld, bool d3)
{
struct iwl_device_power_cmd cmd = {};
bool enable_ps = false;
if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) {
enable_ps = true;
/* Disable power save if any STA interface has
* power save turned off
*/
ieee80211_iterate_active_interfaces_mtx(mld->hw,
IEEE80211_IFACE_ITER_NORMAL,
iwl_mld_vif_ps_iterator,
&enable_ps);
}
if (enable_ps)
if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM)
cmd.flags |=
cpu_to_le16(DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK);
@ -113,10 +88,10 @@ static bool iwl_mld_power_is_radar(struct iwl_mld *mld,
return chanctx_conf->def.chan->flags & IEEE80211_CHAN_RADAR;
}
static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld,
struct iwl_mld_link *link,
struct iwl_mac_power_cmd *cmd,
bool ps_poll)
static void iwl_mld_power_configure_uapsd_v2(struct iwl_mld *mld,
struct iwl_mld_link *link,
struct iwl_mac_power_cmd_v2 *cmd,
bool ps_poll)
{
bool tid_found = false;
@ -175,10 +150,54 @@ static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld,
cmd->uapsd_max_sp = mld->hw->uapsd_max_sp_len;
}
static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld,
struct iwl_mld_link *link,
struct iwl_mac_power_cmd *cmd,
bool ps_poll)
{
bool tid_found = false;
/* set advanced pm flag with no uapsd ACs to enable ps-poll */
if (ps_poll) {
cmd->flags |= cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK);
return;
}
for (enum ieee80211_ac_numbers ac = IEEE80211_AC_VO;
ac <= IEEE80211_AC_BK;
ac++) {
if (!link->queue_params[ac].uapsd)
continue;
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 && !link->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;
}
}
}
}
static void
iwl_mld_power_config_skip_dtim(struct iwl_mld *mld,
const struct ieee80211_bss_conf *link_conf,
struct iwl_mac_power_cmd *cmd)
u8 *skip_dtim_periods, __le16 *flags)
{
unsigned int dtimper_tu;
unsigned int dtimper;
@ -196,15 +215,15 @@ iwl_mld_power_config_skip_dtim(struct iwl_mld *mld,
/* configure skip over dtim up to 900 TU DTIM interval */
skip = max_t(int, 1, 900 / dtimper_tu);
cmd->skip_dtim_periods = skip;
cmd->flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK);
*skip_dtim_periods = skip;
*flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK);
}
#define POWER_KEEP_ALIVE_PERIOD_SEC 25
static void iwl_mld_power_build_cmd(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct iwl_mac_power_cmd *cmd,
bool d3)
static void iwl_mld_power_build_cmd_v2(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct iwl_mac_power_cmd_v2 *cmd,
bool d3)
{
int dtimper, bi;
int keep_alive;
@ -252,9 +271,7 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld,
return;
cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK);
if (iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0) >= 2)
cmd->flags |= cpu_to_le16(POWER_FLAGS_ENABLE_SMPS_MSK);
cmd->flags |= cpu_to_le16(POWER_FLAGS_ENABLE_SMPS_MSK);
/* firmware supports LPRX for beacons at rate 1 Mbps or 6 Mbps only */
if (link_conf->beacon_rate &&
@ -265,7 +282,9 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld,
}
if (d3) {
iwl_mld_power_config_skip_dtim(mld, link_conf, cmd);
iwl_mld_power_config_skip_dtim(mld, link_conf,
&cmd->skip_dtim_periods,
&cmd->flags);
cmd->rx_data_timeout =
cpu_to_le32(IWL_MLD_WOWLAN_PS_RX_DATA_TIMEOUT);
cmd->tx_data_timeout =
@ -286,6 +305,95 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld,
* mac80211 will allow uAPSD. Always call iwl_mld_power_configure_uapsd
* which will look at what mac80211 is saying.
*/
#ifdef CONFIG_IWLWIFI_DEBUGFS
ps_poll = mld_vif->use_ps_poll;
#endif
iwl_mld_power_configure_uapsd_v2(mld, link, cmd, ps_poll);
}
static void iwl_mld_power_build_cmd(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct iwl_mac_power_cmd *cmd,
bool d3)
{
int dtimper, bi;
int keep_alive;
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct ieee80211_bss_conf *link_conf = &vif->bss_conf;
struct iwl_mld_link *link = &mld_vif->deflink;
bool ps_poll = false;
__le32 fw_id = cpu_to_le32(mld_vif->fw_id);
if (ieee80211_vif_is_mld(vif)) {
int link_id;
if (WARN_ON(!vif->active_links))
return;
/* The firmware consumes one single configuration for the vif
* and can't differentiate between links, just pick the lowest
* link_id's configuration and use that.
*/
link_id = __ffs(vif->active_links);
link_conf = link_conf_dereference_check(vif, link_id);
link = iwl_mld_link_dereference_check(mld_vif, link_id);
if (WARN_ON(!link_conf || !link))
return;
}
dtimper = link_conf->dtim_period;
bi = link_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->id_and_color = fw_id;
cmd->keep_alive_seconds = cpu_to_le16(keep_alive);
if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM)
cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK);
if (vif->cfg.ps && iwl_mld_tdls_sta_count(mld) == 0) {
cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK);
cmd->flags |= cpu_to_le16(POWER_FLAGS_ENABLE_SMPS_MSK);
/* firmware supports LPRX for beacons at rate 1 Mbps or
* 6 Mbps only
*/
if (link_conf->beacon_rate &&
(link_conf->beacon_rate->bitrate == 10 ||
link_conf->beacon_rate->bitrate == 60)) {
cmd->flags |= cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK);
cmd->lprx_rssi_threshold = POWER_LPRX_RSSI_THRESHOLD;
}
}
if (d3) {
iwl_mld_power_config_skip_dtim(mld, link_conf,
&cmd->skip_dtim_periods,
&cmd->flags);
cmd->rx_data_timeout =
cpu_to_le32(IWL_MLD_WOWLAN_PS_RX_DATA_TIMEOUT);
cmd->tx_data_timeout =
cpu_to_le32(IWL_MLD_WOWLAN_PS_TX_DATA_TIMEOUT);
} else if (iwl_mld_vif_low_latency(mld_vif) && vif->p2p) {
cmd->tx_data_timeout =
cpu_to_le32(IWL_MLD_SHORT_PS_TX_DATA_TIMEOUT);
cmd->rx_data_timeout =
cpu_to_le32(IWL_MLD_SHORT_PS_RX_DATA_TIMEOUT);
} else {
cmd->rx_data_timeout =
cpu_to_le32(IWL_MLD_DEFAULT_PS_RX_DATA_TIMEOUT);
cmd->tx_data_timeout =
cpu_to_le32(IWL_MLD_DEFAULT_PS_TX_DATA_TIMEOUT);
}
#ifdef CONFIG_IWLWIFI_DEBUGFS
ps_poll = mld_vif->use_ps_poll;
#endif
@ -295,11 +403,23 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld,
int iwl_mld_update_mac_power(struct iwl_mld *mld, struct ieee80211_vif *vif,
bool d3)
{
struct iwl_mac_power_cmd cmd = {};
int cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0);
iwl_mld_power_build_cmd(mld, vif, &cmd, d3);
if (cmd_ver >= 3) {
struct iwl_mac_power_cmd cmd = {};
return iwl_mld_send_cmd_pdu(mld, MAC_PM_POWER_TABLE, &cmd);
iwl_mld_power_build_cmd(mld, vif, &cmd, d3);
return iwl_mld_send_cmd_with_flags_pdu(mld,
MAC_PM_POWER_TABLE, 0,
&cmd, sizeof(cmd));
} else {
struct iwl_mac_power_cmd_v2 cmd = {};
iwl_mld_power_build_cmd_v2(mld, vif, &cmd, d3);
return iwl_mld_send_cmd_with_flags_pdu(mld,
MAC_PM_POWER_TABLE, 0,
&cmd, sizeof(cmd));
}
}
static void

View File

@ -321,10 +321,10 @@ void iwl_mld_ptp_remove(struct iwl_mld *mld)
mld->ptp_data.ptp_clock_info.name,
ptp_clock_index(mld->ptp_data.ptp_clock));
cancel_delayed_work_sync(&mld->ptp_data.dwork);
ptp_clock_unregister(mld->ptp_data.ptp_clock);
mld->ptp_data.ptp_clock = NULL;
mld->ptp_data.last_gp2 = 0;
mld->ptp_data.wrap_counter = 0;
cancel_delayed_work_sync(&mld->ptp_data.dwork);
}
}

View File

@ -39,8 +39,7 @@ iwl_mld_fill_phy_data_from_mpdu(struct iwl_mld *mld,
}
phy_data->phy_info = le16_to_cpu(desc->phy_info);
phy_data->rate_n_flags = iwl_v3_rate_from_v2_v3(desc->v3.rate_n_flags,
mld->fw_rates_ver_3);
phy_data->rate_n_flags = le32_to_cpu(desc->v3.rate_n_flags);
phy_data->gp2_on_air_rise = le32_to_cpu(desc->v3.gp2_on_air_rise);
phy_data->energy_a = desc->v3.energy_a;
phy_data->energy_b = desc->v3.energy_b;
@ -158,7 +157,7 @@ static bool iwl_mld_used_average_energy(struct iwl_mld *mld, int link_id,
guard(rcu)();
link_conf = rcu_dereference(mld->fw_id_to_bss_conf[link_id]);
if (!link_conf)
if (IS_ERR_OR_NULL(link_conf))
return false;
mld_link = iwl_mld_link_from_mac80211(link_conf);
@ -1206,6 +1205,13 @@ static void iwl_mld_decode_eht_non_tb(struct iwl_mld_rx_phy_data *phy_data,
iwl_mld_eht_set_ru_alloc(rx_status,
le32_get_bits(phy_data->ntfy->sigs.eht.b2,
OFDM_RX_FRAME_EHT_STA_RU));
if (phy_data->with_data)
eht->user_info[0] |=
cpu_to_le32(IEEE80211_RADIOTAP_EHT_USER_INFO_STA_ID_KNOWN) |
LE32_DEC_ENC(phy_data->ntfy->sigs.eht.user_id,
OFDM_RX_FRAME_EHT_USER_FIELD_ID,
IEEE80211_RADIOTAP_EHT_USER_INFO_STA_ID);
}
static void iwl_mld_decode_eht_phy_data(struct iwl_mld_rx_phy_data *phy_data,
@ -1314,14 +1320,6 @@ static void iwl_mld_rx_eht(struct iwl_mld *mld, struct sk_buff *skb,
if (likely(!phy_data->ntfy))
return;
if (phy_data->with_data) {
eht->user_info[0] |=
cpu_to_le32(IEEE80211_RADIOTAP_EHT_USER_INFO_STA_ID_KNOWN) |
LE32_DEC_ENC(phy_data->ntfy->sigs.eht.user_id,
OFDM_RX_FRAME_EHT_USER_FIELD_ID,
IEEE80211_RADIOTAP_EHT_USER_INFO_STA_ID);
}
iwl_mld_decode_eht_usig(phy_data, skb);
iwl_mld_decode_eht_phy_data(phy_data, rx_status, eht);
}
@ -2262,6 +2260,30 @@ void iwl_mld_handle_rx_queues_sync_notif(struct iwl_mld *mld,
wake_up(&mld->rxq_sync.waitq);
}
#ifdef CONFIG_PM_SLEEP
void iwl_mld_handle_rsc_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt, int queue)
{
const struct iwl_wowlan_all_rsc_tsc_v5 *notif = (void *)pkt->data;
u32 len = iwl_rx_packet_payload_len(pkt);
struct ieee80211_vif *bss_vif;
if (IWL_FW_CHECK(mld, len != sizeof(*notif),
"invalid notification size %u (%zu)\n",
len, sizeof(*notif)))
return;
/* for the bss lookup and updating the keys' pn */
guard(rcu)();
bss_vif = iwl_mld_get_bss_vif(mld);
if (WARN_ON(!bss_vif))
return;
iwl_mld_process_rsc_notification(mld, bss_vif, notif, queue);
}
#endif /* CONFIG_PM_SLEEP */
static void iwl_mld_no_data_rx(struct iwl_mld *mld,
struct napi_struct *napi,
struct iwl_rx_phy_air_sniffer_ntfy *ntfy)

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#ifndef __iwl_mld_rx_h__
#define __iwl_mld_rx_h__
@ -61,6 +61,11 @@ void iwl_mld_handle_rx_queues_sync_notif(struct iwl_mld *mld,
struct napi_struct *napi,
struct iwl_rx_packet *pkt, int queue);
#ifdef CONFIG_PM_SLEEP
void iwl_mld_handle_rsc_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt, int queue);
#endif
void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include <linux/ieee80211.h>
@ -13,6 +13,7 @@
#include "key.h"
#include "agg.h"
#include "tlc.h"
#include "nan.h"
#include "fw/api/sta.h"
#include "fw/api/mac.h"
#include "fw/api/rx.h"
@ -43,13 +44,13 @@ int iwl_mld_fw_sta_id_from_link_sta(struct iwl_mld *mld,
static void
iwl_mld_fill_ampdu_size_and_dens(struct ieee80211_link_sta *link_sta,
struct ieee80211_bss_conf *link,
bool is_6ghz,
__le32 *tx_ampdu_max_size,
__le32 *tx_ampdu_spacing)
{
u32 agg_size = 0, mpdu_dens = 0;
if (WARN_ON(!link_sta || !link))
if (WARN_ON(!link_sta))
return;
/* Note that we always use only legacy & highest supported PPDUs, so
@ -63,7 +64,7 @@ iwl_mld_fill_ampdu_size_and_dens(struct ieee80211_link_sta *link_sta,
mpdu_dens = link_sta->ht_cap.ampdu_density;
}
if (link->chanreq.oper.chan->band == NL80211_BAND_6GHZ) {
if (is_6ghz) {
/* overwrite HT values on 6 GHz */
mpdu_dens =
le16_get_bits(link_sta->he_6ghz_capa.capa,
@ -427,8 +428,14 @@ static int iwl_mld_send_sta_cmd(struct iwl_mld *mld,
len = sizeof(struct iwl_sta_cfg_cmd_v2);
cmd_v2->link_id = cpu_to_le32(__ffs(le32_to_cpu(cmd->link_mask)));
} else if (cmd->station_type ==
cpu_to_le32(STATION_TYPE_NAN_MCAST_DATA)) {
if (WARN_ON(!hweight32(le32_to_cpu(cmd->link_mask))))
return -EINVAL;
} else if (WARN_ON(cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_PEER_NMI) &&
cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_PEER_NDI) &&
cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_BCAST) &&
cmd->station_type != cpu_to_le32(STATION_TYPE_NAN_MGMT) &&
hweight32(le32_to_cpu(cmd->link_mask)) != 1)) {
return -EINVAL;
}
@ -439,29 +446,61 @@ static int iwl_mld_send_sta_cmd(struct iwl_mld *mld,
return ret;
}
static int
iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
struct ieee80211_link_sta *link_sta)
static u32 iwl_mld_get_nan_link_mask(struct iwl_mld *mld)
{
struct iwl_mld_vif *nan_dev =
iwl_mld_vif_from_mac80211(mld->nan_device_vif);
struct iwl_mld_nan_link *nan_link;
u32 link_mask = 0;
for_each_mld_nan_valid_link(nan_dev, nan_link)
link_mask |= BIT(nan_link->fw_id);
return link_mask;
}
int iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
struct ieee80211_link_sta *link_sta)
{
struct ieee80211_sta *sta = link_sta->sta;
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
struct ieee80211_bss_conf *link;
struct iwl_mld_link *mld_link;
struct iwl_sta_cfg_cmd cmd = {};
int fw_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
bool is_6ghz, uora_exists;
u32 link_mask;
lockdep_assert_wiphy(mld->wiphy);
link = link_conf_dereference_protected(mld_sta->vif,
link_sta->link_id);
mld_link = iwl_mld_link_from_mac80211(link);
if (WARN_ON(!link || !mld_link) || fw_id < 0)
if (WARN_ON(fw_id < 0))
return -EINVAL;
if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NMI ||
mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI) {
if (WARN_ON(!mld->nan_device_vif))
return -EINVAL;
is_6ghz = false;
uora_exists = false;
link_mask = iwl_mld_get_nan_link_mask(mld);
} else {
struct ieee80211_bss_conf *link;
struct iwl_mld_link *mld_link;
link = link_conf_dereference_protected(mld_sta->vif,
link_sta->link_id);
mld_link = iwl_mld_link_from_mac80211(link);
if (WARN_ON(!link || !mld_link))
return -EINVAL;
link_mask = BIT(mld_link->fw_id);
is_6ghz = link->chanreq.oper.chan->band == NL80211_BAND_6GHZ;
uora_exists = link->uora_exists;
}
cmd.sta_id = cpu_to_le32(fw_id);
cmd.link_mask = cpu_to_le32(BIT(mld_link->fw_id));
cmd.link_mask = cpu_to_le32(link_mask);
cmd.station_type = cpu_to_le32(mld_sta->sta_type);
memcpy(&cmd.peer_mld_address, sta->addr, ETH_ALEN);
@ -499,7 +538,7 @@ iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
break;
}
iwl_mld_fill_ampdu_size_and_dens(link_sta, link,
iwl_mld_fill_ampdu_size_and_dens(link_sta, is_6ghz,
&cmd.tx_ampdu_max_size,
&cmd.tx_ampdu_spacing);
@ -511,7 +550,7 @@ iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
if (link_sta->he_cap.has_he) {
cmd.trig_rnd_alloc =
cpu_to_le32(link->uora_exists ? 1 : 0);
cpu_to_le32(uora_exists ? 1 : 0);
/* PPE Thresholds */
iwl_mld_fill_pkt_ext(mld, link_sta, &cmd.pkt_ext);
@ -525,10 +564,29 @@ iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
cmd.ack_enabled = cpu_to_le32(1);
}
if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI) {
struct ieee80211_sta *nmi_sta =
wiphy_dereference(mld->wiphy, sta->nmi);
int nmi_fw_id;
/* copy the local NDI address */
ether_addr_copy(cmd.ndi_local_addr, mld_sta->vif->addr);
if (WARN_ON(!nmi_sta))
return -EINVAL;
nmi_fw_id = iwl_mld_fw_sta_id_from_link_sta(mld,
&nmi_sta->deflink);
if (nmi_fw_id < 0)
return -EINVAL;
cmd.nmi_sta_id = (u8) nmi_fw_id;
}
return iwl_mld_send_sta_cmd(mld, &cmd);
}
static IWL_MLD_ALLOC_FN(link_sta, link_sta)
IWL_MLD_ALLOC_FN_STATIC(link_sta, link_sta)
static int
iwl_mld_add_link_sta(struct iwl_mld *mld, struct ieee80211_link_sta *link_sta)
@ -759,10 +817,23 @@ int iwl_mld_add_sta(struct iwl_mld *mld, struct ieee80211_sta *sta,
{
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
struct ieee80211_link_sta *link_sta;
enum iwl_fw_sta_type type;
int link_id;
int ret;
ret = iwl_mld_init_sta(mld, sta, vif, STATION_TYPE_PEER);
switch (vif->type) {
case NL80211_IFTYPE_NAN:
type = STATION_TYPE_NAN_PEER_NMI;
break;
case NL80211_IFTYPE_NAN_DATA:
type = STATION_TYPE_NAN_PEER_NDI;
break;
default:
type = STATION_TYPE_PEER;
break;
}
ret = iwl_mld_init_sta(mld, sta, vif, type);
if (ret)
return ret;
@ -1001,10 +1072,9 @@ static int iwl_mld_send_aux_sta_cmd(struct iwl_mld *mld,
}
static int
iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld,
iwl_mld_set_internal_sta_to_fw(struct iwl_mld *mld,
const struct iwl_mld_int_sta *internal_sta,
u8 fw_link_id,
const u8 *addr)
u32 link_mask, const u8 *addr)
{
struct iwl_sta_cfg_cmd cmd = {};
@ -1012,20 +1082,30 @@ iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld,
return iwl_mld_send_aux_sta_cmd(mld, internal_sta);
cmd.sta_id = cpu_to_le32((u8)internal_sta->sta_id);
cmd.link_mask = cpu_to_le32(BIT(fw_link_id));
cmd.link_mask = cpu_to_le32(link_mask);
cmd.station_type = cpu_to_le32(internal_sta->sta_type);
/* FW doesn't allow to add a IGTK/BIGTK if the sta isn't marked as MFP.
* On the other hand, FW will never check this flag during RX since
* an AP/GO doesn't receive protected broadcast management frames.
* So, we can set it unconditionally.
*
* For NAN stations associated with a NAN Device, the MFP bit must be
* set to 1, as otherwise the FW will assert when a key associated with
* these stations would be added.
*/
if (internal_sta->sta_type == STATION_TYPE_BCAST_MGMT)
if (internal_sta->sta_type == STATION_TYPE_BCAST_MGMT ||
internal_sta->sta_type == STATION_TYPE_NAN_BCAST ||
internal_sta->sta_type == STATION_TYPE_NAN_MGMT)
cmd.mfp = cpu_to_le32(1);
if (addr) {
memcpy(cmd.peer_mld_address, addr, ETH_ALEN);
memcpy(cmd.peer_link_address, addr, ETH_ALEN);
if (internal_sta->sta_type == STATION_TYPE_NAN_MCAST_DATA) {
ether_addr_copy(cmd.ndi_local_addr, addr);
} else {
memcpy(cmd.peer_mld_address, addr, ETH_ALEN);
memcpy(cmd.peer_link_address, addr, ETH_ALEN);
}
}
return iwl_mld_send_sta_cmd(mld, &cmd);
@ -1034,7 +1114,8 @@ iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld,
static int iwl_mld_add_internal_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *internal_sta,
enum iwl_fw_sta_type sta_type,
u8 fw_link_id, const u8 *addr, u8 tid)
u32 link_mask, const u8 *addr,
u8 tid, bool add_txq)
{
int ret, queue_id;
@ -1046,11 +1127,14 @@ static int iwl_mld_add_internal_sta(struct iwl_mld *mld,
internal_sta->sta_type = sta_type;
ret = iwl_mld_add_internal_sta_to_fw(mld, internal_sta, fw_link_id,
ret = iwl_mld_set_internal_sta_to_fw(mld, internal_sta, link_mask,
addr);
if (ret)
goto err;
if (!add_txq)
return 0;
queue_id = iwl_mld_allocate_internal_txq(mld, internal_sta, tid);
if (queue_id < 0) {
iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id);
@ -1085,8 +1169,8 @@ int iwl_mld_add_bcast_sta(struct iwl_mld *mld,
return iwl_mld_add_internal_sta(mld, &mld_link->bcast_sta,
STATION_TYPE_BCAST_MGMT,
mld_link->fw_id, addr,
IWL_MGMT_TID);
BIT(mld_link->fw_id), addr,
IWL_MGMT_TID, true);
}
int iwl_mld_add_mcast_sta(struct iwl_mld *mld,
@ -1105,14 +1189,16 @@ int iwl_mld_add_mcast_sta(struct iwl_mld *mld,
return iwl_mld_add_internal_sta(mld, &mld_link->mcast_sta,
STATION_TYPE_MCAST,
mld_link->fw_id, mcast_addr, 0);
BIT(mld_link->fw_id), mcast_addr,
0, true);
}
int iwl_mld_add_aux_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *internal_sta)
{
return iwl_mld_add_internal_sta(mld, internal_sta, STATION_TYPE_AUX,
0, NULL, IWL_MAX_TID_COUNT);
0, NULL, IWL_MAX_TID_COUNT,
true);
}
int iwl_mld_add_mon_sta(struct iwl_mld *mld,
@ -1129,23 +1215,25 @@ int iwl_mld_add_mon_sta(struct iwl_mld *mld,
return iwl_mld_add_internal_sta(mld, &mld_link->mon_sta,
STATION_TYPE_BCAST_MGMT,
mld_link->fw_id, NULL,
IWL_MAX_TID_COUNT);
BIT(mld_link->fw_id), NULL,
IWL_MAX_TID_COUNT,
true);
}
static void iwl_mld_remove_internal_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *internal_sta,
bool flush, u8 tid)
{
if (WARN_ON_ONCE(internal_sta->sta_id == IWL_INVALID_STA ||
internal_sta->queue_id == IWL_MLD_INVALID_QUEUE))
if (WARN_ON_ONCE(internal_sta->sta_id == IWL_INVALID_STA))
return;
if (flush)
if (flush && !WARN_ON_ONCE(internal_sta->queue_id ==
IWL_MLD_INVALID_QUEUE))
iwl_mld_flush_link_sta_txqs(mld, internal_sta->sta_id);
iwl_mld_free_txq(mld, BIT(internal_sta->sta_id),
tid, internal_sta->queue_id);
if (internal_sta->queue_id != IWL_MLD_INVALID_QUEUE)
iwl_mld_free_txq(mld, BIT(internal_sta->sta_id),
tid, internal_sta->queue_id);
iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id);
@ -1346,3 +1434,82 @@ int iwl_mld_update_link_stas(struct iwl_mld *mld,
return ret;
}
int iwl_mld_add_nan_bcast_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta)
{
const u8 bcast_addr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
return iwl_mld_add_internal_sta(mld, sta, STATION_TYPE_NAN_BCAST,
0, bcast_addr, 0, false);
}
int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta)
{
return iwl_mld_add_internal_sta(mld, sta, STATION_TYPE_NAN_MGMT,
0, NULL, IWL_MAX_TID_COUNT, true);
}
int iwl_mld_add_nan_mcast_data_sta(struct iwl_mld *mld,
const u8 *ndi_addr,
struct iwl_mld_int_sta *sta)
{
u32 link_mask = iwl_mld_get_nan_link_mask(mld);
/* In case that there are no NAN links, nothing to do */
if (!link_mask)
return 0;
return iwl_mld_add_internal_sta(mld, sta,
STATION_TYPE_NAN_MCAST_DATA,
link_mask, ndi_addr,
0, true);
}
int iwl_mld_update_nan_mcast_data_sta(struct iwl_mld *mld,
const u8 *ndi_addr,
struct iwl_mld_int_sta *sta)
{
u32 link_mask;
if (WARN_ON(!mld->nan_device_vif))
return -EINVAL;
/* If the sta doesn't exist, add it */
if (sta->sta_id == IWL_INVALID_STA)
return iwl_mld_add_nan_mcast_data_sta(mld, ndi_addr, sta);
/* The station was already added */
if (WARN_ON(sta->sta_type != STATION_TYPE_NAN_MCAST_DATA))
return -EINVAL;
link_mask = iwl_mld_get_nan_link_mask(mld);
if (link_mask)
return iwl_mld_set_internal_sta_to_fw(mld,
sta, link_mask,
ndi_addr);
/* If no links are associated with NAN, remove the station */
iwl_mld_remove_nan_mcast_data_sta(mld, sta);
return 0;
}
void iwl_mld_remove_nan_bcast_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta)
{
iwl_mld_remove_internal_sta(mld, sta, false, 0);
}
void iwl_mld_remove_nan_mgmt_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta)
{
iwl_mld_remove_internal_sta(mld, sta, true, IWL_MAX_TID_COUNT);
}
void iwl_mld_remove_nan_mcast_data_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta)
{
iwl_mld_remove_internal_sta(mld, sta, true, 0);
}

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#ifndef __iwl_mld_sta_h__
@ -28,6 +28,9 @@ struct iwl_mld_rxq_dup_data {
* This represents the link-level sta - the driver level equivalent to the
* ieee80211_link_sta
*
* @rx_igtk: FW can only have one IGTK for RX at a time, whereas mac80211 will
* have two. This tracks the one IGTK that currently exists in FW, to
* remove it there when a new one is installed.
* @last_rate_n_flags: rate_n_flags from the last &iwl_tlc_update_notif
* @signal_avg: the signal average coming from the firmware
* @in_fw: whether the link STA is uploaded to the FW (false during restart)
@ -37,6 +40,7 @@ struct iwl_mld_rxq_dup_data {
struct iwl_mld_link_sta {
/* Add here fields that need clean up on restart */
struct_group(zeroed_on_hw_restart,
struct ieee80211_key_conf *rx_igtk;
u32 last_rate_n_flags;
bool in_fw;
s8 signal_avg;
@ -195,6 +199,8 @@ void iwl_mld_remove_sta(struct iwl_mld *mld, struct ieee80211_sta *sta);
int iwl_mld_fw_sta_id_from_link_sta(struct iwl_mld *mld,
struct ieee80211_link_sta *link_sta);
u32 iwl_mld_fw_sta_id_mask(struct iwl_mld *mld, struct ieee80211_sta *sta);
int iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
struct ieee80211_link_sta *link_sta);
int iwl_mld_update_all_link_stations(struct iwl_mld *mld,
struct ieee80211_sta *sta);
void iwl_mld_flush_sta_txqs(struct iwl_mld *mld, struct ieee80211_sta *sta);
@ -270,4 +276,28 @@ int iwl_mld_update_link_stas(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
u16 old_links, u16 new_links);
int iwl_mld_add_nan_bcast_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta);
int iwl_mld_add_nan_mgmt_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta);
int iwl_mld_add_nan_mcast_data_sta(struct iwl_mld *mld,
const u8 *ndi_addr,
struct iwl_mld_int_sta *sta);
int iwl_mld_update_nan_mcast_data_sta(struct iwl_mld *mld,
const u8 *ndi_addr,
struct iwl_mld_int_sta *sta);
void iwl_mld_remove_nan_bcast_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta);
void iwl_mld_remove_nan_mgmt_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta);
void iwl_mld_remove_nan_mcast_data_sta(struct iwl_mld *mld,
struct iwl_mld_int_sta *sta);
#endif /* __iwl_mld_sta_h__ */

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2024-2025 Intel Corporation
* Copyright (C) 2024-2026 Intel Corporation
*/
#include "mld.h"
@ -40,13 +40,21 @@ iwl_mld_fill_stats_from_oper_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u8 fw_sta_id, struct station_info *sinfo)
{
const struct iwl_system_statistics_notif_oper *notif =
(void *)&pkt->data;
const struct iwl_stats_ntfy_per_sta *per_sta =
&notif->per_sta[fw_sta_id];
const struct iwl_stats_ntfy_per_sta *per_sta;
struct ieee80211_link_sta *link_sta;
struct iwl_mld_link_sta *mld_link_sta;
if (iwl_fw_lookup_notif_ver(mld->fw, STATISTICS_GROUP,
STATISTICS_OPER_NOTIF, 3) >= 4) {
const struct iwl_system_statistics_notif_oper *notif =
(void *)&pkt->data;
per_sta = &notif->per_sta[fw_sta_id];
} else {
const struct iwl_system_statistics_notif_oper_v3 *notif =
(void *)&pkt->data;
per_sta = &notif->per_sta[fw_sta_id];
}
/* 0 isn't a valid value, but FW might send 0.
* In that case, set the latest non-zero value we stored
*/
@ -303,6 +311,40 @@ static void iwl_mld_sta_stats_fill_txrate(struct iwl_mld_sta *mld_sta,
}
}
static void iwl_mld_sta_stats_fill_beacon_signal_avg(struct ieee80211_vif *vif,
struct station_info *sinfo)
{
struct ieee80211_bss_conf *link_conf;
struct iwl_mld_link *link;
u8 link_id;
if (iwl_mld_emlsr_active(vif))
return;
/* TODO: support statistics for NAN */
if (vif->type == NL80211_IFTYPE_NAN ||
vif->type == NL80211_IFTYPE_NAN_DATA)
return;
link_id = iwl_mld_get_primary_link(vif);
link_conf = link_conf_dereference_protected(vif, link_id);
if (WARN_ONCE(!link_conf,
"link_conf is NULL for link_id=%u\n", link_id))
return;
link = iwl_mld_link_from_mac80211(link_conf);
if (WARN_ONCE(!link,
"iwl_mld_link is NULL for link_id=%u\n", link_id))
return;
if (!link->avg_signal)
return;
sinfo->rx_beacon_signal_avg = link->avg_signal;
sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_SIGNAL_AVG);
}
void iwl_mld_mac80211_sta_statistics(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
@ -321,9 +363,9 @@ void iwl_mld_mac80211_sta_statistics(struct ieee80211_hw *hw,
iwl_mld_sta_stats_fill_txrate(mld_sta, sinfo);
/* TODO: NL80211_STA_INFO_BEACON_RX */
iwl_mld_sta_stats_fill_beacon_signal_avg(vif, sinfo);
/* TODO: NL80211_STA_INFO_BEACON_SIGNAL_AVG */
/* TODO: NL80211_STA_INFO_BEACON_RX */
}
#define IWL_MLD_TRAFFIC_LOAD_MEDIUM_THRESH 10 /* percentage */
@ -435,6 +477,8 @@ iwl_mld_process_per_link_stats(struct iwl_mld *mld,
fw_id++) {
const struct iwl_stats_ntfy_per_link *link_stats;
struct ieee80211_bss_conf *bss_conf;
struct iwl_mld_link *link;
u32 avg_raw;
int sig;
bss_conf = iwl_mld_fw_id_to_link_conf(mld, fw_id);
@ -448,6 +492,13 @@ iwl_mld_process_per_link_stats(struct iwl_mld *mld,
sig = -le32_to_cpu(link_stats->beacon_filter_average_energy);
iwl_mld_update_link_sig(bss_conf->vif, sig, bss_conf);
link = iwl_mld_link_from_mac80211(bss_conf);
if (WARN_ON_ONCE(!link))
continue;
avg_raw = le32_to_cpu(link_stats->beacon_average_energy);
link->avg_signal = clamp_t(int, -(int)avg_raw, S8_MIN, 0);
/* TODO: parse more fields here (task=statistics)*/
}
@ -507,6 +558,10 @@ static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw,
(old_load >> 1);
}
IWL_DEBUG_EHT(phy->mld,
"PHY %d: load_by_us=%u%% load_not_by_us=%u%%\n",
phy->fw_id, phy->channel_load_by_us, new_load);
iwl_mld_emlsr_check_chan_load(hw, phy, old_load);
}
@ -523,17 +578,42 @@ iwl_mld_process_per_phy_stats(struct iwl_mld *mld,
void iwl_mld_handle_stats_oper_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
const struct iwl_system_statistics_notif_oper *stats =
struct iwl_system_statistics_notif_oper *_notif __free(kfree) = NULL;
const struct iwl_system_statistics_notif_oper *notif =
(void *)&pkt->data;
u32 curr_ts_usec = le32_to_cpu(stats->time_stamp);
BUILD_BUG_ON(ARRAY_SIZE(stats->per_sta) != IWL_STATION_COUNT_MAX);
BUILD_BUG_ON(ARRAY_SIZE(stats->per_link) <
BUILD_BUG_ON(ARRAY_SIZE(notif->per_sta) != IWL_STATION_COUNT_MAX);
BUILD_BUG_ON(ARRAY_SIZE(notif->per_link) <
ARRAY_SIZE(mld->fw_id_to_bss_conf));
iwl_mld_process_per_link_stats(mld, stats->per_link, curr_ts_usec);
iwl_mld_process_per_sta_stats(mld, stats->per_sta);
iwl_mld_process_per_phy_stats(mld, stats->per_phy);
if (iwl_fw_lookup_notif_ver(mld->fw, STATISTICS_GROUP,
STATISTICS_OPER_NOTIF, 3) == 3) {
const struct iwl_system_statistics_notif_oper_v3 *stats =
(void *)&pkt->data;
_notif = kzalloc_obj(*_notif);
if (!_notif)
return;
_notif->time_stamp = stats->time_stamp;
for (int i = 0; i < ARRAY_SIZE(_notif->per_link); i++)
_notif->per_link[i] = stats->per_link[i];
BUILD_BUG_ON(sizeof(_notif->per_phy[0]) <
sizeof(stats->per_phy[0]));
for (int i = 0; i < ARRAY_SIZE(_notif->per_phy); i++)
memcpy(&_notif->per_phy[i], &stats->per_phy[i],
sizeof(stats->per_phy[i]));
for (int i = 0; i < ARRAY_SIZE(_notif->per_sta); i++)
_notif->per_sta[i] = stats->per_sta[i];
notif = _notif;
}
iwl_mld_process_per_link_stats(mld, notif->per_link,
le32_to_cpu(notif->time_stamp));
iwl_mld_process_per_sta_stats(mld, notif->per_sta);
iwl_mld_process_per_phy_stats(mld, notif->per_phy);
}
void iwl_mld_handle_stats_oper_part1_notif(struct iwl_mld *mld,

Some files were not shown because too many files have changed in this diff Show More