diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-05-29 03:05:23 +0300 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-05-29 03:05:23 +0300 |
| commit | e7d6bd24e883bf7c328d73c99bf6bcde19bf5e61 (patch) | |
| tree | b0ef04b56501883d2b56defe4096911cac66bf41 | |
| parent | 9a82e387e27a4422a0d2d9d644180b7bd913e85a (diff) | |
| parent | 6f5dc19f46f4bd0e104c9a4da2f0a912cdf3bd86 (diff) | |
| download | linux-e7d6bd24e883bf7c328d73c99bf6bcde19bf5e61.tar.xz | |
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>
129 files changed, 7075 insertions, 2551 deletions
diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h index 73a9db302245..dfee432615eb 100644 --- a/drivers/net/wireless/ath/ath10k/core.h +++ b/drivers/net/wireless/ath/ath10k/core.h @@ -1269,7 +1269,6 @@ struct ath10k { } testmode; struct { - struct gpio_led wifi_led; struct led_classdev cdev; char label[48]; u32 gpio_state_pin; diff --git a/drivers/net/wireless/ath/ath10k/htt_tx.c b/drivers/net/wireless/ath/ath10k/htt_tx.c index d6f1d85ba871..29e99fbf36fd 100644 --- a/drivers/net/wireless/ath/ath10k/htt_tx.c +++ b/drivers/net/wireless/ath/ath10k/htt_tx.c @@ -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. diff --git a/drivers/net/wireless/ath/ath10k/leds.c b/drivers/net/wireless/ath/ath10k/leds.c index 3a6c8111e7c6..a3961e7760a5 100644 --- a/drivers/net/wireless/ath/ath10k/leds.c +++ b/drivers/net/wireless/ath/ath10k/leds.c @@ -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) diff --git a/drivers/net/wireless/ath/ath10k/qmi.c b/drivers/net/wireless/ath/ath10k/qmi.c index e7f90fd9e9b8..0d74548a5f34 100644 --- a/drivers/net/wireless/ath/ath10k/qmi.c +++ b/drivers/net/wireless/ath/ath10k/qmi.c @@ -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; diff --git a/drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h b/drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h index 9f311f3bc9e7..88d58f78989d 100644 --- a/drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h +++ b/drivers/net/wireless/ath/ath10k/qmi_wlfw_v01.h @@ -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 diff --git a/drivers/net/wireless/ath/ath11k/Kconfig b/drivers/net/wireless/ath/ath11k/Kconfig index 385513cfdc30..122726f84492 100644 --- a/drivers/net/wireless/ath/ath11k/Kconfig +++ b/drivers/net/wireless/ath/ath11k/Kconfig @@ -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 diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c index bbb86f165141..5a50b623bd07 100644 --- a/drivers/net/wireless/ath/ath11k/dp.c +++ b/drivers/net/wireless/ath/ath11k/dp.c @@ -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 */ diff --git a/drivers/net/wireless/ath/ath11k/mhi.c b/drivers/net/wireless/ath/ath11k/mhi.c index f994233df2bb..a6c9ff112c68 100644 --- a/drivers/net/wireless/ath/ath11k/mhi.c +++ b/drivers/net/wireless/ath/ath11k/mhi.c @@ -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: diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c index 7114eca8810d..35bb9e7a63a2 100644 --- a/drivers/net/wireless/ath/ath11k/pci.c +++ b/drivers/net/wireless/ath/ath11k/pci.c @@ -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); } diff --git a/drivers/net/wireless/ath/ath11k/qmi.c b/drivers/net/wireless/ath/ath11k/qmi.c index feebbc30f3df..410a7ee076a0 100644 --- a/drivers/net/wireless/ath/ath11k/qmi.c +++ b/drivers/net/wireless/ath/ath11k/qmi.c @@ -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) { diff --git a/drivers/net/wireless/ath/ath11k/qmi.h b/drivers/net/wireless/ath/ath11k/qmi.h index 7968ab122b65..eae416db8b52 100644 --- a/drivers/net/wireless/ath/ath11k/qmi.h +++ b/drivers/net/wireless/ath/ath11k/qmi.h @@ -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 diff --git a/drivers/net/wireless/ath/ath12k/Kconfig b/drivers/net/wireless/ath/ath12k/Kconfig index d39c075758bd..4a2b240f967a 100644 --- a/drivers/net/wireless/ath/ath12k/Kconfig +++ b/drivers/net/wireless/ath/ath12k/Kconfig @@ -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 diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 3d7da2ea65f7..742d4fd1b598 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -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 @@ core_pdev_create: 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); - goto err; - } - - ath12k_hif_irq_enable(ab); - - ret = ath12k_core_rfkill_config(ab); - if (ret && ret != -EOPNOTSUPP) { - mutex_unlock(&ab->core_lock); + ret = ath12k_core_device_setup(ab); + if (ret) goto err; - } - - mutex_unlock(&ab->core_lock); } return 0; diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 8be435535a4e..f6d8ec9ef7b0 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -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 { diff --git a/drivers/net/wireless/ath/ath12k/debugfs.c b/drivers/net/wireless/ath/ath12k/debugfs.c index 8c81a1c22449..d17d4a8f1cb7 100644 --- a/drivers/net/wireless/ath/ath12k/debugfs.c +++ b/drivers/net/wireless/ath/ath12k/debugfs.c @@ -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); diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index b108ccd0f637..a68b28aa1f4b 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -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); diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 2cff9485c95a..87b27f7cff5d 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -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; diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 375277ca2b89..d9a22d6afbb0 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -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); diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 8c5dacf227da..fd762b5d7bb5 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -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) { diff --git a/drivers/net/wireless/ath/ath12k/qmi.h b/drivers/net/wireless/ath/ath12k/qmi.h index b5a4a01391cb..2a63e214eb42 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.h +++ b/drivers/net/wireless/ath/ath12k/qmi.h @@ -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 diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c index a764d2112a3c..97fc49c40ac1 100644 --- a/drivers/net/wireless/ath/ath12k/thermal.c +++ b/drivers/net/wireless/ath/ath12k/thermal.c @@ -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, ¶m); + 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, ¶m); + 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); -int ath12k_thermal_register(struct ath12k_base *ab) +static int ath12k_thermal_setup_radio(struct ath12k_base *ab, int i) { + char pdev_name[20]; struct ath12k *ar; - int i, j, ret; + 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) +{ + 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); } diff --git a/drivers/net/wireless/ath/ath12k/thermal.h b/drivers/net/wireless/ath/ath12k/thermal.h index 9d84056188e1..30e7b0880e05 100644 --- a/drivers/net/wireless/ath/ath12k/thermal.h +++ b/drivers/net/wireless/ath/ath12k/thermal.h @@ -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_ */ diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 9b8b370ab5ec..18f91051199c 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -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 @@ exit: 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 diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h index 4a34b2ca99ea..14b8dcdf881d 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.h +++ b/drivers/net/wireless/ath/ath12k/wmi.h @@ -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); diff --git a/drivers/net/wireless/ath/ath9k/ar9002_hw.c b/drivers/net/wireless/ath/ath9k/ar9002_hw.c index b26224480041..0f24539b75ec 100644 --- a/drivers/net/wireless/ath/ath9k/ar9002_hw.c +++ b/drivers/net/wireless/ath/ath9k/ar9002_hw.c @@ -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)) { diff --git a/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c b/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c index fe1013a3a588..b9ef34709202 100644 --- a/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c +++ b/drivers/net/wireless/ath/ath9k/ath9k_pci_owl_loader.c @@ -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); diff --git a/drivers/net/wireless/ath/ath9k/common-init.c b/drivers/net/wireless/ath/ath9k/common-init.c index da102c791712..52e02e0752d8 100644 --- a/drivers/net/wireless/ath/ath9k/common-init.c +++ b/drivers/net/wireless/ath/ath9k/common-init.c @@ -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 = diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c index ee951493e993..e1a67e8ed09f 100644 --- a/drivers/net/wireless/ath/ath9k/init.c +++ b/drivers/net/wireless/ath/ath9k/init.c @@ -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); diff --git a/drivers/net/wireless/ath/ath9k/recv.c b/drivers/net/wireless/ath/ath9k/recv.c index 34c74ed99b7b..93b41a1bb2af 100644 --- a/drivers/net/wireless/ath/ath9k/recv.c +++ b/drivers/net/wireless/ath/ath9k/recv.c @@ -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; diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c index 0ac9212e42f7..89d8b3178784 100644 --- a/drivers/net/wireless/ath/ath9k/xmit.c +++ b/drivers/net/wireless/ath/ath9k/xmit.c @@ -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); diff --git a/drivers/net/wireless/intel/iwlwifi/Makefile b/drivers/net/wireless/intel/iwlwifi/Makefile index 941257b811b4..2a74efe09cf0 100644 --- a/drivers/net/wireless/intel/iwlwifi/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/Makefile @@ -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 diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c index f0453f3f6ba6..01ca65eb5acd 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c @@ -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)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/7000.c b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c index f987ad3192c1..1be72d71fccf 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/7000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c @@ -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, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/8000.c b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c index 3c844cd419e8..834aa520ab0c 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/8000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c @@ -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, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/9000.c b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c index 5872fc9b8caf..2954434ce851 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/9000.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c @@ -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, diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c index 582f61661062..2519f577669e 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/ax210.c @@ -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)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 3653ddbf3ce9..ecb4f81a99f5 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -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 = { diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c index 83d893b10f8e..e8968b3051d3 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/dr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/dr.c @@ -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 = { diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c index ad2536f53084..294cf25ae2a6 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-fm.c @@ -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"; diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c index c16cda087a7c..99a5110924cd 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-gf.c @@ -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)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c index 6cf187d92dbf..16b9075acdd8 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-hr.c @@ -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)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c index 2c29054ce7b8..7a04cb120b1b 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-pe.c @@ -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)"; diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c b/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c index b5803ea1eb78..c432aa1a0af6 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/rf-wh.c @@ -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"; diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c index 749d46dc0236..6aaa49aeec99 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/sc.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/sc.c @@ -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 = { diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h index 36159a769916..abd259350589 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h @@ -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, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h index 06370c161fe4..e494e5b18d22 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h @@ -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 @@ -92,6 +92,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 */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/location.h b/drivers/net/wireless/intel/iwlwifi/fw/api/location.h index 2ee3a48aa5df..421ea94ace01 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/location.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/location.h @@ -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,13 +81,54 @@ 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) * @one_sided_disabled: indicates if one-sided 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 diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h index b398c582b867..09b2cddc4ad2 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -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 @@ -72,6 +73,23 @@ enum iwl_mac_conf_subcmd_ids { */ 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 */ NAN_DW_END_NOTIF = 0xf4, @@ -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__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h index a3f916630df2..115e65ba19f8 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h @@ -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. diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h index ac6c1ef2cbcd..699343cf0279 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h @@ -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 diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h b/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h index e6f9abdfa546..3e62a458b131 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h @@ -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) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h b/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h index 68983f6a0026..8d16782a129c 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h @@ -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,6 +557,65 @@ struct iwl_stats_ntfy_per_mac { #define IWL_STATS_MAX_BW_INDEX 5 /** + * 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 + * @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 + */ +struct iwl_stats_ntfy_per_phy_v1 { + __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; +} __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 @@ -571,6 +630,7 @@ struct iwl_stats_ntfy_per_mac { * @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; @@ -583,7 +643,8 @@ struct iwl_stats_ntfy_per_phy { __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; -} __packed; /* STATISTICS_NTFY_PER_PHY_API_S_VER_1 */ + 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 @@ -600,11 +661,26 @@ 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; diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg-old.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg-old.c new file mode 100644 index 000000000000..19b1bfd0abee --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg-old.c @@ -0,0 +1,1022 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2026 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include <linux/devcoredump.h> +#include "iwl-drv.h" +#include "runtime.h" +#include "dbg.h" +#include "debugfs.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" +#include "iwl-fh.h" + +/** + * struct iwl_fw_dump_ptrs - set of pointers needed for the fw-error-dump + * + * @fwrt_ptr: pointer to the buffer coming from fwrt + * @trans_ptr: pointer to struct %iwl_trans_dump_data which contains the + * transport's data. + * @fwrt_len: length of the valid data in fwrt_ptr + */ +struct iwl_fw_dump_ptrs { + struct iwl_trans_dump_data *trans_ptr; + void *fwrt_ptr; + u32 fwrt_len; +}; + +#define RADIO_REG_MAX_READ 0x2ad +static void iwl_read_radio_regs(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + u8 *pos = (void *)(*dump_data)->data; + int i; + + IWL_DEBUG_INFO(fwrt, "WRT radio registers dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RADIO_REG); + (*dump_data)->len = cpu_to_le32(RADIO_REG_MAX_READ); + + for (i = 0; i < RADIO_REG_MAX_READ; i++) { + u32 rd_cmd = RADIO_RSP_RD_CMD; + + rd_cmd |= i << RADIO_RSP_ADDR_POS; + iwl_trans_write_prph(fwrt->trans, RSP_RADIO_CMD, rd_cmd); + *pos = (u8)iwl_trans_read_prph(fwrt->trans, RSP_RADIO_RDDAT); + + pos++; + } + + *dump_data = iwl_fw_error_next_data(*dump_data); + + iwl_trans_release_nic_access(fwrt->trans); +} + +static void iwl_fwrt_dump_rxf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + int size, u32 offset, int fifo_num) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + u32 *fifo_data; + u32 fifo_len; + int i; + + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = size; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + return; + + /* Add a TLV for the RXF */ + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RXF); + (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(fifo_num); + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_D_SPACE + offset)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_WR_PTR + offset)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_RD_PTR + offset)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_FENCE_PTR + offset)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_SET_FENCE_MODE + offset)); + + /* Lock fence */ + iwl_trans_write_prph(fwrt->trans, RXF_SET_FENCE_MODE + offset, 0x1); + /* Set fence pointer to the same place like WR pointer */ + iwl_trans_write_prph(fwrt->trans, RXF_LD_WR2FENCE + offset, 0x1); + /* Set fence offset */ + iwl_trans_write_prph(fwrt->trans, + RXF_LD_FENCE_OFFSET_ADDR + offset, 0x0); + + /* Read FIFO */ + fifo_len /= sizeof(u32); /* Size in DWORDS */ + for (i = 0; i < fifo_len; i++) + fifo_data[i] = iwl_trans_read_prph(fwrt->trans, + RXF_FIFO_RD_FENCE_INC + + offset); + *dump_data = iwl_fw_error_next_data(*dump_data); +} + +static void iwl_fwrt_dump_txf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + int size, u32 offset, int fifo_num) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + u32 *fifo_data; + u32 fifo_len; + int i; + + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = size; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + return; + + /* Add a TLV for the FIFO */ + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXF); + (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(fifo_num); + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_FIFO_ITEM_CNT + offset)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_WR_PTR + offset)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_RD_PTR + offset)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_FENCE_PTR + offset)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_LOCK_FENCE + offset)); + + /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ + iwl_trans_write_prph(fwrt->trans, TXF_READ_MODIFY_ADDR + offset, + TXF_WR_PTR + offset); + + /* Dummy-read to advance the read pointer to the head */ + iwl_trans_read_prph(fwrt->trans, TXF_READ_MODIFY_DATA + offset); + + /* Read FIFO */ + for (i = 0; i < fifo_len / sizeof(u32); i++) + fifo_data[i] = iwl_trans_read_prph(fwrt->trans, + TXF_READ_MODIFY_DATA + + offset); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) + fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, + fifo_data, fifo_len); + + *dump_data = iwl_fw_error_next_data(*dump_data); +} + +static void iwl_fw_dump_rxf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + + IWL_DEBUG_INFO(fwrt, "WRT RX FIFO dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) { + /* Pull RXF1 */ + iwl_fwrt_dump_rxf(fwrt, dump_data, + cfg->lmac[0].rxfifo1_size, 0, 0); + /* Pull RXF2 */ + iwl_fwrt_dump_rxf(fwrt, dump_data, cfg->rxfifo2_size, + RXF_DIFF_FROM_PREV + + fwrt->trans->mac_cfg->umac_prph_offset, 1); + /* Pull LMAC2 RXF1 */ + if (fwrt->smem_cfg.num_lmacs > 1) + iwl_fwrt_dump_rxf(fwrt, dump_data, + cfg->lmac[1].rxfifo1_size, + LMAC2_PRPH_OFFSET, 2); + } + + iwl_trans_release_nic_access(fwrt->trans); +} + +static void iwl_fw_dump_txf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + u32 *fifo_data; + u32 fifo_len; + int i, j; + + IWL_DEBUG_INFO(fwrt, "WRT TX FIFO dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) { + /* Pull TXF data from LMAC1 */ + for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; i++) { + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, TXF_LARC_NUM, i); + iwl_fwrt_dump_txf(fwrt, dump_data, + cfg->lmac[0].txfifo_size[i], 0, i); + } + + /* Pull TXF data from LMAC2 */ + if (fwrt->smem_cfg.num_lmacs > 1) { + for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; + i++) { + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, + TXF_LARC_NUM + + LMAC2_PRPH_OFFSET, i); + iwl_fwrt_dump_txf(fwrt, dump_data, + cfg->lmac[1].txfifo_size[i], + LMAC2_PRPH_OFFSET, + i + cfg->num_txfifo_entries); + } + } + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) { + /* Pull UMAC internal TXF data from all TXFs */ + for (i = 0; + i < ARRAY_SIZE(fwrt->smem_cfg.internal_txfifo_size); + i++) { + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = fwrt->smem_cfg.internal_txfifo_size[i]; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + continue; + + /* Add a TLV for the internal FIFOs */ + (*dump_data)->type = + cpu_to_le32(IWL_FW_ERROR_DUMP_INTERNAL_TXF); + (*dump_data)->len = + cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(i); + + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, TXF_CPU2_NUM, i + + fwrt->smem_cfg.num_txfifo_entries); + + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_FIFO_ITEM_CNT)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_WR_PTR)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_RD_PTR)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_FENCE_PTR)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_LOCK_FENCE)); + + /* Set TXF_CPU2_READ_MODIFY_ADDR to TXF_CPU2_WR_PTR */ + iwl_trans_write_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_ADDR, + TXF_CPU2_WR_PTR); + + /* Dummy-read to advance the read pointer to head */ + iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_DATA); + + /* Read FIFO */ + fifo_len /= sizeof(u32); /* Size in DWORDS */ + for (j = 0; j < fifo_len; j++) + fifo_data[j] = + iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_DATA); + *dump_data = iwl_fw_error_next_data(*dump_data); + } + } + + iwl_trans_release_nic_access(fwrt->trans); +} + +struct iwl_prph_range { + u32 start, end; +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_comm[] = { + { .start = 0x00a00000, .end = 0x00a00000 }, + { .start = 0x00a0000c, .end = 0x00a00024 }, + { .start = 0x00a0002c, .end = 0x00a0003c }, + { .start = 0x00a00410, .end = 0x00a00418 }, + { .start = 0x00a00420, .end = 0x00a00420 }, + { .start = 0x00a00428, .end = 0x00a00428 }, + { .start = 0x00a00430, .end = 0x00a0043c }, + { .start = 0x00a00444, .end = 0x00a00444 }, + { .start = 0x00a004c0, .end = 0x00a004cc }, + { .start = 0x00a004d8, .end = 0x00a004d8 }, + { .start = 0x00a004e0, .end = 0x00a004f0 }, + { .start = 0x00a00840, .end = 0x00a00840 }, + { .start = 0x00a00850, .end = 0x00a00858 }, + { .start = 0x00a01004, .end = 0x00a01008 }, + { .start = 0x00a01010, .end = 0x00a01010 }, + { .start = 0x00a01018, .end = 0x00a01018 }, + { .start = 0x00a01024, .end = 0x00a01024 }, + { .start = 0x00a0102c, .end = 0x00a01034 }, + { .start = 0x00a0103c, .end = 0x00a01040 }, + { .start = 0x00a01048, .end = 0x00a01094 }, + { .start = 0x00a01c00, .end = 0x00a01c20 }, + { .start = 0x00a01c58, .end = 0x00a01c58 }, + { .start = 0x00a01c7c, .end = 0x00a01c7c }, + { .start = 0x00a01c28, .end = 0x00a01c54 }, + { .start = 0x00a01c5c, .end = 0x00a01c5c }, + { .start = 0x00a01c60, .end = 0x00a01cdc }, + { .start = 0x00a01ce0, .end = 0x00a01d0c }, + { .start = 0x00a01d18, .end = 0x00a01d20 }, + { .start = 0x00a01d2c, .end = 0x00a01d30 }, + { .start = 0x00a01d40, .end = 0x00a01d5c }, + { .start = 0x00a01d80, .end = 0x00a01d80 }, + { .start = 0x00a01d98, .end = 0x00a01d9c }, + { .start = 0x00a01da8, .end = 0x00a01da8 }, + { .start = 0x00a01db8, .end = 0x00a01df4 }, + { .start = 0x00a01dc0, .end = 0x00a01dfc }, + { .start = 0x00a01e00, .end = 0x00a01e2c }, + { .start = 0x00a01e40, .end = 0x00a01e60 }, + { .start = 0x00a01e68, .end = 0x00a01e6c }, + { .start = 0x00a01e74, .end = 0x00a01e74 }, + { .start = 0x00a01e84, .end = 0x00a01e90 }, + { .start = 0x00a01e9c, .end = 0x00a01ec4 }, + { .start = 0x00a01ed0, .end = 0x00a01ee0 }, + { .start = 0x00a01f00, .end = 0x00a01f1c }, + { .start = 0x00a01f44, .end = 0x00a01ffc }, + { .start = 0x00a02000, .end = 0x00a02048 }, + { .start = 0x00a02068, .end = 0x00a020f0 }, + { .start = 0x00a02100, .end = 0x00a02118 }, + { .start = 0x00a02140, .end = 0x00a0214c }, + { .start = 0x00a02168, .end = 0x00a0218c }, + { .start = 0x00a021c0, .end = 0x00a021c0 }, + { .start = 0x00a02400, .end = 0x00a02410 }, + { .start = 0x00a02418, .end = 0x00a02420 }, + { .start = 0x00a02428, .end = 0x00a0242c }, + { .start = 0x00a02434, .end = 0x00a02434 }, + { .start = 0x00a02440, .end = 0x00a02460 }, + { .start = 0x00a02468, .end = 0x00a024b0 }, + { .start = 0x00a024c8, .end = 0x00a024cc }, + { .start = 0x00a02500, .end = 0x00a02504 }, + { .start = 0x00a0250c, .end = 0x00a02510 }, + { .start = 0x00a02540, .end = 0x00a02554 }, + { .start = 0x00a02580, .end = 0x00a025f4 }, + { .start = 0x00a02600, .end = 0x00a0260c }, + { .start = 0x00a02648, .end = 0x00a02650 }, + { .start = 0x00a02680, .end = 0x00a02680 }, + { .start = 0x00a026c0, .end = 0x00a026d0 }, + { .start = 0x00a02700, .end = 0x00a0270c }, + { .start = 0x00a02804, .end = 0x00a02804 }, + { .start = 0x00a02818, .end = 0x00a0281c }, + { .start = 0x00a02c00, .end = 0x00a02db4 }, + { .start = 0x00a02df4, .end = 0x00a02fb0 }, + { .start = 0x00a03000, .end = 0x00a03014 }, + { .start = 0x00a0301c, .end = 0x00a0302c }, + { .start = 0x00a03034, .end = 0x00a03038 }, + { .start = 0x00a03040, .end = 0x00a03048 }, + { .start = 0x00a03060, .end = 0x00a03068 }, + { .start = 0x00a03070, .end = 0x00a03074 }, + { .start = 0x00a0307c, .end = 0x00a0307c }, + { .start = 0x00a03080, .end = 0x00a03084 }, + { .start = 0x00a0308c, .end = 0x00a03090 }, + { .start = 0x00a03098, .end = 0x00a03098 }, + { .start = 0x00a030a0, .end = 0x00a030a0 }, + { .start = 0x00a030a8, .end = 0x00a030b4 }, + { .start = 0x00a030bc, .end = 0x00a030bc }, + { .start = 0x00a030c0, .end = 0x00a0312c }, + { .start = 0x00a03c00, .end = 0x00a03c5c }, + { .start = 0x00a04400, .end = 0x00a04454 }, + { .start = 0x00a04460, .end = 0x00a04474 }, + { .start = 0x00a044c0, .end = 0x00a044ec }, + { .start = 0x00a04500, .end = 0x00a04504 }, + { .start = 0x00a04510, .end = 0x00a04538 }, + { .start = 0x00a04540, .end = 0x00a04548 }, + { .start = 0x00a04560, .end = 0x00a0457c }, + { .start = 0x00a04590, .end = 0x00a04598 }, + { .start = 0x00a045c0, .end = 0x00a045f4 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_9000[] = { + { .start = 0x00a05c00, .end = 0x00a05c18 }, + { .start = 0x00a05400, .end = 0x00a056e8 }, + { .start = 0x00a08000, .end = 0x00a098bc }, + { .start = 0x00a02400, .end = 0x00a02758 }, + { .start = 0x00a04764, .end = 0x00a0476c }, + { .start = 0x00a04770, .end = 0x00a04774 }, + { .start = 0x00a04620, .end = 0x00a04624 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_22000[] = { + { .start = 0x00a00000, .end = 0x00a00000 }, + { .start = 0x00a0000c, .end = 0x00a00024 }, + { .start = 0x00a0002c, .end = 0x00a00034 }, + { .start = 0x00a0003c, .end = 0x00a0003c }, + { .start = 0x00a00410, .end = 0x00a00418 }, + { .start = 0x00a00420, .end = 0x00a00420 }, + { .start = 0x00a00428, .end = 0x00a00428 }, + { .start = 0x00a00430, .end = 0x00a0043c }, + { .start = 0x00a00444, .end = 0x00a00444 }, + { .start = 0x00a00840, .end = 0x00a00840 }, + { .start = 0x00a00850, .end = 0x00a00858 }, + { .start = 0x00a01004, .end = 0x00a01008 }, + { .start = 0x00a01010, .end = 0x00a01010 }, + { .start = 0x00a01018, .end = 0x00a01018 }, + { .start = 0x00a01024, .end = 0x00a01024 }, + { .start = 0x00a0102c, .end = 0x00a01034 }, + { .start = 0x00a0103c, .end = 0x00a01040 }, + { .start = 0x00a01048, .end = 0x00a01050 }, + { .start = 0x00a01058, .end = 0x00a01058 }, + { .start = 0x00a01060, .end = 0x00a01070 }, + { .start = 0x00a0108c, .end = 0x00a0108c }, + { .start = 0x00a01c20, .end = 0x00a01c28 }, + { .start = 0x00a01d10, .end = 0x00a01d10 }, + { .start = 0x00a01e28, .end = 0x00a01e2c }, + { .start = 0x00a01e60, .end = 0x00a01e60 }, + { .start = 0x00a01e80, .end = 0x00a01e80 }, + { .start = 0x00a01ea0, .end = 0x00a01ea0 }, + { .start = 0x00a02000, .end = 0x00a0201c }, + { .start = 0x00a02024, .end = 0x00a02024 }, + { .start = 0x00a02040, .end = 0x00a02048 }, + { .start = 0x00a020c0, .end = 0x00a020e0 }, + { .start = 0x00a02400, .end = 0x00a02404 }, + { .start = 0x00a0240c, .end = 0x00a02414 }, + { .start = 0x00a0241c, .end = 0x00a0243c }, + { .start = 0x00a02448, .end = 0x00a024bc }, + { .start = 0x00a024c4, .end = 0x00a024cc }, + { .start = 0x00a02508, .end = 0x00a02508 }, + { .start = 0x00a02510, .end = 0x00a02514 }, + { .start = 0x00a0251c, .end = 0x00a0251c }, + { .start = 0x00a0252c, .end = 0x00a0255c }, + { .start = 0x00a02564, .end = 0x00a025a0 }, + { .start = 0x00a025a8, .end = 0x00a025b4 }, + { .start = 0x00a025c0, .end = 0x00a025c0 }, + { .start = 0x00a025e8, .end = 0x00a025f4 }, + { .start = 0x00a02c08, .end = 0x00a02c18 }, + { .start = 0x00a02c2c, .end = 0x00a02c38 }, + { .start = 0x00a02c68, .end = 0x00a02c78 }, + { .start = 0x00a03000, .end = 0x00a03000 }, + { .start = 0x00a03010, .end = 0x00a03014 }, + { .start = 0x00a0301c, .end = 0x00a0302c }, + { .start = 0x00a03034, .end = 0x00a03038 }, + { .start = 0x00a03040, .end = 0x00a03044 }, + { .start = 0x00a03060, .end = 0x00a03068 }, + { .start = 0x00a03070, .end = 0x00a03070 }, + { .start = 0x00a0307c, .end = 0x00a03084 }, + { .start = 0x00a0308c, .end = 0x00a03090 }, + { .start = 0x00a03098, .end = 0x00a03098 }, + { .start = 0x00a030a0, .end = 0x00a030a0 }, + { .start = 0x00a030a8, .end = 0x00a030b4 }, + { .start = 0x00a030bc, .end = 0x00a030c0 }, + { .start = 0x00a030c8, .end = 0x00a030f4 }, + { .start = 0x00a03100, .end = 0x00a0312c }, + { .start = 0x00a03c00, .end = 0x00a03c5c }, + { .start = 0x00a04400, .end = 0x00a04454 }, + { .start = 0x00a04460, .end = 0x00a04474 }, + { .start = 0x00a044c0, .end = 0x00a044ec }, + { .start = 0x00a04500, .end = 0x00a04504 }, + { .start = 0x00a04510, .end = 0x00a04538 }, + { .start = 0x00a04540, .end = 0x00a04548 }, + { .start = 0x00a04560, .end = 0x00a04560 }, + { .start = 0x00a04570, .end = 0x00a0457c }, + { .start = 0x00a04590, .end = 0x00a04590 }, + { .start = 0x00a04598, .end = 0x00a04598 }, + { .start = 0x00a045c0, .end = 0x00a045f4 }, + { .start = 0x00a05c18, .end = 0x00a05c1c }, + { .start = 0x00a0c000, .end = 0x00a0c018 }, + { .start = 0x00a0c020, .end = 0x00a0c028 }, + { .start = 0x00a0c038, .end = 0x00a0c094 }, + { .start = 0x00a0c0c0, .end = 0x00a0c104 }, + { .start = 0x00a0c10c, .end = 0x00a0c118 }, + { .start = 0x00a0c150, .end = 0x00a0c174 }, + { .start = 0x00a0c17c, .end = 0x00a0c188 }, + { .start = 0x00a0c190, .end = 0x00a0c198 }, + { .start = 0x00a0c1a0, .end = 0x00a0c1a8 }, + { .start = 0x00a0c1b0, .end = 0x00a0c1b8 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_ax210[] = { + { .start = 0x00d03c00, .end = 0x00d03c64 }, + { .start = 0x00d05c18, .end = 0x00d05c1c }, + { .start = 0x00d0c000, .end = 0x00d0c174 }, +}; + +static void iwl_read_prph_block(struct iwl_trans *trans, u32 start, + u32 len_bytes, __le32 *data) +{ + u32 i; + + for (i = 0; i < len_bytes; i += 4) + *data++ = cpu_to_le32(iwl_trans_read_prph(trans, start + i)); +} + +static void iwl_dump_prph(struct iwl_fw_runtime *fwrt, + const struct iwl_prph_range *iwl_prph_dump_addr, + u32 range_len, void *ptr) +{ + struct iwl_fw_error_dump_prph *prph; + struct iwl_trans *trans = fwrt->trans; + struct iwl_fw_error_dump_data **data = + (struct iwl_fw_error_dump_data **)ptr; + u32 i; + + if (!data) + return; + + IWL_DEBUG_INFO(trans, "WRT PRPH dump\n"); + + if (!iwl_trans_grab_nic_access(trans)) + return; + + for (i = 0; i < range_len; i++) { + /* The range includes both boundaries */ + int num_bytes_in_chunk = iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PRPH); + (*data)->len = cpu_to_le32(sizeof(*prph) + + num_bytes_in_chunk); + prph = (void *)(*data)->data; + prph->prph_start = cpu_to_le32(iwl_prph_dump_addr[i].start); + + iwl_read_prph_block(trans, iwl_prph_dump_addr[i].start, + /* our range is inclusive, hence + 4 */ + iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4, + (void *)prph->data); + + *data = iwl_fw_error_next_data(*data); + } + + iwl_trans_release_nic_access(trans); +} + +static void iwl_fw_get_prph_len(struct iwl_fw_runtime *fwrt, + const struct iwl_prph_range *iwl_prph_dump_addr, + u32 range_len, void *ptr) +{ + u32 *prph_len = (u32 *)ptr; + int i, num_bytes_in_chunk; + + if (!prph_len) + return; + + for (i = 0; i < range_len; i++) { + /* The range includes both boundaries */ + num_bytes_in_chunk = + iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4; + + *prph_len += sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_prph) + + num_bytes_in_chunk; + } +} + +static void iwl_fw_prph_handler(struct iwl_fw_runtime *fwrt, void *ptr, + void (*handler)(struct iwl_fw_runtime *, + const struct iwl_prph_range *, + u32, void *)) +{ + u32 range_len; + + if (fwrt->trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_ax210); + handler(fwrt, iwl_prph_dump_addr_ax210, range_len, ptr); + } else if (fwrt->trans->mac_cfg->device_family >= + IWL_DEVICE_FAMILY_22000) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_22000); + handler(fwrt, iwl_prph_dump_addr_22000, range_len, ptr); + } else { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_comm); + handler(fwrt, iwl_prph_dump_addr_comm, range_len, ptr); + + if (fwrt->trans->mac_cfg->mq_rx_supported) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_9000); + handler(fwrt, iwl_prph_dump_addr_9000, range_len, ptr); + } + } +} + +static void iwl_fw_dump_mem(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + u32 len, u32 ofs, u32 type) +{ + struct iwl_fw_error_dump_mem *dump_mem; + + if (!len) + return; + + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM); + (*dump_data)->len = cpu_to_le32(len + sizeof(*dump_mem)); + dump_mem = (void *)(*dump_data)->data; + dump_mem->type = cpu_to_le32(type); + dump_mem->offset = cpu_to_le32(ofs); + iwl_trans_read_mem_bytes(fwrt->trans, ofs, dump_mem->data, len); + *dump_data = iwl_fw_error_next_data(*dump_data); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, ofs, + dump_mem->data, len); + + IWL_DEBUG_INFO(fwrt, "WRT memory dump. Type=%u\n", dump_mem->type); +} + +#define ADD_LEN(len, item_len, const_len) \ + do {size_t item = item_len; len += (!!item) * const_len + item; } \ + while (0) + +static int iwl_fw_rxf_len(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_shared_mem_cfg *mem_cfg) +{ + size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fifo); + u32 fifo_len = 0; + int i; + + if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) + return 0; + + /* Count RXF2 size */ + ADD_LEN(fifo_len, mem_cfg->rxfifo2_size, hdr_len); + + /* Count RXF1 sizes */ + if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) + mem_cfg->num_lmacs = MAX_NUM_LMAC; + + for (i = 0; i < mem_cfg->num_lmacs; i++) + ADD_LEN(fifo_len, mem_cfg->lmac[i].rxfifo1_size, hdr_len); + + return fifo_len; +} + +static int iwl_fw_txf_len(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_shared_mem_cfg *mem_cfg) +{ + size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fifo); + u32 fifo_len = 0; + int i; + + if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) + goto dump_internal_txf; + + /* Count TXF sizes */ + if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) + mem_cfg->num_lmacs = MAX_NUM_LMAC; + + for (i = 0; i < mem_cfg->num_lmacs; i++) { + int j; + + for (j = 0; j < mem_cfg->num_txfifo_entries; j++) + ADD_LEN(fifo_len, mem_cfg->lmac[i].txfifo_size[j], + hdr_len); + } + +dump_internal_txf: + if (!(iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG))) + goto out; + + for (i = 0; i < ARRAY_SIZE(mem_cfg->internal_txfifo_size); i++) + ADD_LEN(fifo_len, mem_cfg->internal_txfifo_size[i], hdr_len); + +out: + return fifo_len; +} + +static void iwl_dump_paging(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **data) +{ + int i; + + IWL_DEBUG_INFO(fwrt, "WRT paging dump\n"); + for (i = 1; i < fwrt->num_of_paging_blk + 1; i++) { + struct iwl_fw_error_dump_paging *paging; + struct page *pages = + fwrt->fw_paging_db[i].fw_paging_block; + dma_addr_t addr = fwrt->fw_paging_db[i].fw_paging_phys; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PAGING); + (*data)->len = cpu_to_le32(sizeof(*paging) + + PAGING_BLOCK_SIZE); + paging = (void *)(*data)->data; + paging->index = cpu_to_le32(i); + dma_sync_single_for_cpu(fwrt->trans->dev, addr, + PAGING_BLOCK_SIZE, + DMA_BIDIRECTIONAL); + memcpy(paging->data, page_address(pages), + PAGING_BLOCK_SIZE); + dma_sync_single_for_device(fwrt->trans->dev, addr, + PAGING_BLOCK_SIZE, + DMA_BIDIRECTIONAL); + (*data) = iwl_fw_error_next_data(*data); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, + fwrt->fw_paging_db[i].fw_offs, + paging->data, + PAGING_BLOCK_SIZE); + } +} + +static struct iwl_fw_error_dump_file * +iwl_fw_error_dump_file(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dump_ptrs *fw_error_dump, + struct iwl_fwrt_dump_data *data) +{ + struct iwl_fw_error_dump_file *dump_file; + struct iwl_fw_error_dump_data *dump_data; + struct iwl_fw_error_dump_info *dump_info; + struct iwl_fw_error_dump_smem_cfg *dump_smem_cfg; + struct iwl_fw_error_dump_trigger_desc *dump_trig; + u32 sram_len, sram_ofs; + const struct iwl_fw_dbg_mem_seg_tlv *fw_mem = fwrt->fw->dbg.mem_tlv; + struct iwl_fwrt_shared_mem_cfg *mem_cfg = &fwrt->smem_cfg; + u32 file_len, fifo_len = 0, prph_len = 0, radio_len = 0; + u32 smem_len = fwrt->fw->dbg.n_mem_tlv ? 0 : fwrt->trans->mac_cfg->base->smem_len; + u32 sram2_len = fwrt->fw->dbg.n_mem_tlv ? + 0 : fwrt->trans->cfg->dccm2_len; + int i; + + /* SRAM - include stack CCM if driver knows the values for it */ + if (!fwrt->trans->cfg->dccm_offset || + !fwrt->trans->cfg->dccm_len) { + const struct fw_img *img; + + if (fwrt->cur_fw_img >= IWL_UCODE_TYPE_MAX) + return NULL; + img = &fwrt->fw->img[fwrt->cur_fw_img]; + sram_ofs = img->sec[IWL_UCODE_SECTION_DATA].offset; + sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; + } else { + sram_ofs = fwrt->trans->cfg->dccm_offset; + sram_len = fwrt->trans->cfg->dccm_len; + } + + /* reading RXF/TXF sizes */ + if (iwl_trans_is_fw_error(fwrt->trans)) { + fifo_len = iwl_fw_rxf_len(fwrt, mem_cfg); + fifo_len += iwl_fw_txf_len(fwrt, mem_cfg); + + /* Make room for PRPH registers */ + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_PRPH)) + iwl_fw_prph_handler(fwrt, &prph_len, + iwl_fw_get_prph_len); + + if (fwrt->trans->mac_cfg->device_family == + IWL_DEVICE_FAMILY_7000 && + iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RADIO_REG)) + radio_len = sizeof(*dump_data) + RADIO_REG_MAX_READ; + } + + file_len = sizeof(*dump_file) + fifo_len + prph_len + radio_len; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) + file_len += sizeof(*dump_data) + sizeof(*dump_info); + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) + file_len += sizeof(*dump_data) + sizeof(*dump_smem_cfg); + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { + size_t hdr_len = sizeof(*dump_data) + + sizeof(struct iwl_fw_error_dump_mem); + + /* Dump SRAM only if no mem_tlvs */ + if (!fwrt->fw->dbg.n_mem_tlv) + ADD_LEN(file_len, sram_len, hdr_len); + + /* Make room for all mem types that exist */ + ADD_LEN(file_len, smem_len, hdr_len); + ADD_LEN(file_len, sram2_len, hdr_len); + + for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) + ADD_LEN(file_len, le32_to_cpu(fw_mem[i].len), hdr_len); + } + + /* Make room for fw's virtual image pages, if it exists */ + if (iwl_fw_dbg_is_paging_enabled(fwrt)) + file_len += fwrt->num_of_paging_blk * + (sizeof(*dump_data) + + sizeof(struct iwl_fw_error_dump_paging) + + PAGING_BLOCK_SIZE); + + if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { + file_len += sizeof(*dump_data) + + fwrt->trans->mac_cfg->base->d3_debug_data_length * 2; + } + + /* If we only want a monitor dump, reset the file length */ + if (data->monitor_only) { + file_len = sizeof(*dump_file) + sizeof(*dump_data) * 2 + + sizeof(*dump_info) + sizeof(*dump_smem_cfg); + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && + data->desc) + file_len += sizeof(*dump_data) + sizeof(*dump_trig) + + data->desc->len; + + dump_file = vzalloc(file_len); + if (!dump_file) + return NULL; + + fw_error_dump->fwrt_ptr = dump_file; + + dump_file->barker = cpu_to_le32(IWL_FW_ERROR_DUMP_BARKER); + dump_data = (void *)dump_file->data; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) { + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_DEV_FW_INFO); + dump_data->len = cpu_to_le32(sizeof(*dump_info)); + dump_info = (void *)dump_data->data; + dump_info->hw_type = + cpu_to_le32(CSR_HW_REV_TYPE(fwrt->trans->info.hw_rev)); + dump_info->hw_step = + cpu_to_le32(fwrt->trans->info.hw_rev_step); + memcpy(dump_info->fw_human_readable, fwrt->fw->human_readable, + sizeof(dump_info->fw_human_readable)); + strscpy_pad(dump_info->dev_human_readable, + fwrt->trans->info.name, + sizeof(dump_info->dev_human_readable)); + strscpy_pad(dump_info->bus_human_readable, fwrt->dev->bus->name, + sizeof(dump_info->bus_human_readable)); + dump_info->num_of_lmacs = fwrt->smem_cfg.num_lmacs; + dump_info->lmac_err_id[0] = + cpu_to_le32(fwrt->dump.lmac_err_id[0]); + if (fwrt->smem_cfg.num_lmacs > 1) + dump_info->lmac_err_id[1] = + cpu_to_le32(fwrt->dump.lmac_err_id[1]); + dump_info->umac_err_id = cpu_to_le32(fwrt->dump.umac_err_id); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) { + /* Dump shared memory configuration */ + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_CFG); + dump_data->len = cpu_to_le32(sizeof(*dump_smem_cfg)); + dump_smem_cfg = (void *)dump_data->data; + dump_smem_cfg->num_lmacs = cpu_to_le32(mem_cfg->num_lmacs); + dump_smem_cfg->num_txfifo_entries = + cpu_to_le32(mem_cfg->num_txfifo_entries); + for (i = 0; i < MAX_NUM_LMAC; i++) { + int j; + u32 *txf_size = mem_cfg->lmac[i].txfifo_size; + + for (j = 0; j < TX_FIFO_MAX_NUM; j++) + dump_smem_cfg->lmac[i].txfifo_size[j] = + cpu_to_le32(txf_size[j]); + dump_smem_cfg->lmac[i].rxfifo1_size = + cpu_to_le32(mem_cfg->lmac[i].rxfifo1_size); + } + dump_smem_cfg->rxfifo2_size = + cpu_to_le32(mem_cfg->rxfifo2_size); + dump_smem_cfg->internal_txfifo_addr = + cpu_to_le32(mem_cfg->internal_txfifo_addr); + for (i = 0; i < TX_FIFO_INTERNAL_MAX_NUM; i++) { + dump_smem_cfg->internal_txfifo_size[i] = + cpu_to_le32(mem_cfg->internal_txfifo_size[i]); + } + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* We only dump the FIFOs if the FW is in error state */ + if (fifo_len) { + iwl_fw_dump_rxf(fwrt, &dump_data); + iwl_fw_dump_txf(fwrt, &dump_data); + } + + if (radio_len) + iwl_read_radio_regs(fwrt, &dump_data); + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && + data->desc) { + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_ERROR_INFO); + dump_data->len = cpu_to_le32(sizeof(*dump_trig) + + data->desc->len); + dump_trig = (void *)dump_data->data; + memcpy(dump_trig, &data->desc->trig_desc, + sizeof(*dump_trig) + data->desc->len); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* In case we only want monitor dump, skip to dump transport data */ + if (data->monitor_only) + goto out; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { + const struct iwl_fw_dbg_mem_seg_tlv *fw_dbg_mem = + fwrt->fw->dbg.mem_tlv; + + if (!fwrt->fw->dbg.n_mem_tlv) + iwl_fw_dump_mem(fwrt, &dump_data, sram_len, sram_ofs, + IWL_FW_ERROR_DUMP_MEM_SRAM); + + for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) { + u32 len = le32_to_cpu(fw_dbg_mem[i].len); + u32 ofs = le32_to_cpu(fw_dbg_mem[i].ofs); + + iwl_fw_dump_mem(fwrt, &dump_data, len, ofs, + le32_to_cpu(fw_dbg_mem[i].data_type)); + } + + iwl_fw_dump_mem(fwrt, &dump_data, smem_len, + fwrt->trans->mac_cfg->base->smem_offset, + IWL_FW_ERROR_DUMP_MEM_SMEM); + + iwl_fw_dump_mem(fwrt, &dump_data, sram2_len, + fwrt->trans->cfg->dccm2_offset, + IWL_FW_ERROR_DUMP_MEM_SRAM); + } + + if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { + u32 addr = fwrt->trans->mac_cfg->base->d3_debug_data_base_addr; + size_t data_size = fwrt->trans->mac_cfg->base->d3_debug_data_length; + + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_D3_DEBUG_DATA); + dump_data->len = cpu_to_le32(data_size * 2); + + memcpy(dump_data->data, fwrt->dump.d3_debug_data, data_size); + + kfree(fwrt->dump.d3_debug_data); + fwrt->dump.d3_debug_data = NULL; + + iwl_trans_read_mem_bytes(fwrt->trans, addr, + dump_data->data + data_size, + data_size); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, addr, + dump_data->data + data_size, + data_size); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* Dump fw's virtual image */ + if (iwl_fw_dbg_is_paging_enabled(fwrt)) + iwl_dump_paging(fwrt, &dump_data); + + if (prph_len) + iwl_fw_prph_handler(fwrt, &dump_data, iwl_dump_prph); + +out: + dump_file->file_len = cpu_to_le32(file_len); + return dump_file; +} + +void iwl_fw_error_dump(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data) +{ + struct iwl_fw_dump_ptrs fw_error_dump = {}; + struct iwl_fw_error_dump_file *dump_file; + struct scatterlist *sg_dump_data; + u32 file_len; + u32 dump_mask = fwrt->fw->dbg.dump_mask; + + dump_file = iwl_fw_error_dump_file(fwrt, &fw_error_dump, dump_data); + if (!dump_file) + return; + + if (dump_data->monitor_only) + dump_mask &= BIT(IWL_FW_ERROR_DUMP_FW_MONITOR); + + fw_error_dump.trans_ptr = iwl_trans_dump_data(fwrt->trans, dump_mask, + fwrt->sanitize_ops, + fwrt->sanitize_ctx); + file_len = le32_to_cpu(dump_file->file_len); + fw_error_dump.fwrt_len = file_len; + + if (fw_error_dump.trans_ptr) { + file_len += fw_error_dump.trans_ptr->len; + dump_file->file_len = cpu_to_le32(file_len); + } + + sg_dump_data = iwl_fw_dbg_alloc_sgtable(file_len); + if (sg_dump_data) { + sg_pcopy_from_buffer(sg_dump_data, + sg_nents(sg_dump_data), + fw_error_dump.fwrt_ptr, + fw_error_dump.fwrt_len, 0); + if (fw_error_dump.trans_ptr) + sg_pcopy_from_buffer(sg_dump_data, + sg_nents(sg_dump_data), + fw_error_dump.trans_ptr->data, + fw_error_dump.trans_ptr->len, + fw_error_dump.fwrt_len); + dev_coredumpsg(fwrt->trans->dev, sg_dump_data, file_len, + GFP_KERNEL); + } + vfree(fw_error_dump.fwrt_ptr); + vfree(fw_error_dump.trans_ptr); +} diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c index 0cffa5493704..069c3bad6f29 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c @@ -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) 2015-2017 Intel Deutschland GmbH */ @@ -13,556 +13,13 @@ #include "iwl-prph.h" #include "iwl-csr.h" #include "iwl-fh.h" -/** - * struct iwl_fw_dump_ptrs - set of pointers needed for the fw-error-dump - * - * @fwrt_ptr: pointer to the buffer coming from fwrt - * @trans_ptr: pointer to struct %iwl_trans_dump_data which contains the - * transport's data. - * @fwrt_len: length of the valid data in fwrt_ptr - */ -struct iwl_fw_dump_ptrs { - struct iwl_trans_dump_data *trans_ptr; - void *fwrt_ptr; - u32 fwrt_len; -}; - -#define RADIO_REG_MAX_READ 0x2ad -static void iwl_read_radio_regs(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data) -{ - u8 *pos = (void *)(*dump_data)->data; - int i; - - IWL_DEBUG_INFO(fwrt, "WRT radio registers dump\n"); - - if (!iwl_trans_grab_nic_access(fwrt->trans)) - return; - - (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RADIO_REG); - (*dump_data)->len = cpu_to_le32(RADIO_REG_MAX_READ); - - for (i = 0; i < RADIO_REG_MAX_READ; i++) { - u32 rd_cmd = RADIO_RSP_RD_CMD; - - rd_cmd |= i << RADIO_RSP_ADDR_POS; - iwl_write_prph_no_grab(fwrt->trans, RSP_RADIO_CMD, rd_cmd); - *pos = (u8)iwl_read_prph_no_grab(fwrt->trans, RSP_RADIO_RDDAT); - - pos++; - } - - *dump_data = iwl_fw_error_next_data(*dump_data); - - iwl_trans_release_nic_access(fwrt->trans); -} - -static void iwl_fwrt_dump_rxf(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data, - int size, u32 offset, int fifo_num) -{ - struct iwl_fw_error_dump_fifo *fifo_hdr; - u32 *fifo_data; - u32 fifo_len; - int i; - - fifo_hdr = (void *)(*dump_data)->data; - fifo_data = (void *)fifo_hdr->data; - fifo_len = size; - - /* No need to try to read the data if the length is 0 */ - if (fifo_len == 0) - return; - - /* Add a TLV for the RXF */ - (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RXF); - (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); - - fifo_hdr->fifo_num = cpu_to_le32(fifo_num); - fifo_hdr->available_bytes = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_RD_D_SPACE + offset)); - fifo_hdr->wr_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_RD_WR_PTR + offset)); - fifo_hdr->rd_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_RD_RD_PTR + offset)); - fifo_hdr->fence_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_RD_FENCE_PTR + offset)); - fifo_hdr->fence_mode = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - RXF_SET_FENCE_MODE + offset)); - - /* Lock fence */ - iwl_trans_write_prph(fwrt->trans, RXF_SET_FENCE_MODE + offset, 0x1); - /* Set fence pointer to the same place like WR pointer */ - iwl_trans_write_prph(fwrt->trans, RXF_LD_WR2FENCE + offset, 0x1); - /* Set fence offset */ - iwl_trans_write_prph(fwrt->trans, - RXF_LD_FENCE_OFFSET_ADDR + offset, 0x0); - - /* Read FIFO */ - fifo_len /= sizeof(u32); /* Size in DWORDS */ - for (i = 0; i < fifo_len; i++) - fifo_data[i] = iwl_trans_read_prph(fwrt->trans, - RXF_FIFO_RD_FENCE_INC + - offset); - *dump_data = iwl_fw_error_next_data(*dump_data); -} - -static void iwl_fwrt_dump_txf(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data, - int size, u32 offset, int fifo_num) -{ - struct iwl_fw_error_dump_fifo *fifo_hdr; - u32 *fifo_data; - u32 fifo_len; - int i; - - fifo_hdr = (void *)(*dump_data)->data; - fifo_data = (void *)fifo_hdr->data; - fifo_len = size; - - /* No need to try to read the data if the length is 0 */ - if (fifo_len == 0) - return; - - /* Add a TLV for the FIFO */ - (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXF); - (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); - - fifo_hdr->fifo_num = cpu_to_le32(fifo_num); - fifo_hdr->available_bytes = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_FIFO_ITEM_CNT + offset)); - fifo_hdr->wr_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_WR_PTR + offset)); - fifo_hdr->rd_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_RD_PTR + offset)); - fifo_hdr->fence_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_FENCE_PTR + offset)); - fifo_hdr->fence_mode = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_LOCK_FENCE + offset)); - - /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ - iwl_trans_write_prph(fwrt->trans, TXF_READ_MODIFY_ADDR + offset, - TXF_WR_PTR + offset); - - /* Dummy-read to advance the read pointer to the head */ - iwl_trans_read_prph(fwrt->trans, TXF_READ_MODIFY_DATA + offset); - - /* Read FIFO */ - for (i = 0; i < fifo_len / sizeof(u32); i++) - fifo_data[i] = iwl_trans_read_prph(fwrt->trans, - TXF_READ_MODIFY_DATA + - offset); - - if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) - fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, - fifo_data, fifo_len); - - *dump_data = iwl_fw_error_next_data(*dump_data); -} - -static void iwl_fw_dump_rxf(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data) -{ - struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; - - IWL_DEBUG_INFO(fwrt, "WRT RX FIFO dump\n"); - - if (!iwl_trans_grab_nic_access(fwrt->trans)) - return; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) { - /* Pull RXF1 */ - iwl_fwrt_dump_rxf(fwrt, dump_data, - cfg->lmac[0].rxfifo1_size, 0, 0); - /* Pull RXF2 */ - iwl_fwrt_dump_rxf(fwrt, dump_data, cfg->rxfifo2_size, - RXF_DIFF_FROM_PREV + - fwrt->trans->mac_cfg->umac_prph_offset, 1); - /* Pull LMAC2 RXF1 */ - if (fwrt->smem_cfg.num_lmacs > 1) - iwl_fwrt_dump_rxf(fwrt, dump_data, - cfg->lmac[1].rxfifo1_size, - LMAC2_PRPH_OFFSET, 2); - } - - iwl_trans_release_nic_access(fwrt->trans); -} - -static void iwl_fw_dump_txf(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data) -{ - struct iwl_fw_error_dump_fifo *fifo_hdr; - struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; - u32 *fifo_data; - u32 fifo_len; - int i, j; - - IWL_DEBUG_INFO(fwrt, "WRT TX FIFO dump\n"); - - if (!iwl_trans_grab_nic_access(fwrt->trans)) - return; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) { - /* Pull TXF data from LMAC1 */ - for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; i++) { - /* Mark the number of TXF we're pulling now */ - iwl_trans_write_prph(fwrt->trans, TXF_LARC_NUM, i); - iwl_fwrt_dump_txf(fwrt, dump_data, - cfg->lmac[0].txfifo_size[i], 0, i); - } - - /* Pull TXF data from LMAC2 */ - if (fwrt->smem_cfg.num_lmacs > 1) { - for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; - i++) { - /* Mark the number of TXF we're pulling now */ - iwl_trans_write_prph(fwrt->trans, - TXF_LARC_NUM + - LMAC2_PRPH_OFFSET, i); - iwl_fwrt_dump_txf(fwrt, dump_data, - cfg->lmac[1].txfifo_size[i], - LMAC2_PRPH_OFFSET, - i + cfg->num_txfifo_entries); - } - } - } - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && - fw_has_capa(&fwrt->fw->ucode_capa, - IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) { - /* Pull UMAC internal TXF data from all TXFs */ - for (i = 0; - i < ARRAY_SIZE(fwrt->smem_cfg.internal_txfifo_size); - i++) { - fifo_hdr = (void *)(*dump_data)->data; - fifo_data = (void *)fifo_hdr->data; - fifo_len = fwrt->smem_cfg.internal_txfifo_size[i]; - - /* No need to try to read the data if the length is 0 */ - if (fifo_len == 0) - continue; - - /* Add a TLV for the internal FIFOs */ - (*dump_data)->type = - cpu_to_le32(IWL_FW_ERROR_DUMP_INTERNAL_TXF); - (*dump_data)->len = - cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); - - fifo_hdr->fifo_num = cpu_to_le32(i); - - /* Mark the number of TXF we're pulling now */ - iwl_trans_write_prph(fwrt->trans, TXF_CPU2_NUM, i + - fwrt->smem_cfg.num_txfifo_entries); - - fifo_hdr->available_bytes = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_FIFO_ITEM_CNT)); - fifo_hdr->wr_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_WR_PTR)); - fifo_hdr->rd_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_RD_PTR)); - fifo_hdr->fence_ptr = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_FENCE_PTR)); - fifo_hdr->fence_mode = - cpu_to_le32(iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_LOCK_FENCE)); - - /* Set TXF_CPU2_READ_MODIFY_ADDR to TXF_CPU2_WR_PTR */ - iwl_trans_write_prph(fwrt->trans, - TXF_CPU2_READ_MODIFY_ADDR, - TXF_CPU2_WR_PTR); - - /* Dummy-read to advance the read pointer to head */ - iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_READ_MODIFY_DATA); - - /* Read FIFO */ - fifo_len /= sizeof(u32); /* Size in DWORDS */ - for (j = 0; j < fifo_len; j++) - fifo_data[j] = - iwl_trans_read_prph(fwrt->trans, - TXF_CPU2_READ_MODIFY_DATA); - *dump_data = iwl_fw_error_next_data(*dump_data); - } - } - - iwl_trans_release_nic_access(fwrt->trans); -} - -struct iwl_prph_range { - u32 start, end; -}; - -static const struct iwl_prph_range iwl_prph_dump_addr_comm[] = { - { .start = 0x00a00000, .end = 0x00a00000 }, - { .start = 0x00a0000c, .end = 0x00a00024 }, - { .start = 0x00a0002c, .end = 0x00a0003c }, - { .start = 0x00a00410, .end = 0x00a00418 }, - { .start = 0x00a00420, .end = 0x00a00420 }, - { .start = 0x00a00428, .end = 0x00a00428 }, - { .start = 0x00a00430, .end = 0x00a0043c }, - { .start = 0x00a00444, .end = 0x00a00444 }, - { .start = 0x00a004c0, .end = 0x00a004cc }, - { .start = 0x00a004d8, .end = 0x00a004d8 }, - { .start = 0x00a004e0, .end = 0x00a004f0 }, - { .start = 0x00a00840, .end = 0x00a00840 }, - { .start = 0x00a00850, .end = 0x00a00858 }, - { .start = 0x00a01004, .end = 0x00a01008 }, - { .start = 0x00a01010, .end = 0x00a01010 }, - { .start = 0x00a01018, .end = 0x00a01018 }, - { .start = 0x00a01024, .end = 0x00a01024 }, - { .start = 0x00a0102c, .end = 0x00a01034 }, - { .start = 0x00a0103c, .end = 0x00a01040 }, - { .start = 0x00a01048, .end = 0x00a01094 }, - { .start = 0x00a01c00, .end = 0x00a01c20 }, - { .start = 0x00a01c58, .end = 0x00a01c58 }, - { .start = 0x00a01c7c, .end = 0x00a01c7c }, - { .start = 0x00a01c28, .end = 0x00a01c54 }, - { .start = 0x00a01c5c, .end = 0x00a01c5c }, - { .start = 0x00a01c60, .end = 0x00a01cdc }, - { .start = 0x00a01ce0, .end = 0x00a01d0c }, - { .start = 0x00a01d18, .end = 0x00a01d20 }, - { .start = 0x00a01d2c, .end = 0x00a01d30 }, - { .start = 0x00a01d40, .end = 0x00a01d5c }, - { .start = 0x00a01d80, .end = 0x00a01d80 }, - { .start = 0x00a01d98, .end = 0x00a01d9c }, - { .start = 0x00a01da8, .end = 0x00a01da8 }, - { .start = 0x00a01db8, .end = 0x00a01df4 }, - { .start = 0x00a01dc0, .end = 0x00a01dfc }, - { .start = 0x00a01e00, .end = 0x00a01e2c }, - { .start = 0x00a01e40, .end = 0x00a01e60 }, - { .start = 0x00a01e68, .end = 0x00a01e6c }, - { .start = 0x00a01e74, .end = 0x00a01e74 }, - { .start = 0x00a01e84, .end = 0x00a01e90 }, - { .start = 0x00a01e9c, .end = 0x00a01ec4 }, - { .start = 0x00a01ed0, .end = 0x00a01ee0 }, - { .start = 0x00a01f00, .end = 0x00a01f1c }, - { .start = 0x00a01f44, .end = 0x00a01ffc }, - { .start = 0x00a02000, .end = 0x00a02048 }, - { .start = 0x00a02068, .end = 0x00a020f0 }, - { .start = 0x00a02100, .end = 0x00a02118 }, - { .start = 0x00a02140, .end = 0x00a0214c }, - { .start = 0x00a02168, .end = 0x00a0218c }, - { .start = 0x00a021c0, .end = 0x00a021c0 }, - { .start = 0x00a02400, .end = 0x00a02410 }, - { .start = 0x00a02418, .end = 0x00a02420 }, - { .start = 0x00a02428, .end = 0x00a0242c }, - { .start = 0x00a02434, .end = 0x00a02434 }, - { .start = 0x00a02440, .end = 0x00a02460 }, - { .start = 0x00a02468, .end = 0x00a024b0 }, - { .start = 0x00a024c8, .end = 0x00a024cc }, - { .start = 0x00a02500, .end = 0x00a02504 }, - { .start = 0x00a0250c, .end = 0x00a02510 }, - { .start = 0x00a02540, .end = 0x00a02554 }, - { .start = 0x00a02580, .end = 0x00a025f4 }, - { .start = 0x00a02600, .end = 0x00a0260c }, - { .start = 0x00a02648, .end = 0x00a02650 }, - { .start = 0x00a02680, .end = 0x00a02680 }, - { .start = 0x00a026c0, .end = 0x00a026d0 }, - { .start = 0x00a02700, .end = 0x00a0270c }, - { .start = 0x00a02804, .end = 0x00a02804 }, - { .start = 0x00a02818, .end = 0x00a0281c }, - { .start = 0x00a02c00, .end = 0x00a02db4 }, - { .start = 0x00a02df4, .end = 0x00a02fb0 }, - { .start = 0x00a03000, .end = 0x00a03014 }, - { .start = 0x00a0301c, .end = 0x00a0302c }, - { .start = 0x00a03034, .end = 0x00a03038 }, - { .start = 0x00a03040, .end = 0x00a03048 }, - { .start = 0x00a03060, .end = 0x00a03068 }, - { .start = 0x00a03070, .end = 0x00a03074 }, - { .start = 0x00a0307c, .end = 0x00a0307c }, - { .start = 0x00a03080, .end = 0x00a03084 }, - { .start = 0x00a0308c, .end = 0x00a03090 }, - { .start = 0x00a03098, .end = 0x00a03098 }, - { .start = 0x00a030a0, .end = 0x00a030a0 }, - { .start = 0x00a030a8, .end = 0x00a030b4 }, - { .start = 0x00a030bc, .end = 0x00a030bc }, - { .start = 0x00a030c0, .end = 0x00a0312c }, - { .start = 0x00a03c00, .end = 0x00a03c5c }, - { .start = 0x00a04400, .end = 0x00a04454 }, - { .start = 0x00a04460, .end = 0x00a04474 }, - { .start = 0x00a044c0, .end = 0x00a044ec }, - { .start = 0x00a04500, .end = 0x00a04504 }, - { .start = 0x00a04510, .end = 0x00a04538 }, - { .start = 0x00a04540, .end = 0x00a04548 }, - { .start = 0x00a04560, .end = 0x00a0457c }, - { .start = 0x00a04590, .end = 0x00a04598 }, - { .start = 0x00a045c0, .end = 0x00a045f4 }, -}; - -static const struct iwl_prph_range iwl_prph_dump_addr_9000[] = { - { .start = 0x00a05c00, .end = 0x00a05c18 }, - { .start = 0x00a05400, .end = 0x00a056e8 }, - { .start = 0x00a08000, .end = 0x00a098bc }, - { .start = 0x00a02400, .end = 0x00a02758 }, - { .start = 0x00a04764, .end = 0x00a0476c }, - { .start = 0x00a04770, .end = 0x00a04774 }, - { .start = 0x00a04620, .end = 0x00a04624 }, -}; - -static const struct iwl_prph_range iwl_prph_dump_addr_22000[] = { - { .start = 0x00a00000, .end = 0x00a00000 }, - { .start = 0x00a0000c, .end = 0x00a00024 }, - { .start = 0x00a0002c, .end = 0x00a00034 }, - { .start = 0x00a0003c, .end = 0x00a0003c }, - { .start = 0x00a00410, .end = 0x00a00418 }, - { .start = 0x00a00420, .end = 0x00a00420 }, - { .start = 0x00a00428, .end = 0x00a00428 }, - { .start = 0x00a00430, .end = 0x00a0043c }, - { .start = 0x00a00444, .end = 0x00a00444 }, - { .start = 0x00a00840, .end = 0x00a00840 }, - { .start = 0x00a00850, .end = 0x00a00858 }, - { .start = 0x00a01004, .end = 0x00a01008 }, - { .start = 0x00a01010, .end = 0x00a01010 }, - { .start = 0x00a01018, .end = 0x00a01018 }, - { .start = 0x00a01024, .end = 0x00a01024 }, - { .start = 0x00a0102c, .end = 0x00a01034 }, - { .start = 0x00a0103c, .end = 0x00a01040 }, - { .start = 0x00a01048, .end = 0x00a01050 }, - { .start = 0x00a01058, .end = 0x00a01058 }, - { .start = 0x00a01060, .end = 0x00a01070 }, - { .start = 0x00a0108c, .end = 0x00a0108c }, - { .start = 0x00a01c20, .end = 0x00a01c28 }, - { .start = 0x00a01d10, .end = 0x00a01d10 }, - { .start = 0x00a01e28, .end = 0x00a01e2c }, - { .start = 0x00a01e60, .end = 0x00a01e60 }, - { .start = 0x00a01e80, .end = 0x00a01e80 }, - { .start = 0x00a01ea0, .end = 0x00a01ea0 }, - { .start = 0x00a02000, .end = 0x00a0201c }, - { .start = 0x00a02024, .end = 0x00a02024 }, - { .start = 0x00a02040, .end = 0x00a02048 }, - { .start = 0x00a020c0, .end = 0x00a020e0 }, - { .start = 0x00a02400, .end = 0x00a02404 }, - { .start = 0x00a0240c, .end = 0x00a02414 }, - { .start = 0x00a0241c, .end = 0x00a0243c }, - { .start = 0x00a02448, .end = 0x00a024bc }, - { .start = 0x00a024c4, .end = 0x00a024cc }, - { .start = 0x00a02508, .end = 0x00a02508 }, - { .start = 0x00a02510, .end = 0x00a02514 }, - { .start = 0x00a0251c, .end = 0x00a0251c }, - { .start = 0x00a0252c, .end = 0x00a0255c }, - { .start = 0x00a02564, .end = 0x00a025a0 }, - { .start = 0x00a025a8, .end = 0x00a025b4 }, - { .start = 0x00a025c0, .end = 0x00a025c0 }, - { .start = 0x00a025e8, .end = 0x00a025f4 }, - { .start = 0x00a02c08, .end = 0x00a02c18 }, - { .start = 0x00a02c2c, .end = 0x00a02c38 }, - { .start = 0x00a02c68, .end = 0x00a02c78 }, - { .start = 0x00a03000, .end = 0x00a03000 }, - { .start = 0x00a03010, .end = 0x00a03014 }, - { .start = 0x00a0301c, .end = 0x00a0302c }, - { .start = 0x00a03034, .end = 0x00a03038 }, - { .start = 0x00a03040, .end = 0x00a03044 }, - { .start = 0x00a03060, .end = 0x00a03068 }, - { .start = 0x00a03070, .end = 0x00a03070 }, - { .start = 0x00a0307c, .end = 0x00a03084 }, - { .start = 0x00a0308c, .end = 0x00a03090 }, - { .start = 0x00a03098, .end = 0x00a03098 }, - { .start = 0x00a030a0, .end = 0x00a030a0 }, - { .start = 0x00a030a8, .end = 0x00a030b4 }, - { .start = 0x00a030bc, .end = 0x00a030c0 }, - { .start = 0x00a030c8, .end = 0x00a030f4 }, - { .start = 0x00a03100, .end = 0x00a0312c }, - { .start = 0x00a03c00, .end = 0x00a03c5c }, - { .start = 0x00a04400, .end = 0x00a04454 }, - { .start = 0x00a04460, .end = 0x00a04474 }, - { .start = 0x00a044c0, .end = 0x00a044ec }, - { .start = 0x00a04500, .end = 0x00a04504 }, - { .start = 0x00a04510, .end = 0x00a04538 }, - { .start = 0x00a04540, .end = 0x00a04548 }, - { .start = 0x00a04560, .end = 0x00a04560 }, - { .start = 0x00a04570, .end = 0x00a0457c }, - { .start = 0x00a04590, .end = 0x00a04590 }, - { .start = 0x00a04598, .end = 0x00a04598 }, - { .start = 0x00a045c0, .end = 0x00a045f4 }, - { .start = 0x00a05c18, .end = 0x00a05c1c }, - { .start = 0x00a0c000, .end = 0x00a0c018 }, - { .start = 0x00a0c020, .end = 0x00a0c028 }, - { .start = 0x00a0c038, .end = 0x00a0c094 }, - { .start = 0x00a0c0c0, .end = 0x00a0c104 }, - { .start = 0x00a0c10c, .end = 0x00a0c118 }, - { .start = 0x00a0c150, .end = 0x00a0c174 }, - { .start = 0x00a0c17c, .end = 0x00a0c188 }, - { .start = 0x00a0c190, .end = 0x00a0c198 }, - { .start = 0x00a0c1a0, .end = 0x00a0c1a8 }, - { .start = 0x00a0c1b0, .end = 0x00a0c1b8 }, -}; - -static const struct iwl_prph_range iwl_prph_dump_addr_ax210[] = { - { .start = 0x00d03c00, .end = 0x00d03c64 }, - { .start = 0x00d05c18, .end = 0x00d05c1c }, - { .start = 0x00d0c000, .end = 0x00d0c174 }, -}; - -static void iwl_read_prph_block(struct iwl_trans *trans, u32 start, - u32 len_bytes, __le32 *data) -{ - u32 i; - - for (i = 0; i < len_bytes; i += 4) - *data++ = cpu_to_le32(iwl_read_prph_no_grab(trans, start + i)); -} - -static void iwl_dump_prph(struct iwl_fw_runtime *fwrt, - const struct iwl_prph_range *iwl_prph_dump_addr, - u32 range_len, void *ptr) -{ - struct iwl_fw_error_dump_prph *prph; - struct iwl_trans *trans = fwrt->trans; - struct iwl_fw_error_dump_data **data = - (struct iwl_fw_error_dump_data **)ptr; - u32 i; - - if (!data) - return; - - IWL_DEBUG_INFO(trans, "WRT PRPH dump\n"); - - if (!iwl_trans_grab_nic_access(trans)) - return; - - for (i = 0; i < range_len; i++) { - /* The range includes both boundaries */ - int num_bytes_in_chunk = iwl_prph_dump_addr[i].end - - iwl_prph_dump_addr[i].start + 4; - - (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PRPH); - (*data)->len = cpu_to_le32(sizeof(*prph) + - num_bytes_in_chunk); - prph = (void *)(*data)->data; - prph->prph_start = cpu_to_le32(iwl_prph_dump_addr[i].start); - - iwl_read_prph_block(trans, iwl_prph_dump_addr[i].start, - /* our range is inclusive, hence + 4 */ - iwl_prph_dump_addr[i].end - - iwl_prph_dump_addr[i].start + 4, - (void *)prph->data); - - *data = iwl_fw_error_next_data(*data); - } - - iwl_trans_release_nic_access(trans); -} /* - * alloc_sgtable - allocates (chained) scatterlist in the given size, + * iwl_fw_dbg_alloc_sgtable - allocates (chained) scatterlist in the given size, * fills it with pages and returns it * @size: the size (in bytes) of the table */ -static struct scatterlist *alloc_sgtable(ssize_t size) +struct scatterlist *iwl_fw_dbg_alloc_sgtable(ssize_t size) { struct scatterlist *result = NULL, *prev; int nents, i, n_prev; @@ -625,423 +82,6 @@ static struct scatterlist *alloc_sgtable(ssize_t size) return result; } -static void iwl_fw_get_prph_len(struct iwl_fw_runtime *fwrt, - const struct iwl_prph_range *iwl_prph_dump_addr, - u32 range_len, void *ptr) -{ - u32 *prph_len = (u32 *)ptr; - int i, num_bytes_in_chunk; - - if (!prph_len) - return; - - for (i = 0; i < range_len; i++) { - /* The range includes both boundaries */ - num_bytes_in_chunk = - iwl_prph_dump_addr[i].end - - iwl_prph_dump_addr[i].start + 4; - - *prph_len += sizeof(struct iwl_fw_error_dump_data) + - sizeof(struct iwl_fw_error_dump_prph) + - num_bytes_in_chunk; - } -} - -static void iwl_fw_prph_handler(struct iwl_fw_runtime *fwrt, void *ptr, - void (*handler)(struct iwl_fw_runtime *, - const struct iwl_prph_range *, - u32, void *)) -{ - u32 range_len; - - if (fwrt->trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { - range_len = ARRAY_SIZE(iwl_prph_dump_addr_ax210); - handler(fwrt, iwl_prph_dump_addr_ax210, range_len, ptr); - } else if (fwrt->trans->mac_cfg->device_family >= - IWL_DEVICE_FAMILY_22000) { - range_len = ARRAY_SIZE(iwl_prph_dump_addr_22000); - handler(fwrt, iwl_prph_dump_addr_22000, range_len, ptr); - } else { - range_len = ARRAY_SIZE(iwl_prph_dump_addr_comm); - handler(fwrt, iwl_prph_dump_addr_comm, range_len, ptr); - - if (fwrt->trans->mac_cfg->mq_rx_supported) { - range_len = ARRAY_SIZE(iwl_prph_dump_addr_9000); - handler(fwrt, iwl_prph_dump_addr_9000, range_len, ptr); - } - } -} - -static void iwl_fw_dump_mem(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **dump_data, - u32 len, u32 ofs, u32 type) -{ - struct iwl_fw_error_dump_mem *dump_mem; - - if (!len) - return; - - (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM); - (*dump_data)->len = cpu_to_le32(len + sizeof(*dump_mem)); - dump_mem = (void *)(*dump_data)->data; - dump_mem->type = cpu_to_le32(type); - dump_mem->offset = cpu_to_le32(ofs); - iwl_trans_read_mem_bytes(fwrt->trans, ofs, dump_mem->data, len); - *dump_data = iwl_fw_error_next_data(*dump_data); - - if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) - fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, ofs, - dump_mem->data, len); - - IWL_DEBUG_INFO(fwrt, "WRT memory dump. Type=%u\n", dump_mem->type); -} - -#define ADD_LEN(len, item_len, const_len) \ - do {size_t item = item_len; len += (!!item) * const_len + item; } \ - while (0) - -static int iwl_fw_rxf_len(struct iwl_fw_runtime *fwrt, - struct iwl_fwrt_shared_mem_cfg *mem_cfg) -{ - size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + - sizeof(struct iwl_fw_error_dump_fifo); - u32 fifo_len = 0; - int i; - - if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) - return 0; - - /* Count RXF2 size */ - ADD_LEN(fifo_len, mem_cfg->rxfifo2_size, hdr_len); - - /* Count RXF1 sizes */ - if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) - mem_cfg->num_lmacs = MAX_NUM_LMAC; - - for (i = 0; i < mem_cfg->num_lmacs; i++) - ADD_LEN(fifo_len, mem_cfg->lmac[i].rxfifo1_size, hdr_len); - - return fifo_len; -} - -static int iwl_fw_txf_len(struct iwl_fw_runtime *fwrt, - struct iwl_fwrt_shared_mem_cfg *mem_cfg) -{ - size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + - sizeof(struct iwl_fw_error_dump_fifo); - u32 fifo_len = 0; - int i; - - if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) - goto dump_internal_txf; - - /* Count TXF sizes */ - if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) - mem_cfg->num_lmacs = MAX_NUM_LMAC; - - for (i = 0; i < mem_cfg->num_lmacs; i++) { - int j; - - for (j = 0; j < mem_cfg->num_txfifo_entries; j++) - ADD_LEN(fifo_len, mem_cfg->lmac[i].txfifo_size[j], - hdr_len); - } - -dump_internal_txf: - if (!(iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && - fw_has_capa(&fwrt->fw->ucode_capa, - IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG))) - goto out; - - for (i = 0; i < ARRAY_SIZE(mem_cfg->internal_txfifo_size); i++) - ADD_LEN(fifo_len, mem_cfg->internal_txfifo_size[i], hdr_len); - -out: - return fifo_len; -} - -static void iwl_dump_paging(struct iwl_fw_runtime *fwrt, - struct iwl_fw_error_dump_data **data) -{ - int i; - - IWL_DEBUG_INFO(fwrt, "WRT paging dump\n"); - for (i = 1; i < fwrt->num_of_paging_blk + 1; i++) { - struct iwl_fw_error_dump_paging *paging; - struct page *pages = - fwrt->fw_paging_db[i].fw_paging_block; - dma_addr_t addr = fwrt->fw_paging_db[i].fw_paging_phys; - - (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PAGING); - (*data)->len = cpu_to_le32(sizeof(*paging) + - PAGING_BLOCK_SIZE); - paging = (void *)(*data)->data; - paging->index = cpu_to_le32(i); - dma_sync_single_for_cpu(fwrt->trans->dev, addr, - PAGING_BLOCK_SIZE, - DMA_BIDIRECTIONAL); - memcpy(paging->data, page_address(pages), - PAGING_BLOCK_SIZE); - dma_sync_single_for_device(fwrt->trans->dev, addr, - PAGING_BLOCK_SIZE, - DMA_BIDIRECTIONAL); - (*data) = iwl_fw_error_next_data(*data); - - if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) - fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, - fwrt->fw_paging_db[i].fw_offs, - paging->data, - PAGING_BLOCK_SIZE); - } -} - -static struct iwl_fw_error_dump_file * -iwl_fw_error_dump_file(struct iwl_fw_runtime *fwrt, - struct iwl_fw_dump_ptrs *fw_error_dump, - struct iwl_fwrt_dump_data *data) -{ - struct iwl_fw_error_dump_file *dump_file; - struct iwl_fw_error_dump_data *dump_data; - struct iwl_fw_error_dump_info *dump_info; - struct iwl_fw_error_dump_smem_cfg *dump_smem_cfg; - struct iwl_fw_error_dump_trigger_desc *dump_trig; - u32 sram_len, sram_ofs; - const struct iwl_fw_dbg_mem_seg_tlv *fw_mem = fwrt->fw->dbg.mem_tlv; - struct iwl_fwrt_shared_mem_cfg *mem_cfg = &fwrt->smem_cfg; - u32 file_len, fifo_len = 0, prph_len = 0, radio_len = 0; - u32 smem_len = fwrt->fw->dbg.n_mem_tlv ? 0 : fwrt->trans->mac_cfg->base->smem_len; - u32 sram2_len = fwrt->fw->dbg.n_mem_tlv ? - 0 : fwrt->trans->cfg->dccm2_len; - int i; - - /* SRAM - include stack CCM if driver knows the values for it */ - if (!fwrt->trans->cfg->dccm_offset || - !fwrt->trans->cfg->dccm_len) { - const struct fw_img *img; - - if (fwrt->cur_fw_img >= IWL_UCODE_TYPE_MAX) - return NULL; - img = &fwrt->fw->img[fwrt->cur_fw_img]; - sram_ofs = img->sec[IWL_UCODE_SECTION_DATA].offset; - sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; - } else { - sram_ofs = fwrt->trans->cfg->dccm_offset; - sram_len = fwrt->trans->cfg->dccm_len; - } - - /* reading RXF/TXF sizes */ - if (iwl_trans_is_fw_error(fwrt->trans)) { - fifo_len = iwl_fw_rxf_len(fwrt, mem_cfg); - fifo_len += iwl_fw_txf_len(fwrt, mem_cfg); - - /* Make room for PRPH registers */ - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_PRPH)) - iwl_fw_prph_handler(fwrt, &prph_len, - iwl_fw_get_prph_len); - - if (fwrt->trans->mac_cfg->device_family == - IWL_DEVICE_FAMILY_7000 && - iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RADIO_REG)) - radio_len = sizeof(*dump_data) + RADIO_REG_MAX_READ; - } - - file_len = sizeof(*dump_file) + fifo_len + prph_len + radio_len; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) - file_len += sizeof(*dump_data) + sizeof(*dump_info); - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) - file_len += sizeof(*dump_data) + sizeof(*dump_smem_cfg); - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { - size_t hdr_len = sizeof(*dump_data) + - sizeof(struct iwl_fw_error_dump_mem); - - /* Dump SRAM only if no mem_tlvs */ - if (!fwrt->fw->dbg.n_mem_tlv) - ADD_LEN(file_len, sram_len, hdr_len); - - /* Make room for all mem types that exist */ - ADD_LEN(file_len, smem_len, hdr_len); - ADD_LEN(file_len, sram2_len, hdr_len); - - for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) - ADD_LEN(file_len, le32_to_cpu(fw_mem[i].len), hdr_len); - } - - /* Make room for fw's virtual image pages, if it exists */ - if (iwl_fw_dbg_is_paging_enabled(fwrt)) - file_len += fwrt->num_of_paging_blk * - (sizeof(*dump_data) + - sizeof(struct iwl_fw_error_dump_paging) + - PAGING_BLOCK_SIZE); - - if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { - file_len += sizeof(*dump_data) + - fwrt->trans->mac_cfg->base->d3_debug_data_length * 2; - } - - /* If we only want a monitor dump, reset the file length */ - if (data->monitor_only) { - file_len = sizeof(*dump_file) + sizeof(*dump_data) * 2 + - sizeof(*dump_info) + sizeof(*dump_smem_cfg); - } - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && - data->desc) - file_len += sizeof(*dump_data) + sizeof(*dump_trig) + - data->desc->len; - - dump_file = vzalloc(file_len); - if (!dump_file) - return NULL; - - fw_error_dump->fwrt_ptr = dump_file; - - dump_file->barker = cpu_to_le32(IWL_FW_ERROR_DUMP_BARKER); - dump_data = (void *)dump_file->data; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) { - dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_DEV_FW_INFO); - dump_data->len = cpu_to_le32(sizeof(*dump_info)); - dump_info = (void *)dump_data->data; - dump_info->hw_type = - cpu_to_le32(CSR_HW_REV_TYPE(fwrt->trans->info.hw_rev)); - dump_info->hw_step = - cpu_to_le32(fwrt->trans->info.hw_rev_step); - memcpy(dump_info->fw_human_readable, fwrt->fw->human_readable, - sizeof(dump_info->fw_human_readable)); - strscpy_pad(dump_info->dev_human_readable, - fwrt->trans->info.name, - sizeof(dump_info->dev_human_readable)); - strscpy_pad(dump_info->bus_human_readable, fwrt->dev->bus->name, - sizeof(dump_info->bus_human_readable)); - dump_info->num_of_lmacs = fwrt->smem_cfg.num_lmacs; - dump_info->lmac_err_id[0] = - cpu_to_le32(fwrt->dump.lmac_err_id[0]); - if (fwrt->smem_cfg.num_lmacs > 1) - dump_info->lmac_err_id[1] = - cpu_to_le32(fwrt->dump.lmac_err_id[1]); - dump_info->umac_err_id = cpu_to_le32(fwrt->dump.umac_err_id); - - dump_data = iwl_fw_error_next_data(dump_data); - } - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) { - /* Dump shared memory configuration */ - dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_CFG); - dump_data->len = cpu_to_le32(sizeof(*dump_smem_cfg)); - dump_smem_cfg = (void *)dump_data->data; - dump_smem_cfg->num_lmacs = cpu_to_le32(mem_cfg->num_lmacs); - dump_smem_cfg->num_txfifo_entries = - cpu_to_le32(mem_cfg->num_txfifo_entries); - for (i = 0; i < MAX_NUM_LMAC; i++) { - int j; - u32 *txf_size = mem_cfg->lmac[i].txfifo_size; - - for (j = 0; j < TX_FIFO_MAX_NUM; j++) - dump_smem_cfg->lmac[i].txfifo_size[j] = - cpu_to_le32(txf_size[j]); - dump_smem_cfg->lmac[i].rxfifo1_size = - cpu_to_le32(mem_cfg->lmac[i].rxfifo1_size); - } - dump_smem_cfg->rxfifo2_size = - cpu_to_le32(mem_cfg->rxfifo2_size); - dump_smem_cfg->internal_txfifo_addr = - cpu_to_le32(mem_cfg->internal_txfifo_addr); - for (i = 0; i < TX_FIFO_INTERNAL_MAX_NUM; i++) { - dump_smem_cfg->internal_txfifo_size[i] = - cpu_to_le32(mem_cfg->internal_txfifo_size[i]); - } - - dump_data = iwl_fw_error_next_data(dump_data); - } - - /* We only dump the FIFOs if the FW is in error state */ - if (fifo_len) { - iwl_fw_dump_rxf(fwrt, &dump_data); - iwl_fw_dump_txf(fwrt, &dump_data); - } - - if (radio_len) - iwl_read_radio_regs(fwrt, &dump_data); - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && - data->desc) { - dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_ERROR_INFO); - dump_data->len = cpu_to_le32(sizeof(*dump_trig) + - data->desc->len); - dump_trig = (void *)dump_data->data; - memcpy(dump_trig, &data->desc->trig_desc, - sizeof(*dump_trig) + data->desc->len); - - dump_data = iwl_fw_error_next_data(dump_data); - } - - /* In case we only want monitor dump, skip to dump trasport data */ - if (data->monitor_only) - goto out; - - if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { - const struct iwl_fw_dbg_mem_seg_tlv *fw_dbg_mem = - fwrt->fw->dbg.mem_tlv; - - if (!fwrt->fw->dbg.n_mem_tlv) - iwl_fw_dump_mem(fwrt, &dump_data, sram_len, sram_ofs, - IWL_FW_ERROR_DUMP_MEM_SRAM); - - for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) { - u32 len = le32_to_cpu(fw_dbg_mem[i].len); - u32 ofs = le32_to_cpu(fw_dbg_mem[i].ofs); - - iwl_fw_dump_mem(fwrt, &dump_data, len, ofs, - le32_to_cpu(fw_dbg_mem[i].data_type)); - } - - iwl_fw_dump_mem(fwrt, &dump_data, smem_len, - fwrt->trans->mac_cfg->base->smem_offset, - IWL_FW_ERROR_DUMP_MEM_SMEM); - - iwl_fw_dump_mem(fwrt, &dump_data, sram2_len, - fwrt->trans->cfg->dccm2_offset, - IWL_FW_ERROR_DUMP_MEM_SRAM); - } - - if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { - u32 addr = fwrt->trans->mac_cfg->base->d3_debug_data_base_addr; - size_t data_size = fwrt->trans->mac_cfg->base->d3_debug_data_length; - - dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_D3_DEBUG_DATA); - dump_data->len = cpu_to_le32(data_size * 2); - - memcpy(dump_data->data, fwrt->dump.d3_debug_data, data_size); - - kfree(fwrt->dump.d3_debug_data); - fwrt->dump.d3_debug_data = NULL; - - iwl_trans_read_mem_bytes(fwrt->trans, addr, - dump_data->data + data_size, - data_size); - - if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) - fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, addr, - dump_data->data + data_size, - data_size); - - dump_data = iwl_fw_error_next_data(dump_data); - } - - /* Dump fw's virtual image */ - if (iwl_fw_dbg_is_paging_enabled(fwrt)) - iwl_dump_paging(fwrt, &dump_data); - - if (prph_len) - iwl_fw_prph_handler(fwrt, &dump_data, iwl_dump_prph); - -out: - dump_file->file_len = cpu_to_le32(file_len); - return dump_file; -} - /** * struct iwl_dump_ini_region_data - region data * @reg_tlv: region TLV @@ -1071,7 +111,7 @@ static int iwl_dump_ini_prph_mac_iter_common(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_prph_mac_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; u32 addr = le32_to_cpu(reg->addrs[idx]) + @@ -1084,7 +124,7 @@ iwl_dump_ini_prph_mac_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_prph_mac_block_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_addr_size *pairs = (void *)reg->addrs; @@ -1132,16 +172,16 @@ static int iwl_dump_ini_prph_phy_iter_common(struct iwl_fw_runtime *fwrt, continue; } - iwl_write_prph_no_grab(fwrt->trans, indirect_wr_addr, - WMAL_INDRCT_CMD(addr + i)); + iwl_trans_write_prph(fwrt->trans, indirect_wr_addr, + WMAL_INDRCT_CMD(addr + i)); if (fwrt->trans->info.hw_rf_id != IWL_CFG_RF_TYPE_JF1 && fwrt->trans->info.hw_rf_id != IWL_CFG_RF_TYPE_JF2 && fwrt->trans->info.hw_rf_id != IWL_CFG_RF_TYPE_HR1 && fwrt->trans->info.hw_rf_id != IWL_CFG_RF_TYPE_HR2) { udelay(2); - prph_stts = iwl_read_prph_no_grab(fwrt->trans, - WMAL_MRSPF_STTS); + prph_stts = iwl_trans_read_prph(fwrt->trans, + WMAL_MRSPF_STTS); /* Abort dump if status is 0xA5A5A5A2 or FIFO1 empty */ if (prph_stts == WMAL_TIMEOUT_VAL || @@ -1149,8 +189,8 @@ static int iwl_dump_ini_prph_phy_iter_common(struct iwl_fw_runtime *fwrt, break; } - prph_val = iwl_read_prph_no_grab(fwrt->trans, - indirect_rd_addr); + prph_val = iwl_trans_read_prph(fwrt->trans, + indirect_rd_addr); *val++ = cpu_to_le32(prph_val); } @@ -1161,7 +201,7 @@ static int iwl_dump_ini_prph_phy_iter_common(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_prph_phy_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; u32 addr = le32_to_cpu(reg->addrs[idx]); @@ -1174,7 +214,7 @@ iwl_dump_ini_prph_phy_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_prph_phy_block_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_addr_size *pairs = (void *)reg->addrs; @@ -1187,7 +227,7 @@ iwl_dump_ini_prph_phy_block_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_csr_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1206,7 +246,7 @@ static int iwl_dump_ini_csr_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_config_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_trans *trans = fwrt->trans; struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; @@ -1234,7 +274,7 @@ static int iwl_dump_ini_config_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_dev_mem_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1256,7 +296,7 @@ static int iwl_dump_ini_dev_mem_iter(struct iwl_fw_runtime *fwrt, } static int _iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct page *page = fwrt->fw_paging_db[idx].fw_paging_block; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1276,7 +316,7 @@ static int _iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_error_dump_range *range; u32 page_size; @@ -1285,7 +325,7 @@ static int iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, idx++; if (!fwrt->trans->mac_cfg->gen2) - return _iwl_dump_ini_paging_iter(fwrt, range_ptr, range_len, idx); + return _iwl_dump_ini_paging_iter(fwrt, range_ptr, idx); range = range_ptr; page_size = fwrt->trans->init_dram.paging[idx].size; @@ -1301,7 +341,7 @@ static int iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_mon_dram_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1320,7 +360,7 @@ iwl_dump_ini_mon_dram_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_mon_smem_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1388,7 +428,7 @@ static bool iwl_ini_txf_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1410,7 +450,7 @@ static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, range->fifo_hdr.num_of_registers = cpu_to_le32(registers_num); range->range_data_size = cpu_to_le32(iter->fifo_size + registers_size); - iwl_write_prph_no_grab(fwrt->trans, TXF_LARC_NUM + offs, iter->fifo); + iwl_trans_write_prph(fwrt->trans, TXF_LARC_NUM + offs, iter->fifo); /* * read txf registers. for each register, write to the dump the @@ -1420,8 +460,8 @@ static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, addr = le32_to_cpu(reg->addrs[i]) + offs; reg_dump->addr = cpu_to_le32(addr); - reg_dump->data = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, - addr)); + reg_dump->data = cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + addr)); reg_dump++; } @@ -1432,17 +472,17 @@ static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, } /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ - iwl_write_prph_no_grab(fwrt->trans, TXF_READ_MODIFY_ADDR + offs, - TXF_WR_PTR + offs); + iwl_trans_write_prph(fwrt->trans, TXF_READ_MODIFY_ADDR + offs, + TXF_WR_PTR + offs); /* Dummy-read to advance the read pointer to the head */ - iwl_read_prph_no_grab(fwrt->trans, TXF_READ_MODIFY_DATA + offs); + iwl_trans_read_prph(fwrt->trans, TXF_READ_MODIFY_DATA + offs); /* Read FIFO */ addr = TXF_READ_MODIFY_DATA + offs; data = (void *)reg_dump; for (i = 0; i < iter->fifo_size; i += sizeof(*data)) - *data++ = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, addr)); + *data++ = cpu_to_le32(iwl_trans_read_prph(fwrt->trans, addr)); if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, @@ -1457,7 +497,7 @@ out: static int iwl_dump_ini_prph_snps_dphyip_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1487,12 +527,12 @@ iwl_dump_ini_prph_snps_dphyip_iter(struct iwl_fw_runtime *fwrt, continue; } - iwl_write_prph_no_grab(fwrt->trans, indirect_rd_wr_addr, - addr + i); + iwl_trans_write_prph(fwrt->trans, indirect_rd_wr_addr, + addr + i); /* wait a bit for value to be ready in register */ udelay(1); - prph_val = iwl_read_prph_no_grab(fwrt->trans, - indirect_rd_wr_addr); + prph_val = iwl_trans_read_prph(fwrt->trans, + indirect_rd_wr_addr); *val++ = cpu_to_le32((prph_val & DPHYIP_INDIRECT_RD_MSK) >> DPHYIP_INDIRECT_RD_SHIFT); } @@ -1570,7 +610,7 @@ static void iwl_ini_get_rxf_data(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_rxf_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1601,8 +641,8 @@ static int iwl_dump_ini_rxf_iter(struct iwl_fw_runtime *fwrt, addr = le32_to_cpu(reg->addrs[i]) + offs; reg_dump->addr = cpu_to_le32(addr); - reg_dump->data = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, - addr)); + reg_dump->data = cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + addr)); reg_dump++; } @@ -1615,18 +655,17 @@ static int iwl_dump_ini_rxf_iter(struct iwl_fw_runtime *fwrt, offs = rxf_data.offset; /* Lock fence */ - iwl_write_prph_no_grab(fwrt->trans, RXF_SET_FENCE_MODE + offs, 0x1); + iwl_trans_write_prph(fwrt->trans, RXF_SET_FENCE_MODE + offs, 0x1); /* Set fence pointer to the same place like WR pointer */ - iwl_write_prph_no_grab(fwrt->trans, RXF_LD_WR2FENCE + offs, 0x1); + iwl_trans_write_prph(fwrt->trans, RXF_LD_WR2FENCE + offs, 0x1); /* Set fence offset */ - iwl_write_prph_no_grab(fwrt->trans, RXF_LD_FENCE_OFFSET_ADDR + offs, - 0x0); + iwl_trans_write_prph(fwrt->trans, RXF_LD_FENCE_OFFSET_ADDR + offs, 0x0); /* Read FIFO */ addr = RXF_FIFO_RD_FENCE_INC + offs; data = (void *)reg_dump; for (i = 0; i < rxf_data.size; i += sizeof(*data)) - *data++ = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, addr)); + *data++ = cpu_to_le32(iwl_trans_read_prph(fwrt->trans, addr)); out: iwl_trans_release_nic_access(fwrt->trans); @@ -1637,7 +676,7 @@ out: static int iwl_dump_ini_err_table_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_region_err_table *err_table = ®->err_table; @@ -1656,7 +695,7 @@ iwl_dump_ini_err_table_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_special_mem_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_region_special_device_memory *special_mem = @@ -1677,7 +716,7 @@ iwl_dump_ini_special_mem_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_dbgi_sram_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1690,9 +729,11 @@ iwl_dump_ini_dbgi_sram_iter(struct iwl_fw_runtime *fwrt, range->range_data_size = reg->dev_addr.size; for (i = 0; i < (le32_to_cpu(reg->dev_addr.size) / 4); i++) { - prph_data = iwl_read_prph_no_grab(fwrt->trans, (i % 2) ? - DBGI_SRAM_TARGET_ACCESS_RDATA_MSB : - DBGI_SRAM_TARGET_ACCESS_RDATA_LSB); + prph_data = + iwl_trans_read_prph(fwrt->trans, + (i % 2) ? + DBGI_SRAM_TARGET_ACCESS_RDATA_MSB : + DBGI_SRAM_TARGET_ACCESS_RDATA_LSB); if (iwl_trans_is_hw_error_value(prph_data)) { iwl_trans_release_nic_access(fwrt->trans); return -EBUSY; @@ -1705,7 +746,7 @@ iwl_dump_ini_dbgi_sram_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_fw_pkt_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { struct iwl_fw_ini_error_dump_range *range = range_ptr; struct iwl_rx_packet *pkt = reg_data->dump_data->fw_pkt; @@ -1726,7 +767,7 @@ static int iwl_dump_ini_fw_pkt_iter(struct iwl_fw_runtime *fwrt, static int iwl_dump_ini_imr_iter(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range_ptr, u32 range_len, int idx) + void *range_ptr, int idx) { /* read the IMR memory and DMA it to SRAM */ struct iwl_fw_ini_error_dump_range *range = range_ptr; @@ -1791,7 +832,7 @@ static __le32 iwl_get_mon_reg(struct iwl_fw_runtime *fwrt, u32 alloc_id, if (!reg_info || !reg_info->addr || !reg_info->mask) return 0; - val = iwl_read_prph_no_grab(fwrt->trans, reg_info->addr + offs); + val = iwl_trans_read_prph(fwrt->trans, reg_info->addr + offs); return cpu_to_le32(mask_apply_and_normalize(val, reg_info->mask)); } @@ -2236,24 +1277,22 @@ struct iwl_dump_ini_mem_ops { void *data, u32 data_len); int (*fill_range)(struct iwl_fw_runtime *fwrt, struct iwl_dump_ini_region_data *reg_data, - void *range, u32 range_len, int idx); + void *range, int idx); }; -/** - * iwl_dump_ini_mem - dump memory region - * - * @fwrt: fw runtime struct - * @list: list to add the dump tlv to - * @reg_data: memory region - * @ops: memory dump operations - * - * Creates a dump tlv and copy a memory region into it. - * - * Returns: the size of the current dump tlv or 0 if failed - */ -static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, - struct iwl_dump_ini_region_data *reg_data, - const struct iwl_dump_ini_mem_ops *ops) +struct iwl_fw_ini_dump_entry { + const struct iwl_dump_ini_mem_ops *ops; + struct iwl_dump_ini_region_data reg_data; + struct list_head list; + u32 region_dump_policy; + u32 size; + u8 data[]; +} __packed; + +static void iwl_dump_ini_mem_prep(struct iwl_fw_runtime *fwrt, + struct list_head *list, + struct iwl_dump_ini_region_data *reg_data, + const struct iwl_dump_ini_mem_ops *ops) { struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; struct iwl_fw_ini_dump_entry *entry; @@ -2261,58 +1300,59 @@ static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, struct iwl_fw_ini_error_dump_header *header; u32 type = reg->type; u32 id = le32_get_bits(reg->id, IWL_FW_INI_REGION_ID_MASK); - u32 num_of_ranges, i, size; - u8 *range; - u32 free_size; - u64 header_size; + u32 num_of_ranges, size; u32 dump_policy = IWL_FW_INI_DUMP_VERBOSE; + u32 dp; IWL_DEBUG_FW(fwrt, "WRT: Collecting region: dump type=%d, id=%d, type=%d\n", dump_policy, id, type); if (le32_to_cpu(reg->hdr.version) >= 2) { - u32 dp = le32_get_bits(reg->id, - IWL_FW_INI_REGION_DUMP_POLICY_MASK); + dp = le32_get_bits(reg->id, IWL_FW_INI_REGION_DUMP_POLICY_MASK); if (dump_policy == IWL_FW_INI_DUMP_VERBOSE && !(dp & IWL_FW_INI_DEBUG_DUMP_POLICY_NO_LIMIT)) { IWL_DEBUG_FW(fwrt, "WRT: no dump - type %d and policy mismatch=%d\n", dump_policy, dp); - return 0; + return; } else if (dump_policy == IWL_FW_INI_DUMP_MEDIUM && !(dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_MAX_LIMIT_5MB)) { IWL_DEBUG_FW(fwrt, "WRT: no dump - type %d and policy mismatch=%d\n", dump_policy, dp); - return 0; + return; } else if (dump_policy == IWL_FW_INI_DUMP_BRIEF && !(dp & IWL_FW_INI_DEBUG_DUMP_POLICY_MAX_LIMIT_600KB)) { IWL_DEBUG_FW(fwrt, "WRT: no dump - type %d and policy mismatch=%d\n", dump_policy, dp); - return 0; + return; } + } else { + dp = 0; } if (!ops->get_num_of_ranges || !ops->get_size || !ops->fill_mem_hdr || !ops->fill_range) { IWL_DEBUG_FW(fwrt, "WRT: no ops for collecting data\n"); - return 0; + return; } size = ops->get_size(fwrt, reg_data); if (size < sizeof(*header)) { IWL_DEBUG_FW(fwrt, "WRT: size didn't include space for header\n"); - return 0; + return; } entry = vzalloc(sizeof(*entry) + sizeof(*tlv) + size); if (!entry) - return 0; + return; entry->size = sizeof(*tlv) + size; + entry->reg_data = *reg_data; + entry->region_dump_policy = dp; tlv = (void *)entry->data; tlv->type = reg->type; @@ -2329,7 +1369,29 @@ static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, header->name_len = cpu_to_le32(IWL_FW_INI_MAX_NAME); memcpy(header->name, reg->name, IWL_FW_INI_MAX_NAME); - free_size = size; + entry->ops = ops; + list_add_tail(&entry->list, list); +} + +static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, + struct iwl_fw_ini_dump_entry *entry) +{ + struct iwl_dump_ini_region_data *reg_data = &entry->reg_data; + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + const struct iwl_dump_ini_mem_ops *ops = entry->ops; + struct iwl_fw_ini_error_dump_data *tlv; + struct iwl_fw_ini_error_dump_header *header; + u32 type = reg->type; + u32 id = le32_get_bits(reg->id, IWL_FW_INI_REGION_ID_MASK); + u32 i; + u8 *range; + u32 free_size; + u64 header_size; + + tlv = (void *)entry->data; + header = (void *)tlv->data; + + free_size = entry->size - sizeof(*tlv); range = ops->fill_mem_hdr(fwrt, reg_data, header, free_size); if (!range) { IWL_ERR(fwrt, @@ -2350,9 +1412,8 @@ static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, free_size -= header_size; - for (i = 0; i < num_of_ranges; i++) { - int range_size = ops->fill_range(fwrt, reg_data, range, - free_size, i); + for (i = 0; i < le32_to_cpu(header->num_of_ranges); i++) { + int range_size = ops->fill_range(fwrt, reg_data, range, i); if (range_size < 0) { IWL_ERR(fwrt, @@ -2372,11 +1433,10 @@ static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, range = range + range_size; } - list_add_tail(&entry->list, list); - return entry->size; out_err: + list_del(&entry->list); vfree(entry); return 0; @@ -2605,22 +1665,19 @@ static bool iwl_dump_due_to_error(enum iwl_fw_ini_time_point tp_id) tp_id == IWL_FW_INI_TIME_POINT_FW_HW_ERROR; } -static u32 -iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt, - struct iwl_fwrt_dump_data *dump_data, - struct list_head *list, - enum iwl_fw_ini_time_point tp_id, - u64 regions_mask, - struct iwl_dump_ini_region_data *imr_reg_data, - enum iwl_dump_ini_region_selector which) +static void +iwl_dump_ini_dump_regions_prep(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + struct list_head *list, + enum iwl_fw_ini_time_point tp_id, + u64 regions_mask, + struct iwl_ucode_tlv **imr_tlv) { - u32 size = 0; - for (int i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.active_regions); i++) { struct iwl_dump_ini_region_data reg_data = { .dump_data = dump_data, }; - u32 reg_type, dp; + u32 reg_type; struct iwl_fw_ini_region_tlv *reg; if (!(BIT_ULL(i) & regions_mask)) @@ -2638,8 +1695,6 @@ iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt, if (reg_type >= ARRAY_SIZE(iwl_dump_ini_region_ops)) continue; - dp = le32_get_bits(reg->id, IWL_FW_INI_REGION_DUMP_POLICY_MASK); - if ((reg_type == IWL_FW_INI_REGION_PERIPHERY_PHY || reg_type == IWL_FW_INI_REGION_PERIPHERY_PHY_RANGE || reg_type == IWL_FW_INI_REGION_PERIPHERY_SNPS_DPHYIP) && @@ -2650,19 +1705,6 @@ iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt, continue; } - switch (which) { - case IWL_INI_DUMP_ALL_REGIONS: - break; - case IWL_INI_DUMP_EARLY_REGIONS: - if (!(dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_BEFORE_RESET)) - continue; - break; - case IWL_INI_DUMP_LATE_REGIONS: - if (dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_BEFORE_RESET) - continue; - break; - } - /* * DRAM_IMR can be collected only for FW/HW error timepoint * when fw is not alive. In addition, it must be collected @@ -2671,19 +1713,45 @@ iwl_dump_ini_dump_regions(struct iwl_fw_runtime *fwrt, */ if (reg_type == IWL_FW_INI_REGION_DRAM_IMR) { if (iwl_dump_due_to_error(tp_id)) - imr_reg_data->reg_tlv = - fwrt->trans->dbg.active_regions[i]; + *imr_tlv = fwrt->trans->dbg.active_regions[i]; else IWL_INFO(fwrt, "WRT: trying to collect DRAM_IMR at time point: %d, skipping\n", tp_id); - /* continue to next region */ + /* continue to next region */ continue; } + iwl_dump_ini_mem_prep(fwrt, list, ®_data, + &iwl_dump_ini_region_ops[reg_type]); + } +} + +static u32 +iwl_dump_ini_dump_entries(struct iwl_fw_runtime *fwrt, + struct list_head *list, + enum iwl_dump_ini_region_selector which) +{ + struct iwl_fw_ini_dump_entry *entry, *tmp; + u32 size = 0; + + list_for_each_entry_safe(entry, tmp, list, list) { + u32 dp = entry->region_dump_policy; + + switch (which) { + case IWL_INI_DUMP_ALL_REGIONS: + break; + case IWL_INI_DUMP_EARLY_REGIONS: + if (!(dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_BEFORE_RESET)) + continue; + break; + case IWL_INI_DUMP_LATE_REGIONS: + if (dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_BEFORE_RESET) + continue; + break; + } - size += iwl_dump_ini_mem(fwrt, list, ®_data, - &iwl_dump_ini_region_ops[reg_type]); + size += iwl_dump_ini_mem(fwrt, entry); } return size; @@ -2706,32 +1774,32 @@ static u32 iwl_dump_ini_trigger(struct iwl_fw_runtime *fwrt, BUILD_BUG_ON((sizeof(trigger->regions_mask) * BITS_PER_BYTE) < ARRAY_SIZE(fwrt->trans->dbg.active_regions)); + iwl_dump_ini_dump_regions_prep(fwrt, dump_data, list, tp_id, + regions_mask, &imr_reg_data.reg_tlv); + + /* append DRAM_IMR region to be collected last */ + if (imr_reg_data.reg_tlv) + iwl_dump_ini_mem_prep(fwrt, list, &imr_reg_data, + &iwl_dump_ini_region_ops[IWL_FW_INI_REGION_DRAM_IMR]); + if (trigger->apply_policy & cpu_to_le32(IWL_FW_INI_APPLY_POLICY_SPLIT_DUMP_RESET)) { - size += iwl_dump_ini_dump_regions(fwrt, dump_data, list, tp_id, - regions_mask, &imr_reg_data, + size += iwl_dump_ini_dump_entries(fwrt, list, IWL_INI_DUMP_EARLY_REGIONS); iwl_trans_pcie_fw_reset_handshake(fwrt->trans); - size += iwl_dump_ini_dump_regions(fwrt, dump_data, list, tp_id, - regions_mask, &imr_reg_data, + size += iwl_dump_ini_dump_entries(fwrt, list, IWL_INI_DUMP_LATE_REGIONS); } else { if (fw_has_capa(&fwrt->fw->ucode_capa, IWL_UCODE_TLV_CAPA_RESET_DURING_ASSERT) && iwl_dump_due_to_error(tp_id)) iwl_trans_pcie_fw_reset_handshake(fwrt->trans); - size += iwl_dump_ini_dump_regions(fwrt, dump_data, list, tp_id, - regions_mask, &imr_reg_data, + size += iwl_dump_ini_dump_entries(fwrt, list, IWL_INI_DUMP_ALL_REGIONS); } - /* collect DRAM_IMR region in the last */ - if (imr_reg_data.reg_tlv) - size += iwl_dump_ini_mem(fwrt, list, &imr_reg_data, - &iwl_dump_ini_region_ops[IWL_FW_INI_REGION_DRAM_IMR]); - if (size) { + if (size) size += iwl_dump_ini_info(fwrt, trigger, list); - } return size; } @@ -2797,52 +1865,6 @@ static inline void iwl_fw_free_dump_desc(struct iwl_fw_runtime *fwrt, fwrt->dump.umac_err_id = 0; } -static void iwl_fw_error_dump(struct iwl_fw_runtime *fwrt, - struct iwl_fwrt_dump_data *dump_data) -{ - struct iwl_fw_dump_ptrs fw_error_dump = {}; - struct iwl_fw_error_dump_file *dump_file; - struct scatterlist *sg_dump_data; - u32 file_len; - u32 dump_mask = fwrt->fw->dbg.dump_mask; - - dump_file = iwl_fw_error_dump_file(fwrt, &fw_error_dump, dump_data); - if (!dump_file) - return; - - if (dump_data->monitor_only) - dump_mask &= BIT(IWL_FW_ERROR_DUMP_FW_MONITOR); - - fw_error_dump.trans_ptr = iwl_trans_dump_data(fwrt->trans, dump_mask, - fwrt->sanitize_ops, - fwrt->sanitize_ctx); - file_len = le32_to_cpu(dump_file->file_len); - fw_error_dump.fwrt_len = file_len; - - if (fw_error_dump.trans_ptr) { - file_len += fw_error_dump.trans_ptr->len; - dump_file->file_len = cpu_to_le32(file_len); - } - - sg_dump_data = alloc_sgtable(file_len); - if (sg_dump_data) { - sg_pcopy_from_buffer(sg_dump_data, - sg_nents(sg_dump_data), - fw_error_dump.fwrt_ptr, - fw_error_dump.fwrt_len, 0); - if (fw_error_dump.trans_ptr) - sg_pcopy_from_buffer(sg_dump_data, - sg_nents(sg_dump_data), - fw_error_dump.trans_ptr->data, - fw_error_dump.trans_ptr->len, - fw_error_dump.fwrt_len); - dev_coredumpsg(fwrt->trans->dev, sg_dump_data, file_len, - GFP_KERNEL); - } - vfree(fw_error_dump.fwrt_ptr); - vfree(fw_error_dump.trans_ptr); -} - static void iwl_dump_ini_list_free(struct list_head *list) { while (!list_empty(list)) { @@ -2871,7 +1893,7 @@ static void iwl_fw_error_ini_dump(struct iwl_fw_runtime *fwrt, if (!file_len) return; - sg_dump_data = alloc_sgtable(file_len); + sg_dump_data = iwl_fw_dbg_alloc_sgtable(file_len); if (sg_dump_data) { struct iwl_fw_ini_dump_entry *entry; int sg_entries = sg_nents(sg_dump_data); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.h b/drivers/net/wireless/intel/iwlwifi/fw/dbg.h index 8034c9ecba69..fc962a320583 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dbg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.h @@ -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, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c index ddee7c2deb36..f06978d5b5ee 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c @@ -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; diff --git a/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h index 525a82030daa..07f1240df866 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h @@ -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 */ @@ -266,18 +266,6 @@ struct iwl_fw_ini_error_dump_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 * @file_len: the length of all the file including the header diff --git a/drivers/net/wireless/intel/iwlwifi/fw/file.h b/drivers/net/wireless/intel/iwlwifi/fw/file.h index f7a6f21267e9..197c88c25f72 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/file.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/file.h @@ -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) { diff --git a/drivers/net/wireless/intel/iwlwifi/fw/img.h b/drivers/net/wireless/intel/iwlwifi/fw/img.h index 94113d1db8e1..75b1344f6cbe 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/img.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/img.h @@ -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); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c index 55128caac7ed..8d9ff36e30f5 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c @@ -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"), + }, + }, {} }; diff --git a/drivers/net/wireless/intel/iwlwifi/fw/rs.c b/drivers/net/wireless/intel/iwlwifi/fw/rs.c index 746f2acffb8f..2aa300b26158 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/rs.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/rs.c @@ -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 */ } diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-config.h b/drivers/net/wireless/intel/iwlwifi/iwl-config.h index 5f40cd15e27f..30d5ec31b9c3 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-config.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-config.h @@ -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__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-csr.h b/drivers/net/wireless/intel/iwlwifi/iwl-csr.h index f3fa37fee2e4..d2fa80a3dd04 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-csr.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-csr.h @@ -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) diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c index d5ded4d3a30b..488524529538 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c @@ -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; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-io.c b/drivers/net/wireless/intel/iwlwifi/iwl-io.c index b1944584c693..bb746112ddad 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-io.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-io.c @@ -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) { diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-io.h b/drivers/net/wireless/intel/iwlwifi/iwl-io.h index 5bcec239ffc4..6dce2e5267a6 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-io.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-io.h @@ -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); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index 8f3f651451bb..7027bca249a0 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -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 = ®d->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; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h index 12f28bb0e859..e676d7c2d6cc 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h @@ -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; }; /** diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h index ac0a29a1c31f..52d35b73ed74 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-utils.h @@ -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[]; }; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-prph.h b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h index a7214ddcfaf5..6ca1f51b69a1 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-prph.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h @@ -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 */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c index 16b2c313e72b..0009488ca51b 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -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); + diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index 61e4f4776dcb..3ae840e546e8 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -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) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c index 3bf36f8f6874..e3627ad0321c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c @@ -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]); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ap.c b/drivers/net/wireless/intel/iwlwifi/mld/ap.c index 5c59acc8c4c5..bc426b911ce5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ap.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ap.c @@ -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 */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ap.h b/drivers/net/wireless/intel/iwlwifi/mld/ap.h index 4a6f52b9552d..f10e9c9a38ff 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ap.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/ap.h @@ -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); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index 3a595a1c2e00..fc0a5871df2f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -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, - ¬if->gtk[0].sc); - iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, ¬if->gtk[0].sc); + ¬if->gtk[0].sc, rsc_notif_ver); + if (rsc_notif_ver == IWL_FW_CMD_VER_UNKNOWN) + iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, + ¬if->gtk[0].sc); /* only one igtk is passed by FW */ iwl_mld_convert_igtk_resume_data(wowlan_status, ¬if->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; - - if (key->keyidx >= 0 && key->keyidx <= 3) { + int rsc_notif_ver = iwl_fw_lookup_notif_ver(data->mld->fw, + DATA_PATH_GROUP, + RSC_NOTIF, + IWL_FW_CMD_VER_UNKNOWN); + + /* 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, @@ -933,6 +956,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, struct iwl_mld_mcast_key_data *key_data, @@ -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; } } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.h b/drivers/net/wireless/intel/iwlwifi/mld/d3.h index 618d6fb3c796..c2e8ba877042 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.h @@ -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, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c index b05b58eb1281..351a4f177e92 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c @@ -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); } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c index 3464b3268712..81df3fdfcbf5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ftm-initiator.c @@ -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", diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c index 1e85a9168d2b..2b837c6fa5fe 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c @@ -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; + } - 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))) + if (!iwl_mld_vif_fw_id_valid(mld_vif)) return; + iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_REMOVE); + 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]); } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.h b/drivers/net/wireless/intel/iwlwifi/mld/iface.h index 8dfc79fed253..bc6f45ff76f8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/iface.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.h @@ -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__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/key.c b/drivers/net/wireless/intel/iwlwifi/mld/key.c index 04192c5f07ff..bf80b4770b5a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/key.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/key.c @@ -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) + 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; + + /* otherwise that's not valid */ + IWL_WARN(mld, "empty STA mask for key %d\n", key->keyidx); return -EINVAL; + } - if (igtk) { - if (mld->num_igtks == IWL_MAX_NUM_IGTKS) + 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++; } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index be2cdf43c72e..234821f6a441 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -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), +}; + +/* 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), }; -#define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade) +/* 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); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.h b/drivers/net/wireless/intel/iwlwifi/mld/link.h index ca691259fc5e..f1997e280058 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.h @@ -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); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index b48ebec18dd5..92858b8f7395 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -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 */ - { - .num_different_channels = 2, - .max_interfaces = 3, - .limits = iwl_mld_limits_nan, - .n_limits = ARRAY_SIZE(iwl_mld_limits_nan) - 1, - }, + /* NAN combination follow, this excludes P2P and AP */ { - .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 = + 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; + iwl_mld_hw_set_nan(mld); } else { - wiphy->iface_combinations = iwl_mld_iface_combinations; - /* Do not include NAN combinations */ + /* Do not include NAN combination */ wiphy->n_iface_combinations = - ARRAY_SIZE(iwl_mld_iface_combinations) - 2; + 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, }; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c index 16bb1b4904f9..8502129abe49 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mcc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mcc.c @@ -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: diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c index 9af79297c3b6..78c78cf891cd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c @@ -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, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.h b/drivers/net/wireless/intel/iwlwifi/mld/mld.h index 606cb64f8ea4..922aa3dbff54 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mld.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.h @@ -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) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c index 8227ccb31d60..a2f8a6957535 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mlo.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mlo.c @@ -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) \ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.c b/drivers/net/wireless/intel/iwlwifi/mld/nan.c index 4d8e85f2bd7c..d34a9a2cbeae 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.c @@ -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); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mld/nan.h b/drivers/net/wireless/intel/iwlwifi/mld/nan.h index c04d77208971..5411bca52cde 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/nan.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/nan.h @@ -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__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/notif.c b/drivers/net/wireless/intel/iwlwifi/mld/notif.c index 9c88a8579a75..7574689e4088 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/notif.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/notif.c @@ -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); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/phy.c b/drivers/net/wireless/intel/iwlwifi/mld/phy.c index 1d93fb9e4dbf..59bf088ead84 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/phy.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/phy.c @@ -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); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/power.c b/drivers/net/wireless/intel/iwlwifi/mld/power.c index 49b0d9f8f865..da065a446f81 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/power.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/power.c @@ -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 = @@ -289,17 +308,118 @@ static void iwl_mld_power_build_cmd(struct iwl_mld *mld, #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 iwl_mld_power_configure_uapsd(mld, link, cmd, ps_poll); } 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); + + if (cmd_ver >= 3) { + struct iwl_mac_power_cmd cmd = {}; - iwl_mld_power_build_cmd(mld, vif, &cmd, d3); + 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 = {}; - return iwl_mld_send_cmd_pdu(mld, MAC_PM_POWER_TABLE, &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 diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c index c65f4b56a327..20ae338e5696 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c @@ -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); } } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c index a2e586c6ea67..269439d789f4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c @@ -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) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.h b/drivers/net/wireless/intel/iwlwifi/mld/rx.h index 09dddbd40f55..573b89c3c9c6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.h @@ -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, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.c b/drivers/net/wireless/intel/iwlwifi/mld/sta.c index 4c97d12ce2d0..77eeeed66116 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.c @@ -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); + if (WARN_ON(fw_id < 0)) + return -EINVAL; - mld_link = iwl_mld_link_from_mac80211(link); + 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; - if (WARN_ON(!link || !mld_link) || fw_id < 0) - 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 @@ remove_added_link_stas: 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); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mld/sta.h b/drivers/net/wireless/intel/iwlwifi/mld/sta.h index 36288c2fb38c..98d693235c66 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/sta.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/sta.h @@ -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__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mld/stats.c b/drivers/net/wireless/intel/iwlwifi/mld/stats.c index 54eb0ead78ee..e7b283cbe199 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/stats.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/stats.c @@ -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 = - ¬if->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 = ¬if->per_sta[fw_sta_id]; + } else { + const struct iwl_system_statistics_notif_oper_v3 *notif = + (void *)&pkt->data; + per_sta = ¬if->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, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile b/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile index 36317feb923b..efa61638b8ee 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause iwlmld-tests-y += module.o hcmd.o utils.o link.o rx.o agg.o link-selection.o +iwlmld-tests-y += chan_load_thresh.o ccflags-y += -I$(src)/../ obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += iwlmld-tests.o diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c new file mode 100644 index 000000000000..87e29e09949b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/chan_load_thresh.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2026 Intel Corporation + */ +#include <kunit/static_stub.h> +#include "mld.h" +#include "link.h" +#include "iface.h" +#include "utils.h" + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +struct test_chan_load_case { + const char *desc; + u32 load; + enum iwl_mld_link_chan_load_level old_lvl; + enum iwl_mld_link_chan_load_level expected_lvl; + bool expected_scan_trig; +}; + +static const struct test_chan_load_case test_chan_load_thresh_cases[] = { + /* Level-up transitions */ + { + .desc = "Transition NONE->NONE", + .load = 20, + .old_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_scan_trig = false, + }, + { + .desc = "Transition NONE->LVL1", + .load = 50, + .old_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_lvl = LINK_CHAN_LOAD_LVL1, + .expected_scan_trig = true, + }, + { + .desc = "Transition LVL1->LVL2", + .load = 75, + .old_lvl = LINK_CHAN_LOAD_LVL1, + .expected_lvl = LINK_CHAN_LOAD_LVL2, + .expected_scan_trig = true, + }, + { + .desc = "Transition LVL2->LVL3", + .load = 90, + .old_lvl = LINK_CHAN_LOAD_LVL2, + .expected_lvl = LINK_CHAN_LOAD_LVL3, + .expected_scan_trig = true, + }, + + /* Level-down transitions */ + { + .desc = "Transition LVL1->NONE", + .load = 30, + .old_lvl = LINK_CHAN_LOAD_LVL1, + .expected_lvl = LINK_CHAN_LOAD_LVL_NONE, + .expected_scan_trig = false, + }, + { + .desc = "Transition LVL2->LVL1", + .load = 60, + .old_lvl = LINK_CHAN_LOAD_LVL2, + .expected_lvl = LINK_CHAN_LOAD_LVL1, + .expected_scan_trig = false, + }, + { + .desc = "Transition LVL3->LVL2", + .load = 70, + .old_lvl = LINK_CHAN_LOAD_LVL3, + .expected_lvl = LINK_CHAN_LOAD_LVL2, + .expected_scan_trig = false, + }, + + /* No change */ + { + .desc = "Transition LVL2->LVL2", + .load = 72, + .old_lvl = LINK_CHAN_LOAD_LVL2, + .expected_lvl = LINK_CHAN_LOAD_LVL2, + .expected_scan_trig = false, + }, +}; + +KUNIT_ARRAY_PARAM_DESC(test_chan_load_thresh_cases, + test_chan_load_thresh_cases, desc); + +static void test_chan_load_thresholds(struct kunit *test) +{ + const struct test_chan_load_case *tc = test->param_value; + struct iwl_mld *mld = test->priv; + struct ieee80211_vif *vif; + struct iwl_mld_vif *mld_vif; + struct ieee80211_bss_conf *link_conf; + struct iwl_mld_link *mld_link; + struct iwl_mld_kunit_link assoc_link = { + .id = 0, + .chandef = &chandef_6ghz_160mhz, + }; + bool scan_trig; + u32 chan_load; + + /* Setup associated non-MLO station */ + vif = iwlmld_kunit_setup_non_mlo_assoc(&assoc_link); + mld_vif = iwl_mld_vif_from_mac80211(vif); + + link_conf = &vif->bss_conf; + mld_link = &mld_vif->deflink; + + chan_load = tc->load; + mld_link->chan_load_lvl = tc->old_lvl; + + /* Execute function under test */ + wiphy_lock(mld->wiphy); + scan_trig = iwl_mld_chan_load_requires_scan(mld, link_conf, chan_load); + wiphy_unlock(mld->wiphy); + + /* Check return value */ + KUNIT_EXPECT_EQ(test, tc->expected_scan_trig, scan_trig); + + /* Check updated channel-load level */ + KUNIT_EXPECT_EQ(test, tc->expected_lvl, mld_link->chan_load_lvl); +} + +static struct kunit_case chan_load_thresh_test_cases[] = { + KUNIT_CASE_PARAM(test_chan_load_thresholds, + test_chan_load_thresh_cases_gen_params), + {} +}; + +static struct kunit_suite chan_load_thresh_test_suite = { + .name = "iwl_mld_chan_load_threshold_tests", + .init = iwlmld_kunit_test_init, + .test_cases = chan_load_thresh_test_cases, +}; + +kunit_test_suite(chan_load_thresh_test_suite); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c index 766c24db3613..69d222a8194c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c @@ -2,7 +2,7 @@ /* * KUnit tests for link selection functions * - * Copyright (C) 2025 Intel Corporation + * Copyright (C) 2025-2026 Intel Corporation */ #include <kunit/static_stub.h> @@ -34,6 +34,7 @@ static const struct link_grading_test_case { .link_id = 0, .chandef = &chandef_2ghz_20mhz, .active = false, + .signal = -70, .has_chan_util_elem = true, .chan_util = 128, }, @@ -45,6 +46,7 @@ static const struct link_grading_test_case { .link_id = 0, .chandef = &chandef_2ghz_20mhz, .active = false, + .signal = -70, .has_chan_util_elem = true, .chan_util = 180, }, @@ -55,6 +57,7 @@ static const struct link_grading_test_case { .input.link = { .link_id = 0, .chandef = &chandef_2ghz_20mhz, + .signal = -70, .has_chan_util_elem = true, .chan_util = 180, .active = true, @@ -67,6 +70,7 @@ static const struct link_grading_test_case { .input.link = { .link_id = 0, .chandef = &chandef_2ghz_20mhz, + .signal = -70, .active = true, }, .expected_grade = 120, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c index dce747270167..cb1968b07452 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/utils.c @@ -2,7 +2,7 @@ /* * KUnit tests for channel helper functions * - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #include <kunit/test.h> #include <kunit/test-bug.h> @@ -68,8 +68,6 @@ int iwlmld_kunit_test_init(struct kunit *test) return 0; } -static IWL_MLD_ALLOC_FN(link, bss_conf) - static void iwlmld_kunit_init_link(struct ieee80211_vif *vif, struct ieee80211_bss_conf *link, struct iwl_mld_link *mld_link, int link_id) @@ -94,7 +92,7 @@ static void iwlmld_kunit_init_link(struct ieee80211_vif *vif, rcu_assign_pointer(vif->link_conf[link_id], link); } -static IWL_MLD_ALLOC_FN(vif, vif) +IWL_MLD_ALLOC_FN_STATIC(vif, vif) /* Helper function to add and initialize a VIF for KUnit tests */ struct ieee80211_vif *iwlmld_kunit_add_vif(bool mlo, enum nl80211_iftype type) @@ -199,7 +197,7 @@ void iwlmld_kunit_assign_chanctx_to_link(struct ieee80211_vif *vif, vif->active_links |= BIT(link->link_id); } -static IWL_MLD_ALLOC_FN(link_sta, link_sta) +IWL_MLD_ALLOC_FN_STATIC(link_sta, link_sta) static void iwlmld_kunit_add_link_sta(struct ieee80211_sta *sta, struct ieee80211_link_sta *link_sta, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c index 78d6162d9297..a03834d3ac65 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c @@ -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> @@ -15,9 +15,9 @@ #include "fw/api/context.h" #include "fw/api/dhc.h" -static u8 iwl_mld_fw_bw_from_sta_bw(const struct ieee80211_link_sta *link_sta) +static u8 iwl_mld_fw_bw_from_sta_bw(enum ieee80211_sta_rx_bandwidth bandwidth) { - switch (link_sta->bandwidth) { + switch (bandwidth) { case IEEE80211_STA_RX_BW_320: return IWL_TLC_MNG_CH_WIDTH_320MHZ; case IEEE80211_STA_RX_BW_160: @@ -32,40 +32,61 @@ static u8 iwl_mld_fw_bw_from_sta_bw(const struct ieee80211_link_sta *link_sta) } } +struct iwl_mld_tlc_sta_capa { + u16 non_ht_rates; + u16 max_amsdu_len; + u8 rx_nss; + enum ieee80211_sta_rx_bandwidth bandwidth; + enum ieee80211_smps_mode smps_mode; + const struct ieee80211_sta_ht_cap *ht_cap; + const struct ieee80211_sta_vht_cap *vht_cap; + const struct ieee80211_sta_he_cap *he_cap; + const struct ieee80211_sta_eht_cap *eht_cap; + const struct ieee80211_sta_uhr_cap *uhr_cap; + + const struct ieee80211_sta_he_cap *own_he_cap; + const struct ieee80211_sta_eht_cap *own_eht_cap; + const struct ieee80211_sta_uhr_cap *own_uhr_cap; +}; + static __le16 iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld, - struct ieee80211_vif *vif, - struct ieee80211_link_sta *link_sta, - const struct ieee80211_sta_he_cap *own_he_cap, - const struct ieee80211_sta_eht_cap *own_eht_cap, - const struct ieee80211_sta_uhr_cap *own_uhr_cap) + struct iwl_mld_tlc_sta_capa *capa) { - struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap; - struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap; - struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap; - bool has_vht = vht_cap->vht_supported; + const struct ieee80211_sta_ht_cap *ht_cap = capa->ht_cap; + const struct ieee80211_sta_vht_cap *vht_cap = capa->vht_cap; + const struct ieee80211_sta_he_cap *he_cap = capa->he_cap; + const struct ieee80211_sta_eht_cap *eht_cap = capa->eht_cap; + const struct ieee80211_sta_uhr_cap *uhr_cap = capa->uhr_cap; + const struct ieee80211_sta_he_cap *own_he_cap = capa->own_he_cap; + const struct ieee80211_sta_eht_cap *own_eht_cap = capa->own_eht_cap; + const struct ieee80211_sta_uhr_cap *own_uhr_cap = capa->own_uhr_cap; + bool has_vht = vht_cap && vht_cap->vht_supported; u16 flags = 0; /* STBC flags */ if (mld->cfg->ht_params.stbc && (hweight8(iwl_mld_get_valid_tx_ant(mld)) > 1)) { - if (he_cap->has_he && he_cap->he_cap_elem.phy_cap_info[2] & - IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ) + if (he_cap && he_cap->has_he && + he_cap->he_cap_elem.phy_cap_info[2] & + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ) flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK; - else if (vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK) + else if (vht_cap && + vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK) flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK; - else if (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC) + else if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_RX_STBC) flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK; } /* LDPC */ if (mld->cfg->ht_params.ldpc && - ((ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) || + ((ht_cap && ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) || (has_vht && (vht_cap->cap & IEEE80211_VHT_CAP_RXLDPC)))) flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK; - if (he_cap->has_he && (he_cap->he_cap_elem.phy_cap_info[1] & - IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD)) + if (he_cap && he_cap->has_he && + (he_cap->he_cap_elem.phy_cap_info[1] & + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD)) flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK; if (own_he_cap && @@ -74,7 +95,7 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld, flags &= ~IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK; /* DCM */ - if (he_cap->has_he && + if (he_cap && he_cap->has_he && (he_cap->he_cap_elem.phy_cap_info[3] & IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_MASK && own_he_cap && @@ -85,15 +106,15 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld, /* Extra EHT LTF */ if (own_eht_cap && own_eht_cap->eht_cap_elem.phy_cap_info[5] & - IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF && - link_sta->eht_cap.has_eht && - link_sta->eht_cap.eht_cap_elem.phy_cap_info[5] & - IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF) { + IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF && + eht_cap && eht_cap->has_eht && + eht_cap->eht_cap_elem.phy_cap_info[5] & + IEEE80211_EHT_PHY_CAP5_SUPP_EXTRA_EHT_LTF) { flags |= IWL_TLC_MNG_CFG_FLAGS_EHT_EXTRA_LTF_MSK; } - if (link_sta->uhr_cap.has_uhr && own_uhr_cap && - link_sta->uhr_cap.phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX && + if (uhr_cap && uhr_cap->has_uhr && own_uhr_cap && + uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX && own_uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_TX) flags |= IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_1_5_MBPS_MSK | IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_3_MBPS_MSK; @@ -114,27 +135,27 @@ static u8 iwl_mld_get_fw_chains(struct iwl_mld *mld) return fw_chains; } -static u8 iwl_mld_get_fw_sgi(struct ieee80211_link_sta *link_sta) +static u8 iwl_mld_get_fw_sgi(struct iwl_mld_tlc_sta_capa *capa) { - struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap; - struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap; - struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap; + const struct ieee80211_sta_ht_cap *ht_cap = capa->ht_cap; + const struct ieee80211_sta_vht_cap *vht_cap = capa->vht_cap; + const struct ieee80211_sta_he_cap *he_cap = capa->he_cap; u8 sgi_chwidths = 0; /* If the association supports HE, HT/VHT rates will never be used for * Tx and therefor there's no need to set the * sgi-per-channel-width-support bits */ - if (he_cap->has_he) + if (he_cap && he_cap->has_he) return 0; - if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20) + if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_SGI_20) sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_20MHZ); - if (ht_cap->cap & IEEE80211_HT_CAP_SGI_40) + if (ht_cap && ht_cap->cap & IEEE80211_HT_CAP_SGI_40) sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_40MHZ); - if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80) + if (vht_cap && vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80) sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_80MHZ); - if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160) + if (vht_cap && vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160) sgi_chwidths |= BIT(IWL_TLC_MNG_CH_WIDTH_160MHZ); return sgi_chwidths; @@ -164,31 +185,30 @@ iwl_mld_get_highest_fw_mcs(const struct ieee80211_sta_vht_cap *vht_cap, } static void -iwl_mld_fill_vht_rates(const struct ieee80211_link_sta *link_sta, - const struct ieee80211_sta_vht_cap *vht_cap, +iwl_mld_fill_vht_rates(struct iwl_mld_tlc_sta_capa *capa, struct iwl_tlc_config_cmd *cmd) { u32 supp; int i, highest_mcs; - u8 max_nss = link_sta->rx_nss; + u8 max_nss = capa->rx_nss; struct ieee80211_vht_cap ieee_vht_cap = { - .vht_cap_info = cpu_to_le32(vht_cap->cap), - .supp_mcs = vht_cap->vht_mcs, + .vht_cap_info = cpu_to_le32(capa->vht_cap->cap), + .supp_mcs = capa->vht_cap->vht_mcs, }; /* the station support only a single receive chain */ - if (link_sta->smps_mode == IEEE80211_SMPS_STATIC) + if (capa->smps_mode == IEEE80211_SMPS_STATIC) max_nss = 1; for (i = 0; i < max_nss && i < IWL_TLC_NSS_MAX; i++) { int nss = i + 1; - highest_mcs = iwl_mld_get_highest_fw_mcs(vht_cap, nss); + highest_mcs = iwl_mld_get_highest_fw_mcs(capa->vht_cap, nss); if (!highest_mcs) continue; supp = BIT(highest_mcs + 1) - 1; - if (link_sta->bandwidth == IEEE80211_STA_RX_BW_20) + if (capa->bandwidth == IEEE80211_STA_RX_BW_20) supp &= ~BIT(IWL_TLC_MNG_HT_RATE_MCS9); cmd->ht_rates[i][IWL_TLC_MCS_PER_BW_80] = cpu_to_le32(supp); @@ -196,7 +216,7 @@ iwl_mld_fill_vht_rates(const struct ieee80211_link_sta *link_sta, * configuration is supported - only for MCS 0 since we already * decoded the MCS bits anyway ourselves. */ - if (link_sta->bandwidth == IEEE80211_STA_RX_BW_160 && + if (capa->bandwidth == IEEE80211_STA_RX_BW_160 && ieee80211_get_vht_max_nss(&ieee_vht_cap, IEEE80211_VHT_CHANWIDTH_160MHZ, 0, true, nss) >= nss) @@ -223,20 +243,19 @@ static u32 iwl_mld_he_mac80211_mcs_to_fw_mcs(u16 mcs) } static void -iwl_mld_fill_he_rates(const struct ieee80211_link_sta *link_sta, - const struct ieee80211_sta_he_cap *own_he_cap, +iwl_mld_fill_he_rates(struct iwl_mld_tlc_sta_capa *capa, struct iwl_tlc_config_cmd *cmd) { - const struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap; + const struct ieee80211_sta_he_cap *he_cap = capa->he_cap; u16 mcs_160 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160); u16 mcs_80 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80); - u16 tx_mcs_80 = le16_to_cpu(own_he_cap->he_mcs_nss_supp.tx_mcs_80); - u16 tx_mcs_160 = le16_to_cpu(own_he_cap->he_mcs_nss_supp.tx_mcs_160); + u16 tx_mcs_80 = le16_to_cpu(capa->own_he_cap->he_mcs_nss_supp.tx_mcs_80); + u16 tx_mcs_160 = le16_to_cpu(capa->own_he_cap->he_mcs_nss_supp.tx_mcs_160); int i; - u8 nss = link_sta->rx_nss; + u8 nss = capa->rx_nss; /* the station support only a single receive chain */ - if (link_sta->smps_mode == IEEE80211_SMPS_STATIC) + if (capa->smps_mode == IEEE80211_SMPS_STATIC) nss = 1; for (i = 0; i < nss && i < IWL_TLC_NSS_MAX; i++) { @@ -313,17 +332,15 @@ static u8 iwl_mld_get_eht_max_nss(u8 rx_nss, u8 tx_nss) static void iwl_mld_fill_eht_rates(struct ieee80211_vif *vif, - const struct ieee80211_link_sta *link_sta, - const struct ieee80211_sta_he_cap *own_he_cap, - const struct ieee80211_sta_eht_cap *own_eht_cap, + struct iwl_mld_tlc_sta_capa *capa, struct iwl_tlc_config_cmd *cmd) { /* peer RX mcs capa */ const struct ieee80211_eht_mcs_nss_supp *eht_rx_mcs = - &link_sta->eht_cap.eht_mcs_nss_supp; + &capa->eht_cap->eht_mcs_nss_supp; /* our TX mcs capa */ const struct ieee80211_eht_mcs_nss_supp *eht_tx_mcs = - &own_eht_cap->eht_mcs_nss_supp; + &capa->own_eht_cap->eht_mcs_nss_supp; enum IWL_TLC_MCS_PER_BW bw; struct ieee80211_eht_mcs_nss_supp_20mhz_only mcs_rx_20; @@ -331,7 +348,7 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif, /* peer is 20 MHz only */ if (vif->type == NL80211_IFTYPE_AP && - !(link_sta->he_cap.he_cap_elem.phy_cap_info[0] & + !(capa->he_cap->he_cap_elem.phy_cap_info[0] & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) { mcs_rx_20 = eht_rx_mcs->only_20mhz; } else { @@ -346,7 +363,7 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif, } /* NIC is capable of 20 MHz only */ - if (!(own_he_cap->he_cap_elem.phy_cap_info[0] & + if (!(capa->own_he_cap->he_cap_elem.phy_cap_info[0] & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) { mcs_tx_20 = eht_tx_mcs->only_20mhz; } else { @@ -402,67 +419,51 @@ iwl_mld_fill_eht_rates(struct ieee80211_vif *vif, } /* the station support only a single receive chain */ - if (link_sta->smps_mode == IEEE80211_SMPS_STATIC || - link_sta->rx_nss < 2) + if (capa->smps_mode == IEEE80211_SMPS_STATIC || + capa->rx_nss < 2) memset(cmd->ht_rates[IWL_TLC_NSS_2], 0, sizeof(cmd->ht_rates[IWL_TLC_NSS_2])); } static void -iwl_mld_fill_supp_rates(struct iwl_mld *mld, struct ieee80211_vif *vif, - struct ieee80211_link_sta *link_sta, - struct ieee80211_supported_band *sband, - const struct ieee80211_sta_he_cap *own_he_cap, - const struct ieee80211_sta_eht_cap *own_eht_cap, - const struct ieee80211_sta_uhr_cap *own_uhr_cap, +iwl_mld_fill_supp_rates(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct iwl_mld_tlc_sta_capa *capa, struct iwl_tlc_config_cmd *cmd) { - int i; - u16 non_ht_rates = 0; - unsigned long rates_bitmap; - const struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap; - const struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap; - const struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap; - - /* non HT rates */ - rates_bitmap = link_sta->supp_rates[sband->band]; - for_each_set_bit(i, &rates_bitmap, BITS_PER_LONG) - non_ht_rates |= BIT(sband->bitrates[i].hw_value); - - cmd->non_ht_rates = cpu_to_le16(non_ht_rates); + cmd->non_ht_rates = cpu_to_le16(capa->non_ht_rates); cmd->mode = IWL_TLC_MNG_MODE_NON_HT; - if (link_sta->uhr_cap.has_uhr && own_uhr_cap) { + if (capa->uhr_cap && capa->uhr_cap->has_uhr && capa->own_uhr_cap) { cmd->mode = IWL_TLC_MNG_MODE_UHR; /* * FIXME: spec currently inherits from EHT but has no * finer MCS bits. Once that's there, need to add them * to the bitmaps (and maybe copy this to UHR, or so.) */ - iwl_mld_fill_eht_rates(vif, link_sta, own_he_cap, - own_eht_cap, cmd); - } else if (link_sta->eht_cap.has_eht && own_he_cap && own_eht_cap) { + iwl_mld_fill_eht_rates(vif, capa, cmd); + } else if (capa->eht_cap && capa->eht_cap->has_eht && + capa->own_he_cap && capa->own_eht_cap) { cmd->mode = IWL_TLC_MNG_MODE_EHT; - iwl_mld_fill_eht_rates(vif, link_sta, own_he_cap, - own_eht_cap, cmd); - } else if (he_cap->has_he && own_he_cap) { + iwl_mld_fill_eht_rates(vif, capa, cmd); + } else if (capa->he_cap && capa->he_cap->has_he && capa->own_he_cap) { cmd->mode = IWL_TLC_MNG_MODE_HE; - iwl_mld_fill_he_rates(link_sta, own_he_cap, cmd); - } else if (vht_cap->vht_supported) { + iwl_mld_fill_he_rates(capa, cmd); + } else if (capa->vht_cap && capa->vht_cap->vht_supported) { cmd->mode = IWL_TLC_MNG_MODE_VHT; - iwl_mld_fill_vht_rates(link_sta, vht_cap, cmd); - } else if (ht_cap->ht_supported) { + iwl_mld_fill_vht_rates(capa, cmd); + } else if (capa->ht_cap && capa->ht_cap->ht_supported) { cmd->mode = IWL_TLC_MNG_MODE_HT; cmd->ht_rates[IWL_TLC_NSS_1][IWL_TLC_MCS_PER_BW_80] = - cpu_to_le32(ht_cap->mcs.rx_mask[0]); + cpu_to_le32(capa->ht_cap->mcs.rx_mask[0]); /* the station support only a single receive chain */ - if (link_sta->smps_mode == IEEE80211_SMPS_STATIC) + if (capa->smps_mode == IEEE80211_SMPS_STATIC) cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] = 0; else cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] = - cpu_to_le32(ht_cap->mcs.rx_mask[1]); + cpu_to_le32(capa->ht_cap->mcs.rx_mask[1]); } } @@ -527,60 +528,32 @@ static int iwl_mld_convert_tlc_cmd_to_v4(struct iwl_tlc_config_cmd *cmd, static void iwl_mld_send_tlc_cmd(struct iwl_mld *mld, struct ieee80211_vif *vif, - struct ieee80211_link_sta *link_sta, - struct ieee80211_bss_conf *link) + struct iwl_mld_sta *mld_sta, + int fw_sta_mask, int phy_id, + struct iwl_mld_tlc_sta_capa *capa) { - struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta); - struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); - enum nl80211_band band = link->chanreq.oper.chan->band; - struct ieee80211_supported_band *sband = mld->hw->wiphy->bands[band]; - const struct ieee80211_sta_he_cap *own_he_cap = - ieee80211_get_he_iftype_cap_vif(sband, vif); - const struct ieee80211_sta_eht_cap *own_eht_cap = - ieee80211_get_eht_iftype_cap_vif(sband, vif); - const struct ieee80211_sta_uhr_cap *own_uhr_cap = - ieee80211_get_uhr_iftype_cap_vif(sband, vif); struct iwl_tlc_config_cmd cmd = { /* For AP mode, use 20 MHz until the STA is authorized */ .max_ch_width = mld_sta->sta_state > IEEE80211_STA_ASSOC ? - iwl_mld_fw_bw_from_sta_bw(link_sta) : + iwl_mld_fw_bw_from_sta_bw(capa->bandwidth) : IWL_TLC_MNG_CH_WIDTH_20MHZ, - .flags = iwl_mld_get_tlc_cmd_flags(mld, vif, link_sta, - own_he_cap, own_eht_cap, - own_uhr_cap), + .flags = iwl_mld_get_tlc_cmd_flags(mld, capa), .chains = iwl_mld_get_fw_chains(mld), - .sgi_ch_width_supp = iwl_mld_get_fw_sgi(link_sta), - .max_mpdu_len = cpu_to_le16(link_sta->agg.max_amsdu_len), + .sgi_ch_width_supp = iwl_mld_get_fw_sgi(capa), + .max_mpdu_len = cpu_to_le16(capa->max_amsdu_len), }; - int fw_sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta); u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, TLC_MNG_CONFIG_CMD); u8 cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, 0); - struct ieee80211_chanctx_conf *chan_ctx; struct iwl_tlc_config_cmd_v5 cmd_v5 = {}; struct iwl_tlc_config_cmd_v4 cmd_v4 = {}; void *cmd_ptr; u8 cmd_size; - u32 phy_id; int ret; - if (fw_sta_id < 0) - return; - - cmd.sta_mask = cpu_to_le32(BIT(fw_sta_id)); - - if (WARN_ON_ONCE(!mld_link)) - return; - - chan_ctx = rcu_dereference_wiphy(mld->wiphy, mld_link->chan_ctx); - if (WARN_ON(!chan_ctx)) - return; - - phy_id = iwl_mld_phy_from_mac80211(chan_ctx)->fw_id; + cmd.sta_mask = cpu_to_le32(fw_sta_mask); cmd.phy_id = cpu_to_le32(phy_id); - iwl_mld_fill_supp_rates(mld, vif, link_sta, sband, - own_he_cap, own_eht_cap, - own_uhr_cap, &cmd); + iwl_mld_fill_supp_rates(mld, vif, capa, &cmd); if (cmd_ver == 6) { cmd_ptr = &cmd; @@ -641,16 +614,32 @@ int iwl_mld_send_tlc_dhc(struct iwl_mld *mld, u8 sta_id, u32 type, u32 data) return ret; } -void iwl_mld_config_tlc_link(struct iwl_mld *mld, - struct ieee80211_vif *vif, - struct ieee80211_bss_conf *link_conf, - struct ieee80211_link_sta *link_sta) +static void _iwl_mld_config_tlc_link(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *chan_ctx, + u32 fw_sta_mask, + struct ieee80211_link_sta *link_sta, + struct iwl_mld_tlc_sta_capa *capa) { struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta); + struct ieee80211_supported_band *sband; + unsigned long rates_bitmap; + enum nl80211_band band; + int i; - if (WARN_ON_ONCE(!link_conf->chanreq.oper.chan)) + /* + * Note: Due to NAN, the chan_ctx here might not be the same as the + * vif->links[link_sta->link_id] one (which is NULL in NAN), so some + * care is needed in this function (and the 'capa' exists to make it + * less error-prone, the other functions need not worry about it.) + */ + + if (WARN_ON(!chan_ctx)) return; + band = chan_ctx->def.chan->band; + sband = mld->hw->wiphy->bands[band]; + /* Before we have information about a station, configure the A-MSDU RC * limit such that iwlmd and mac80211 would not be allowed to build * A-MSDUs. @@ -660,7 +649,21 @@ void iwl_mld_config_tlc_link(struct iwl_mld *mld, ieee80211_sta_recalc_aggregates(link_sta->sta); } - iwl_mld_send_tlc_cmd(mld, vif, link_sta, link_conf); + /* non HT rates */ + rates_bitmap = link_sta->supp_rates[sband->band]; + for_each_set_bit(i, &rates_bitmap, BITS_PER_LONG) + capa->non_ht_rates |= BIT(sband->bitrates[i].hw_value); + + capa->max_amsdu_len = link_sta->agg.max_amsdu_len; + capa->ht_cap = &link_sta->ht_cap; + capa->vht_cap = &link_sta->vht_cap; + capa->he_cap = &link_sta->he_cap; + capa->eht_cap = &link_sta->eht_cap; + capa->uhr_cap = &link_sta->uhr_cap; + + iwl_mld_send_tlc_cmd(mld, vif, mld_sta, fw_sta_mask, + iwl_mld_phy_from_mac80211(chan_ctx)->fw_id, + capa); } void iwl_mld_tlc_update_phy(struct iwl_mld *mld, struct ieee80211_vif *vif, @@ -706,6 +709,136 @@ void iwl_mld_tlc_update_phy(struct iwl_mld *mld, struct ieee80211_vif *vif, } } +static void iwl_mld_config_tlc_nan(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct ieee80211_link_sta *link_sta, + int fw_sta_id) +{ + struct ieee80211_nan_peer_sched *sched; + struct iwl_mld_nan_link *nan_link; + struct ieee80211_sta *nmi, *iter; + struct iwl_mld_vif *nan_mld_vif; + u32 fw_sta_mask; + + nmi = wiphy_dereference(mld->wiphy, link_sta->sta->nmi); + if (WARN_ON(!nmi)) + return; + + sched = nmi->nan_sched; + + /* This sta might not be in the list yet as it is just getting added */ + fw_sta_mask = BIT(fw_sta_id); + for_each_station(iter, mld->hw) { + struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(iter); + struct ieee80211_sta *iter_nmi; + + iter_nmi = wiphy_dereference(mld->wiphy, iter->nmi); + + if (iter_nmi == nmi) + fw_sta_mask |= BIT(mld_sta->deflink.fw_id); + } + + if (WARN_ON(!mld->nan_device_vif)) + return; + + nan_mld_vif = iwl_mld_vif_from_mac80211(mld->nan_device_vif); + + for_each_mld_nan_valid_link(nan_mld_vif, nan_link) { + struct ieee80211_chanctx_conf *chan_ctx; + struct iwl_mld_tlc_sta_capa capa = {}; + struct cfg80211_chan_def *chandef; + bool common_channel = false; + + chan_ctx = nan_link->chanctx; + if (!chan_ctx) + continue; + + chandef = iwl_mld_get_chandef_from_chanctx(mld, chan_ctx); + + capa.smps_mode = IEEE80211_SMPS_OFF; /* always off */ + + capa.rx_nss = 2; /* maximum we support */ + capa.bandwidth = ieee80211_chan_width_to_rx_bw(chandef->width); + + for (int j = 0; j < (sched ? sched->n_channels : 0); j++) { + enum ieee80211_sta_rx_bandwidth rx_bw; + enum nl80211_chan_width width; + int chains; + + if (sched->channels[j].chanctx_conf != chan_ctx) + continue; + + common_channel = true; + + width = sched->channels[j].chanreq.oper.width; + rx_bw = ieee80211_chan_width_to_rx_bw(width); + capa.bandwidth = min(capa.bandwidth, rx_bw); + + chains = sched->channels[j].needed_rx_chains; + capa.rx_nss = min(capa.rx_nss, chains); + } + + /* Apply minimum parameters if there is no common channel */ + if (!common_channel) { + capa.bandwidth = IEEE80211_STA_RX_BW_20; + capa.rx_nss = 1; + } + + capa.own_he_cap = &mld->wiphy->nan_capa.phy.he; + /* no EHT/UHR for NAN */ + + _iwl_mld_config_tlc_link(mld, vif, chan_ctx, fw_sta_mask, + link_sta, &capa); + } +} + +void iwl_mld_config_tlc_link(struct iwl_mld *mld, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct ieee80211_link_sta *link_sta) +{ + struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf); + int fw_sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta); + struct ieee80211_chanctx_conf *chan_ctx; + struct ieee80211_supported_band *sband; + struct iwl_mld_tlc_sta_capa capa = {}; + struct cfg80211_chan_def *chandef; + enum nl80211_band band; + + if (fw_sta_id < 0) + return; + + if (vif->type == NL80211_IFTYPE_NAN) + return; + + if (vif->type == NL80211_IFTYPE_NAN_DATA) { + iwl_mld_config_tlc_nan(mld, vif, link_sta, fw_sta_id); + return; + } + + if (WARN_ON_ONCE(!mld_link)) + return; + + chan_ctx = rcu_dereference_wiphy(mld->wiphy, mld_link->chan_ctx); + if (WARN_ON_ONCE(!chan_ctx)) + return; + + chandef = &iwl_mld_phy_from_mac80211(chan_ctx)->chandef; + band = chandef->chan->band; + sband = mld->hw->wiphy->bands[band]; + + capa.smps_mode = link_sta->smps_mode; + capa.rx_nss = link_sta->rx_nss; + capa.bandwidth = ieee80211_chan_width_to_rx_bw(chandef->width); + capa.bandwidth = min(capa.bandwidth, link_sta->bandwidth); + capa.own_he_cap = ieee80211_get_he_iftype_cap_vif(sband, vif); + capa.own_eht_cap = ieee80211_get_eht_iftype_cap_vif(sband, vif); + capa.own_uhr_cap = ieee80211_get_uhr_iftype_cap_vif(sband, vif); + + _iwl_mld_config_tlc_link(mld, vif, chan_ctx, BIT(fw_sta_id), + link_sta, &capa); +} + void iwl_mld_config_tlc(struct iwl_mld *mld, struct ieee80211_vif *vif, struct ieee80211_sta *sta) { @@ -714,6 +847,7 @@ void iwl_mld_config_tlc(struct iwl_mld *mld, struct ieee80211_vif *vif, lockdep_assert_wiphy(mld->wiphy); + /* Note: for NAN this is only the mac80211-level deflink */ for_each_vif_active_link(vif, link, link_id) { struct ieee80211_link_sta *link_sta = link_sta_dereference_check(sta, link_id); @@ -822,9 +956,7 @@ void iwl_mld_handle_tlc_notif(struct iwl_mld *mld, if (WARN_ON(!mld_link_sta)) return; - mld_link_sta->last_rate_n_flags = - iwl_v3_rate_from_v2_v3(notif->rate, - mld->fw_rates_ver_3); + mld_link_sta->last_rate_n_flags = le32_to_cpu(notif->rate); rs_pretty_print_rate(pretty_rate, sizeof(pretty_rate), mld_link_sta->last_rate_n_flags); diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c index 0bcb1ae69468..1e8716cbb4ce 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c @@ -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/ip.h> @@ -71,14 +71,19 @@ static int iwl_mld_allocate_txq(struct iwl_mld *mld, struct ieee80211_txq *txq) { u8 tid = txq->tid == IEEE80211_NUM_TIDS ? IWL_MGMT_TID : txq->tid; u32 fw_sta_mask = iwl_mld_fw_sta_id_mask(mld, txq->sta); - /* We can't know when the station is asleep or awake, so we - * must disable the queue hang detection. - */ - unsigned int watchdog_timeout = txq->vif->type == NL80211_IFTYPE_AP ? - IWL_WATCHDOG_DISABLED : - mld->trans->mac_cfg->base->wd_timeout; + unsigned int watchdog_timeout; int queue, size; + switch (txq->vif->type) { + case NL80211_IFTYPE_AP: /* STA might go to PS */ + case NL80211_IFTYPE_NAN_DATA: /* peer might ULW/break schedule */ + watchdog_timeout = IWL_WATCHDOG_DISABLED; + break; + default: + watchdog_timeout = mld->trans->mac_cfg->base->wd_timeout; + break; + } + lockdep_assert_wiphy(mld->wiphy); if (tid == IWL_MGMT_TID) @@ -346,7 +351,8 @@ u8 iwl_mld_get_lowest_rate(struct iwl_mld *mld, iwl_mld_get_basic_rates_and_band(mld, vif, info, &basic_rates, &band); if (band >= NUM_NL80211_BANDS) { - WARN_ON(vif->type != NL80211_IFTYPE_NAN); + WARN_ON(vif->type != NL80211_IFTYPE_NAN && + vif->type != NL80211_IFTYPE_NAN_DATA); return IWL_FIRST_OFDM_RATE; } @@ -489,7 +495,7 @@ static __le32 iwl_mld_get_tx_rate_n_flags(struct iwl_mld *mld, rate = iwl_mld_mac80211_rate_idx_to_fw(mld, info, -1) | iwl_mld_get_tx_ant(mld, info, sta, fc); - return iwl_v3_rate_to_v2_v3(rate, mld->fw_rates_ver_3); + return cpu_to_le32(rate); } static void @@ -552,10 +558,12 @@ iwl_mld_fill_tx_cmd(struct iwl_mld *mld, struct sk_buff *skb, flags |= IWL_TX_FLAGS_ENCRYPT_DIS; /* For data and mgmt packets rate info comes from the fw. - * Only set rate/antenna for injected frames with fixed rate, or - * when no sta is given. + * Only set rate/antenna for: + * - injected frames with fixed rate, + * - when no sta is given. + * - frames that are sent to an NMI sta, which is only used for management. */ - if (unlikely(!sta || + if (unlikely(!sta || mld_sta->vif->type == NL80211_IFTYPE_NAN || info->control.flags & IEEE80211_TX_CTRL_RATE_INJECT)) { flags |= IWL_TX_FLAGS_CMD_RATE; rate_n_flags = iwl_mld_get_tx_rate_n_flags(mld, info, sta, @@ -673,11 +681,16 @@ iwl_mld_get_tx_queue_id(struct iwl_mld *mld, struct ieee80211_txq *txq, WARN_ON(!ieee80211_is_mgmt(fc)); return mld_vif->aux_sta.queue_id; case NL80211_IFTYPE_NAN: - mld_vif = iwl_mld_vif_from_mac80211(info->control.vif); - WARN_ON(!ieee80211_is_mgmt(fc)); + return iwl_mld_nan_get_mgmt_queue(mld, info->control.vif); + case NL80211_IFTYPE_NAN_DATA: + WARN_ON(!ieee80211_is_data(fc)); - return mld_vif->aux_sta.queue_id; + if (!iwl_mld_nan_use_nan_stations(mld)) + break; + + mld_vif = iwl_mld_vif_from_mac80211(info->control.vif); + return mld_vif->nan.mcast_data_sta.queue_id; default: WARN_ONCE(1, "Unsupported vif type\n"); break; @@ -1018,14 +1031,11 @@ void iwl_mld_tx_from_txq(struct iwl_mld *mld, struct ieee80211_txq *txq) rcu_read_unlock(); } -static void iwl_mld_hwrate_to_tx_rate(struct iwl_mld *mld, - __le32 rate_n_flags_fw, +static void iwl_mld_hwrate_to_tx_rate(u32 rate_n_flags, struct ieee80211_tx_info *info) { enum nl80211_band band = info->band; struct ieee80211_tx_rate *tx_rate = &info->status.rates[0]; - u32 rate_n_flags = iwl_v3_rate_from_v2_v3(rate_n_flags_fw, - mld->fw_rates_ver_3); u32 sgi = rate_n_flags & RATE_MCS_SGI_MSK; u32 chan_width = rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK; u32 format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; @@ -1156,7 +1166,8 @@ void iwl_mld_handle_tx_resp_notif(struct iwl_mld *mld, iwl_dbg_tlv_time_point(&mld->fwrt, tp, NULL); } - iwl_mld_hwrate_to_tx_rate(mld, tx_resp->initial_rate, info); + iwl_mld_hwrate_to_tx_rate(le32_to_cpu(tx_resp->initial_rate), + info); if (likely(!iwl_mld_time_sync_frame(mld, skb, hdr->addr1))) ieee80211_tx_status_skb(mld->hw, skb); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c index 1b67836b1fac..3a14ca5e512a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c @@ -261,20 +261,28 @@ iwl_mvm_ftm_target_chandef_v2(struct iwl_mvm *mvm, switch (peer->chandef.width) { case NL80211_CHAN_WIDTH_20_NOHT: - *format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY; - *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_LEGACY, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_20: - *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_40: - *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - *format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_40MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_80: - *format_bw = IWL_LOCATION_FRAME_FORMAT_VHT; - *format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_VHT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_80MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_160: cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, @@ -282,8 +290,10 @@ iwl_mvm_ftm_target_chandef_v2(struct iwl_mvm *mvm, IWL_FW_CMD_VER_UNKNOWN); if (cmd_ver >= 13) { - *format_bw = IWL_LOCATION_FRAME_FORMAT_HE; - *format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HE, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_160MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; } fallthrough; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c index 83f6e508a094..ae7a163c81c9 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2024 Intel Corporation + * Copyright (C) 2018-2024, 2026 Intel Corporation */ #include <net/cfg80211.h> #include <linux/etherdevice.h> @@ -54,27 +54,37 @@ static int iwl_mvm_ftm_responder_set_bw_v2(struct cfg80211_chan_def *chandef, { switch (chandef->width) { case NL80211_CHAN_WIDTH_20_NOHT: - *format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY; - *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_LEGACY, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_20: - *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); break; case NL80211_CHAN_WIDTH_40: - *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; - *format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_40MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); break; case NL80211_CHAN_WIDTH_80: - *format_bw = IWL_LOCATION_FRAME_FORMAT_VHT; - *format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_VHT, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_80MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); break; case NL80211_CHAN_WIDTH_160: if (cmd_ver >= 9) { - *format_bw = IWL_LOCATION_FRAME_FORMAT_HE; - *format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS; + *format_bw = u8_encode_bits(IWL_LOCATION_FRAME_FORMAT_HE, + IWL_LOCATION_FMT_BW_FORMAT); + *format_bw |= u8_encode_bits(IWL_LOCATION_BW_160MHZ, + IWL_LOCATION_FMT_BW_BANDWIDTH); *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); break; } diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 402ba5dee8b2..be89b84204fb 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -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-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -469,7 +469,7 @@ struct iwl_mvm_vif { struct dentry *dbgfs_slink; struct iwl_dbgfs_pm dbgfs_pm; struct iwl_dbgfs_bf dbgfs_bf; - struct iwl_mac_power_cmd mac_pwr_cmd; + struct iwl_mac_power_cmd_v2 mac_pwr_cmd; int dbgfs_quota_min; bool ftm_unprotected; #endif diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c index ae177477b201..726477336bb4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -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-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -335,7 +335,7 @@ static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = { RX_HANDLER_GRP(STATISTICS_GROUP, STATISTICS_OPER_NOTIF, iwl_mvm_handle_rx_system_oper_stats, RX_HANDLER_ASYNC_LOCKED_WIPHY, - struct iwl_system_statistics_notif_oper), + struct iwl_system_statistics_notif_oper_v3), RX_HANDLER_GRP(STATISTICS_GROUP, STATISTICS_OPER_PART1_NOTIF, iwl_mvm_handle_rx_system_oper_part1_stats, RX_HANDLER_ASYNC_LOCKED, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/power.c b/drivers/net/wireless/intel/iwlwifi/mvm/power.c index 610de29b7be0..46792c508753 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/power.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/power.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2019, 2021-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2019, 2021-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -83,7 +83,7 @@ void iwl_mvm_beacon_filter_set_cqm_params(struct iwl_mvm *mvm, } static void iwl_mvm_power_log(struct iwl_mvm *mvm, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { IWL_DEBUG_POWER(mvm, "Sending power table command on mac id 0x%X for power level %d, flags = 0x%X\n", @@ -121,7 +121,7 @@ static void iwl_mvm_power_log(struct iwl_mvm *mvm, static void iwl_mvm_power_configure_uapsd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); enum ieee80211_ac_numbers ac; @@ -296,7 +296,7 @@ static bool iwl_mvm_power_is_radar(struct ieee80211_bss_conf *link_conf) static void iwl_mvm_power_config_skip_dtim(struct iwl_mvm *mvm, struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { struct ieee80211_bss_conf *link_conf; unsigned int min_link_skip = ~0; @@ -344,7 +344,7 @@ static void iwl_mvm_power_config_skip_dtim(struct iwl_mvm *mvm, static void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, - struct iwl_mac_power_cmd *cmd) + struct iwl_mac_power_cmd_v2 *cmd) { int dtimper, bi; int keep_alive; @@ -466,7 +466,7 @@ static void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, static int iwl_mvm_power_send_cmd(struct iwl_mvm *mvm, struct ieee80211_vif *vif) { - struct iwl_mac_power_cmd cmd = {}; + struct iwl_mac_power_cmd_v2 cmd = {}; iwl_mvm_power_build_cmd(mvm, vif, &cmd); iwl_mvm_power_log(mvm, &cmd); @@ -717,7 +717,7 @@ int iwl_mvm_power_mac_dbgfs_read(struct iwl_mvm *mvm, int bufsz) { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); - struct iwl_mac_power_cmd cmd = {}; + struct iwl_mac_power_cmd_v2 cmd = {}; int pos = 0; mutex_lock(&mvm->mutex); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c index f7b620136c85..e05e531b09f0 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c @@ -325,11 +325,11 @@ void iwl_mvm_ptp_remove(struct iwl_mvm *mvm) mvm->ptp_data.ptp_clock_info.name, ptp_clock_index(mvm->ptp_data.ptp_clock)); + cancel_delayed_work_sync(&mvm->ptp_data.dwork); ptp_clock_unregister(mvm->ptp_data.ptp_clock); mvm->ptp_data.ptp_clock = NULL; memset(&mvm->ptp_data.ptp_clock_info, 0, sizeof(mvm->ptp_data.ptp_clock_info)); mvm->ptp_data.last_gp2 = 0; - cancel_delayed_work_sync(&mvm->ptp_data.dwork); } } diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c index d0c0faae0122..269c4b45de80 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c @@ -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-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -757,8 +757,9 @@ iwl_mvm_update_tcm_from_stats(struct iwl_mvm *mvm, __le32 *air_time_le, spin_unlock(&mvm->tcm.lock); } -static void iwl_mvm_handle_per_phy_stats(struct iwl_mvm *mvm, - struct iwl_stats_ntfy_per_phy *per_phy) +static void +iwl_mvm_handle_per_phy_stats(struct iwl_mvm *mvm, + struct iwl_stats_ntfy_per_phy_v1 *per_phy) { int i; @@ -937,7 +938,7 @@ void iwl_mvm_handle_rx_system_oper_stats(struct iwl_mvm *mvm, { u8 average_energy[IWL_STATION_COUNT_MAX]; struct iwl_rx_packet *pkt = rxb_addr(rxb); - struct iwl_system_statistics_notif_oper *stats; + struct iwl_system_statistics_notif_oper_v3 *stats; int i; u32 notif_ver = iwl_fw_lookup_notif_ver(mvm->fw, STATISTICS_GROUP, STATISTICS_OPER_NOTIF, 0); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index dc99e7ac4726..608100bc6b11 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -537,6 +537,8 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct pci_device_id iwl_hw_card_ids[] = { {IWL_PCI_DEVICE(0xA840, 0x4314, iwl_bz_mac_cfg)}, {IWL_PCI_DEVICE(0xA840, 0x1775, iwl_bz_mac_cfg)}, {IWL_PCI_DEVICE(0xA840, 0x1776, iwl_bz_mac_cfg)}, + {IWL_PCI_DEVICE(0xA840, 0x1735, iwl_bz_mac_cfg)}, + {IWL_PCI_DEVICE(0xA840, 0x1736, iwl_bz_mac_cfg)}, {IWL_PCI_DEVICE(0x7740, PCI_ANY_ID, iwl_bz_mac_cfg)}, {IWL_PCI_DEVICE(0x4D40, PCI_ANY_ID, iwl_bz_mac_cfg)}, @@ -1052,6 +1054,8 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_dev_info iwl_dev_info_table[] = { IWL_DEV_INFO(iwl_rf_fm, iwl_killer_be1750i_name, SUBDEV(0x1772)), IWL_DEV_INFO(iwl_rf_fm, iwl_killer_be1790s_name, SUBDEV(0x1791)), IWL_DEV_INFO(iwl_rf_fm, iwl_killer_be1790i_name, SUBDEV(0x1792)), + IWL_DEV_INFO(iwl_rf_fm_160mhz, iwl_killer_be1730x_name, SUBDEV(0x1730)), + IWL_DEV_INFO(iwl_rf_fm_160mhz, iwl_killer_be1730x_name, SUBDEV(0x1731)), /* Killer discrete */ IWL_DEV_INFO(iwl_rf_fm, iwl_killer_be1750w_name, @@ -1069,7 +1073,7 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_dev_info iwl_dev_info_table[] = { /* PE RF */ IWL_DEV_INFO(iwl_rf_pe, iwl_bn201_name, RF_TYPE(PE)), - IWL_DEV_INFO(iwl_rf_pe, iwl_be223_name, RF_TYPE(PE), + IWL_DEV_INFO(iwl_rf_pe_no_uhr, iwl_be223_name, RF_TYPE(PE), SUBDEV_MASKED(0x0524, 0xFFF)), IWL_DEV_INFO(iwl_rf_pe, iwl_bn203_name, RF_TYPE(PE), SUBDEV_MASKED(0x0324, 0xFFF)), @@ -1077,6 +1081,8 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_dev_info iwl_dev_info_table[] = { /* Killer */ IWL_DEV_INFO(iwl_rf_wh, iwl_killer_be1775s_name, SUBDEV(0x1776)), IWL_DEV_INFO(iwl_rf_wh, iwl_killer_be1775i_name, SUBDEV(0x1775)), + IWL_DEV_INFO(iwl_rf_wh_160mhz, iwl_killer_be1735x_name, SUBDEV(0x1735)), + IWL_DEV_INFO(iwl_rf_wh_160mhz, iwl_killer_be1735x_name, SUBDEV(0x1736)), IWL_DEV_INFO(iwl_rf_pe, iwl_killer_bn1850w2_name, SUBDEV(0x1851)), IWL_DEV_INFO(iwl_rf_pe, iwl_killer_bn1850i_name, SUBDEV(0x1852)), @@ -1239,8 +1245,12 @@ static int _iwl_pci_resume(struct device *device, bool restore) u32 scratch = iwl_read32(trans, CSR_FUNC_SCRATCH); if (!(scratch & CSR_FUNC_SCRATCH_POWER_OFF_MASK) || - scratch == ~0U) + scratch == ~0U) { device_was_powered_off = true; + IWL_DEBUG_WOWLAN(trans, + "Scratch 0x%08x indicates device was powered off\n", + scratch); + } } else { /* * bh are re-enabled by iwl_trans_pcie_release_nic_access, diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h index 7b7b35e442f9..abc0c831d1ca 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2003-2015, 2018-2025 Intel Corporation + * Copyright (C) 2003-2015, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -287,6 +287,106 @@ enum iwl_pcie_imr_status { IMR_D2S_ERROR, }; +/* + * 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 a 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 BDs + * @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 overlaid 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_pcie_txqs - TX queues data * @@ -1086,6 +1186,8 @@ u32 iwl_trans_pcie_read_prph(struct iwl_trans *trans, u32 reg); void iwl_trans_pcie_write_prph(struct iwl_trans *trans, u32 addr, u32 val); int iwl_trans_pcie_read_mem(struct iwl_trans *trans, u32 addr, void *buf, int dwords); +int iwl_trans_pcie_read_mem_no_grab(struct iwl_trans *trans, u32 addr, + void *buf, u32 dwords); int iwl_trans_pcie_sw_reset(struct iwl_trans *trans, bool retake_ownership); struct iwl_trans_dump_data * iwl_trans_pcie_dump_data(struct iwl_trans *trans, u32 dump_mask, @@ -1153,6 +1255,9 @@ int iwl_trans_pcie_copy_imr(struct iwl_trans *trans, int iwl_trans_pcie_rxq_dma_data(struct iwl_trans *trans, int queue, struct iwl_trans_rxq_dma_data *data); +int iwl_trans_pcie_send_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd); + static inline bool iwl_pcie_gen1_is_pm_supported(struct iwl_trans *trans) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c index 64262bcca55d..0e324aeb9055 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include "iwl-trans.h" #include "iwl-prph.h" @@ -327,6 +327,13 @@ static void iwl_pcie_get_rf_name(struct iwl_trans *trans) else pos = scnprintf(buf, buflen, "WH"); break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_PE): + if (SILICON_Z_STEP == + CSR_HW_RFID_STEP(trans->info.hw_rf_id)) + pos = scnprintf(buf, buflen, "PETC"); + else + pos = scnprintf(buf, buflen, "PE"); + break; default: return; } diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c index a05f60f9224b..1c4ee76d8387 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2007-2015, 2018-2024 Intel Corporation + * Copyright (C) 2007-2015, 2018-2024, 2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2016-2017 Intel Deutschland GmbH */ @@ -141,10 +141,10 @@ static void iwl_pcie_alloc_fw_monitor_block(struct iwl_trans *trans, return; if (power != max_power) - IWL_ERR(trans, - "Sorry - debug buffer is only %luK while you requested %luK\n", - (unsigned long)BIT(power - 10), - (unsigned long)BIT(max_power - 10)); + IWL_INFO(trans, + "Sorry - debug buffer is only %luK while you requested %luK\n", + (unsigned long)BIT(power - 10), + (unsigned long)BIT(max_power - 10)); fw_mon->block = block; fw_mon->physical = physical; @@ -2044,7 +2044,7 @@ iwl_trans_pcie_call_prod_reset_dsm(struct pci_dev *pdev, u16 cmd, u16 value) 0xDD, 0x26, 0xB5, 0xFD); if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), &dsm_guid, ACPI_DSM_REV, - DSM_INTERNAL_FUNC_PRODUCT_RESET)) + BIT(DSM_INTERNAL_FUNC_PRODUCT_RESET))) return ERR_PTR(-ENODEV); return iwl_acpi_get_dsm_object(&pdev->dev, ACPI_DSM_REV, @@ -2424,6 +2424,15 @@ bool iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans) return false; } +static void iwl_trans_pcie_resched_with_nic_access(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_unlock_bh(&trans_pcie->reg_lock); + cond_resched(); + spin_lock_bh(&trans_pcie->reg_lock); +} + void __releases(nic_access_nobh) iwl_trans_pcie_release_nic_access(struct iwl_trans *trans) { @@ -2506,6 +2515,51 @@ int iwl_trans_pcie_read_mem(struct iwl_trans *trans, u32 addr, return 0; } +int iwl_trans_pcie_read_mem_no_grab(struct iwl_trans *trans, u32 addr, + void *buf, u32 dwords) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); +#define IWL_MAX_HW_ERRS 5 + unsigned int num_consec_hw_errors = 0; + u32 offs = 0; + u32 *vals = buf; + + lockdep_assert_held(&trans_pcie->reg_lock); + + while (offs < dwords) { + /* limit the time we spin here under lock to 1/2s */ + unsigned long end = jiffies + HZ / 2; + bool resched = false; + + iwl_write32(trans, HBUS_TARG_MEM_RADDR, + addr + 4 * offs); + + while (offs < dwords) { + vals[offs] = iwl_read32(trans, HBUS_TARG_MEM_RDAT); + + if (iwl_trans_is_hw_error_value(vals[offs])) + num_consec_hw_errors++; + else + num_consec_hw_errors = 0; + + if (num_consec_hw_errors >= IWL_MAX_HW_ERRS) + return -EIO; + + offs++; + + if (time_after(jiffies, end)) { + resched = true; + break; + } + } + + if (resched) + iwl_trans_pcie_resched_with_nic_access(trans); + } + + return 0; +} + int iwl_trans_pcie_read_config32(struct iwl_trans *trans, u32 ofs, u32 *val) { @@ -3201,8 +3255,7 @@ static ssize_t iwl_dbgfs_reset_write(struct file *file, return -EINVAL; trans->request_top_reset = 1; } - iwl_op_mode_nic_error(trans->op_mode, IWL_ERR_TYPE_DEBUGFS); - iwl_trans_schedule_reset(trans, IWL_ERR_TYPE_DEBUGFS); + iwl_trans_fw_error(trans, IWL_ERR_TYPE_DEBUGFS); return count; } @@ -4000,6 +4053,30 @@ static void get_crf_id(struct iwl_trans *iwl_trans, else sd_reg_ver_addr = SD_REG_VER; + /* wait until the device is ready to access the prph registers */ + if (iwl_trans->mac_cfg->device_family == IWL_DEVICE_FAMILY_DR || + iwl_trans->mac_cfg->device_family == IWL_DEVICE_FAMILY_SC) { + u32 req = iwl_read_umac_prph_no_grab(iwl_trans, + WFPM_RSRCS_4PHS_REQ_STTS); + int ret; + + if (!(req & RSRC_REQ_CNVR_TOP)) { + IWL_ERR(iwl_trans, + "WFPM_RSRCS_4PHS_REQ_STTS bit 6 is clear 0x%x\n", + req); + return; + } + + ret = iwl_poll_umac_prph_bits_no_grab(iwl_trans, + WFPM_RSRCS_4PHS_ACK_STTS, + RSRC_ACK_CNVR_TOP, + RSRC_ACK_CNVR_TOP, + 50 * 1000); + if (ret < 0) + IWL_ERR(iwl_trans, + "WFPM_RSRCS_4PHS_ACK_STTS bit 6 is clear\n"); + } + /* Enable access to peripheral registers */ val = iwl_read_umac_prph_no_grab(iwl_trans, WFPM_CTRL_REG); val |= WFPM_AUX_CTL_AUX_IF_MAC_OWNER_MSK; diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c index 70ce31d7c76e..623ddde8c8e5 100644 --- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c +++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c @@ -1568,28 +1568,23 @@ int mwifiex_send_rgpower_table(struct mwifiex_private *priv, const u8 *data, return -EINVAL; } - if (start_raw) { - while ((*pos != '\r' && *pos != '\n') && - (token = strsep((char **)&pos, " "))) { - if (ptr - hostcmd->cmd >= - MWIFIEX_SIZE_OF_CMD_BUFFER) { - mwifiex_dbg( - adapter, ERROR, - "%s: hostcmd is larger than %d, aborting\n", - __func__, MWIFIEX_SIZE_OF_CMD_BUFFER); - return -ENOMEM; - } + while ((*pos != '\r' && *pos != '\n') && + (token = strsep((char **)&pos, " "))) { + if (ptr - hostcmd->cmd >= MWIFIEX_SIZE_OF_CMD_BUFFER) { + mwifiex_dbg(adapter, ERROR, + "%s: hostcmd is larger than %d, aborting\n", + __func__, MWIFIEX_SIZE_OF_CMD_BUFFER); + return -ENOMEM; + } - ret = kstrtou8(token, 16, ptr); - if (ret < 0) { - mwifiex_dbg( - adapter, ERROR, - "%s: failed to parse hostcmd %d token: %s\n", - __func__, ret, token); - return ret; - } - ptr++; + ret = kstrtou8(token, 16, ptr); + if (ret < 0) { + mwifiex_dbg(adapter, ERROR, + "%s: failed to parse hostcmd %d token: %s\n", + __func__, ret, token); + return ret; } + ptr++; } } diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 67a33e5e7d54..6ea082157307 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -356,6 +356,14 @@ static struct net_device *hwsim_mon; /* global monitor netdev */ .hw_value = (_freq), \ } +#define CHANS1G(_freq, _offset, _flags) { \ + .band = NL80211_BAND_S1GHZ, \ + .center_freq = (_freq), \ + .freq_offset = (_offset), \ + .hw_value = (_freq), \ + .flags = (_flags), \ +} + static const struct ieee80211_channel hwsim_channels_2ghz[] = { CHAN2G(2412), /* Channel 1 */ CHAN2G(2417), /* Channel 2 */ @@ -490,7 +498,38 @@ static const struct ieee80211_channel hwsim_channels_6ghz[] = { static_assert(HWSIM_NUM_CHANNELS_6GHZ == ARRAY_SIZE(hwsim_channels_6ghz), "Inconsistent 6 GHz channel count"); -static struct ieee80211_channel hwsim_channels_s1g[HWSIM_NUM_S1G_CHANNELS_US]; +/* + * US 2024 channels (op class 1). Additionally to emulate real world + * US operation, the edgeband 1MHz channels (1, 51) are marked as NO_PRIMARY. + */ +static const struct ieee80211_channel hwsim_channels_s1g[] = { + CHANS1G(902, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), /* Channel 1 */ + CHANS1G(903, 500, 0), /* Channel 3 */ + CHANS1G(904, 500, 0), /* Channel 5 */ + CHANS1G(905, 500, 0), /* Channel 7 */ + CHANS1G(906, 500, 0), /* Channel 9 */ + CHANS1G(907, 500, 0), /* Channel 11 */ + CHANS1G(908, 500, 0), /* Channel 13 */ + CHANS1G(909, 500, 0), /* Channel 15 */ + CHANS1G(910, 500, 0), /* Channel 17 */ + CHANS1G(911, 500, 0), /* Channel 19 */ + CHANS1G(912, 500, 0), /* Channel 21 */ + CHANS1G(913, 500, 0), /* Channel 23 */ + CHANS1G(914, 500, 0), /* Channel 25 */ + CHANS1G(915, 500, 0), /* Channel 27 */ + CHANS1G(916, 500, 0), /* Channel 29 */ + CHANS1G(917, 500, 0), /* Channel 31 */ + CHANS1G(918, 500, 0), /* Channel 33 */ + CHANS1G(919, 500, 0), /* Channel 35 */ + CHANS1G(920, 500, 0), /* Channel 37 */ + CHANS1G(921, 500, 0), /* Channel 39 */ + CHANS1G(922, 500, 0), /* Channel 41 */ + CHANS1G(923, 500, 0), /* Channel 43 */ + CHANS1G(924, 500, 0), /* Channel 45 */ + CHANS1G(925, 500, 0), /* Channel 47 */ + CHANS1G(926, 500, 0), /* Channel 49 */ + CHANS1G(927, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), /* Channel 51 */ +}; static const struct ieee80211_sta_s1g_cap hwsim_s1g_cap = { .s1g = true, @@ -519,19 +558,6 @@ static const struct ieee80211_sta_s1g_cap hwsim_s1g_cap = { 0 }, }; -static void hwsim_init_s1g_channels(struct ieee80211_channel *chans) -{ - int ch, freq; - - for (ch = 0; ch < ARRAY_SIZE(hwsim_channels_s1g); ch++) { - freq = 902000 + (ch + 1) * 500; - chans[ch].band = NL80211_BAND_S1GHZ; - chans[ch].center_freq = KHZ_TO_MHZ(freq); - chans[ch].freq_offset = freq % 1000; - chans[ch].hw_value = ch + 1; - } -} - static const struct ieee80211_rate hwsim_rates[] = { { .bitrate = 10 }, { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, @@ -2816,7 +2842,10 @@ static int mac80211_hwsim_sta_add(struct ieee80211_hw *hw, hwsim_check_magic(vif); hwsim_set_sta_magic(sta); - mac80211_hwsim_sta_rc_update(hw, vif, &sta->deflink, 0); + + /* For now, don't run RC update on STAs on an S1G interface */ + if (!vif->cfg.s1g) + mac80211_hwsim_sta_rc_update(hw, vif, &sta->deflink, 0); if (sta->valid_links) { WARN(hweight16(sta->valid_links) > 1, @@ -3505,8 +3534,13 @@ static int mac80211_hwsim_change_vif_links(struct ieee80211_hw *hw, if (!new_links) add |= BIT(0); - for_each_set_bit(i, &rem, IEEE80211_MLD_MAX_NUM_LINKS) + wiphy_dbg(hw->wiphy, "%s:\n", __func__); + + for_each_set_bit(i, &rem, IEEE80211_MLD_MAX_NUM_LINKS) { mac80211_hwsim_config_mac_nl(hw, old[i]->addr, false); + wiphy_dbg(hw->wiphy, + " link [%d/%pM] removed\n", i, old[i]->addr); + } for_each_set_bit(i, &add, IEEE80211_MLD_MAX_NUM_LINKS) { struct ieee80211_bss_conf *link_conf; @@ -3516,6 +3550,8 @@ static int mac80211_hwsim_change_vif_links(struct ieee80211_hw *hw, continue; mac80211_hwsim_config_mac_nl(hw, link_conf->addr, true); + wiphy_dbg(hw->wiphy, + " link [%d/%pM] added\n", i, link_conf->addr); } return 0; @@ -7440,8 +7476,6 @@ static int __init init_mac80211_hwsim(void) if (err) goto out_exit_virtio; - hwsim_init_s1g_channels(hwsim_channels_s1g); - for (i = 0; i < radios; i++) { struct hwsim_new_radio_params param = { 0 }; diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index c64a99131954..23d46cd57137 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -2216,6 +2216,64 @@ ieee80211_find_or_create_chanctx(struct ieee80211_sub_if_data *sdata, assign_on_failure, radio_idx); } +static bool +ieee80211_nan_evac_chanctx_filter(struct ieee80211_chanctx *ctx, + void *filter_data) +{ + return ctx == filter_data; +} + +static int +ieee80211_try_nan_chan_evacuation(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode mode, + u8 radar_detect_width) +{ + struct ieee80211_sub_if_data *nan_sdata = ieee80211_find_nan_sdata(local); + struct ieee80211_check_combinations_data comb_data = { + .chandef = chandef, + .chanmode = mode, + .radar_detect = radar_detect_width, + .radio_idx = -1, + .chanctx_filter = ieee80211_nan_evac_chanctx_filter, + }; + struct ieee80211_nan_channel *evac_chan; + struct ieee80211_chanctx *evac_ctx; + int ret; + + if (!nan_sdata) + return -ENOENT; + + /* Find an evacuation candidate... */ + evac_chan = ieee80211_nan_find_evac_chan(local, nan_sdata, NULL); + if (!evac_chan || WARN_ON(!evac_chan->chanctx_conf)) + return -ENOENT; + + evac_ctx = container_of(evac_chan->chanctx_conf, + struct ieee80211_chanctx, conf); + + /* + * ... check combinations assuming to-be-evacuated ctx is already + * released + */ + comb_data.filter_data = evac_ctx; + ret = ieee80211_check_combinations_ext(sdata, &comb_data); + if (ret < 0) + return ret; + + /* That helped! Let's evacuate the channel */ + ieee80211_nan_evacuate_channel(nan_sdata, evac_chan); + + /* Re-check, just to be on the safe-side */ + ret = ieee80211_check_combinations(sdata, chandef, mode, + radar_detect_width, -1); + + /* That shouldn't happen, we checked before! */ + WARN_ON(ret); + return ret; +} + int _ieee80211_link_use_channel(struct ieee80211_link_data *link, const struct ieee80211_chan_req *chanreq, enum ieee80211_chanctx_mode mode, @@ -2247,8 +2305,15 @@ int _ieee80211_link_use_channel(struct ieee80211_link_data *link, ret = ieee80211_check_combinations(sdata, &chanreq->oper, mode, radar_detect_width, -1); - if (ret < 0) - goto out; + if (ret < 0) { + /* Let's check if evacuating a NAN channel will help */ + ret = ieee80211_try_nan_chan_evacuation(local, sdata, + &chanreq->oper, + mode, + radar_detect_width); + if (ret < 0) + goto out; + } if (!local->in_reconfig) __ieee80211_link_release_channel(link, false); @@ -2256,15 +2321,6 @@ int _ieee80211_link_use_channel(struct ieee80211_link_data *link, ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, mode, assign_on_failure, &reused_ctx); if (IS_ERR(ctx)) { - /* Try to evacuate a NAN channel to free up a chanctx */ - if (ieee80211_nan_try_evacuate(&local->hw, NULL)) - ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, - mode, - assign_on_failure, - &reused_ctx); - } - - if (IS_ERR(ctx)) { ret = PTR_ERR(ctx); goto out; } diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index fc4424b125c1..18a101710432 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1946,6 +1946,11 @@ ieee80211_vif_get_num_mcast_if(struct ieee80211_sub_if_data *sdata) return -1; } +static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata) +{ + return test_bit(SDATA_STATE_RUNNING, &sdata->state); +} + int ieee80211_hw_config(struct ieee80211_local *local, int radio_idx, u32 changed); int ieee80211_hw_conf_chan(struct ieee80211_local *local); @@ -2054,6 +2059,29 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata, struct cfg80211_nan_peer_sched *sched); void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched); void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata); +struct ieee80211_nan_channel * +ieee80211_nan_find_evac_chan(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_chanctx *ctx); +void ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata, + struct ieee80211_nan_channel *nan_channel); + +static inline struct ieee80211_sub_if_data * +ieee80211_find_nan_sdata(struct ieee80211_local *local) +{ + struct ieee80211_sub_if_data *sdata; + + lockdep_assert_wiphy(local->hw.wiphy); + + /* Find the NAN interface - there can only be one */ + list_for_each_entry(sdata, &local->interfaces, list) { + if (ieee80211_sdata_running(sdata) && + sdata->vif.type == NL80211_IFTYPE_NAN) + return sdata; + } + + return NULL; +} /* scan/BSS handling */ void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work); @@ -2155,11 +2183,6 @@ void ieee80211_recalc_txpower(struct ieee80211_link_data *link, bool update_bss); void ieee80211_recalc_offload(struct ieee80211_local *local); -static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata) -{ - return test_bit(SDATA_STATE_RUNNING, &sdata->state); -} - /* link handling */ void ieee80211_link_setup(struct ieee80211_link_data *link); void ieee80211_link_init(struct ieee80211_sub_if_data *sdata, @@ -2818,10 +2841,36 @@ int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata, struct cfg80211_csa_settings *csa_settings); void ieee80211_recalc_sb_count(struct ieee80211_sub_if_data *sdata, u64 tsf); void ieee80211_recalc_dtim(struct ieee80211_sub_if_data *sdata, u64 tsf); -int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, - const struct cfg80211_chan_def *chandef, - enum ieee80211_chanctx_mode chanmode, - u8 radar_detect, int radio_idx); + +struct ieee80211_check_combinations_data { + const struct cfg80211_chan_def *chandef; + enum ieee80211_chanctx_mode chanmode; + u8 radar_detect; + int radio_idx; + bool (*chanctx_filter)(struct ieee80211_chanctx *ctx, + void *filter_data); + void *filter_data; +}; + +int ieee80211_check_combinations_ext(struct ieee80211_sub_if_data *sdata, + struct ieee80211_check_combinations_data *data); + +static inline int +ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, + const struct cfg80211_chan_def *chandef, + enum ieee80211_chanctx_mode chanmode, + u8 radar_detect, int radio_idx) +{ + struct ieee80211_check_combinations_data data = { + .chandef = chandef, + .chanmode = chanmode, + .radar_detect = radar_detect, + .radio_idx = radio_idx, + }; + + return ieee80211_check_combinations_ext(sdata, &data); +} + int ieee80211_max_num_channels(struct ieee80211_local *local, int radio_idx); u32 ieee80211_get_radio_mask(struct wiphy *wiphy, struct net_device *dev); void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local, @@ -2926,6 +2975,10 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata, struct ieee80211_chan_req *chanreq, struct cfg80211_chan_def *ap_chandef, unsigned long *userspace_selectors); +int ieee80211_parse_neg_ttlm(struct ieee80211_sub_if_data *sdata, + const struct ieee80211_ttlm_elem *ttlm, + struct ieee80211_neg_ttlm *neg_ttlm, + u8 *direction); #else #define EXPORT_SYMBOL_IF_MAC80211_KUNIT(sym) #define VISIBLE_IF_MAC80211_KUNIT static diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index f60f59d1b37d..e8d6f6a95c0a 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -8189,7 +8189,7 @@ ieee80211_send_neg_ttlm_res(struct ieee80211_sub_if_data *sdata, ieee80211_tx_skb(sdata, skb); } -static int +VISIBLE_IF_MAC80211_KUNIT int ieee80211_parse_neg_ttlm(struct ieee80211_sub_if_data *sdata, const struct ieee80211_ttlm_elem *ttlm, struct ieee80211_neg_ttlm *neg_ttlm, @@ -8270,6 +8270,7 @@ ieee80211_parse_neg_ttlm(struct ieee80211_sub_if_data *sdata, } return 0; } +EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_parse_neg_ttlm); static void ieee80211_process_neg_ttlm_req(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c index cea620aaee6a..1800bb96dd29 100644 --- a/net/mac80211/nan.c +++ b/net/mac80211/nan.c @@ -712,7 +712,7 @@ out: return ret; } -static void +void ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata, struct ieee80211_nan_channel *nan_channel) { @@ -754,33 +754,20 @@ ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata, ieee80211_free_chanctx(sdata->local, ctx, false); } -bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, - struct ieee80211_chanctx_conf *conf) +struct ieee80211_nan_channel * +ieee80211_nan_find_evac_chan(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct ieee80211_chanctx *ctx) { - struct ieee80211_sub_if_data *sdata = NULL, *tmp; - struct ieee80211_local *local = hw_to_local(hw); - struct ieee80211_nan_channel *evac_chan = NULL; struct ieee80211_nan_sched_cfg *sched_cfg; - struct ieee80211_chanctx *ctx = NULL; + struct ieee80211_nan_channel *evac_chan = NULL; int min_slot_count = INT_MAX; int usable_channels = 0; lockdep_assert_wiphy(local->hw.wiphy); - if (conf) - ctx = container_of(conf, struct ieee80211_chanctx, conf); - - /* Find the NAN interface - there can only be one */ - list_for_each_entry(tmp, &local->interfaces, list) { - if (ieee80211_sdata_running(tmp) && - tmp->vif.type == NL80211_IFTYPE_NAN) { - sdata = tmp; - break; - } - } - - if (!sdata) - return false; + if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_NAN)) + return NULL; sched_cfg = &sdata->vif.cfg.nan_sched; @@ -823,10 +810,34 @@ bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, /* No suitable NAN channel found */ if (!evac_chan) - return false; + return NULL; /* NAN needs at least one remaining usable channel after evacuation */ if (usable_channels < 2) + return NULL; + + return evac_chan; +} + +bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf) +{ + struct ieee80211_local *local = hw_to_local(hw); + struct ieee80211_sub_if_data *sdata = + ieee80211_find_nan_sdata(local); + struct ieee80211_nan_channel *evac_chan; + struct ieee80211_chanctx *ctx = NULL; + + lockdep_assert_wiphy(local->hw.wiphy); + + if (!sdata) + return false; + + if (conf) + ctx = container_of(conf, struct ieee80211_chanctx, conf); + + evac_chan = ieee80211_nan_find_evac_chan(local, sdata, ctx); + if (!evac_chan) return false; ieee80211_nan_evacuate_channel(sdata, evac_chan); diff --git a/net/mac80211/tests/.kunitconfig b/net/mac80211/tests/.kunitconfig new file mode 100644 index 000000000000..ab2cc5cfc1f5 --- /dev/null +++ b/net/mac80211/tests/.kunitconfig @@ -0,0 +1,4 @@ +CONFIG_KUNIT=y +CONFIG_CFG80211=y +CONFIG_MAC80211=y +CONFIG_MAC80211_KUNIT_TEST=y diff --git a/net/mac80211/tests/Makefile b/net/mac80211/tests/Makefile index 3c7f874e5c41..2e9ade90f7b6 100644 --- a/net/mac80211/tests/Makefile +++ b/net/mac80211/tests/Makefile @@ -1,3 +1,3 @@ -mac80211-tests-y += module.o util.o elems.o mfp.o tpe.o chan-mode.o s1g_tim.o +mac80211-tests-y += module.o util.o elems.o mfp.o tpe.o chan-mode.o s1g_tim.o ttlm.o obj-$(CONFIG_MAC80211_KUNIT_TEST) += mac80211-tests.o diff --git a/net/mac80211/tests/ttlm.c b/net/mac80211/tests/ttlm.c new file mode 100644 index 000000000000..18d0592b13d9 --- /dev/null +++ b/net/mac80211/tests/ttlm.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * KUnit tests for negotiated TTLM (TID-To-Link Mapping) parsing + * + * Copyright (C) 2026 Michael Bommarito <michael.bommarito@gmail.com> + */ +#include <kunit/test.h> +#include <linux/ieee80211.h> +#include "../ieee80211_i.h" + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +/* + * Build a negotiated TTLM element in caller-supplied buffer. + * + * @buf: destination buffer (must be at least elem_size bytes) + * @elem_size: sizeof(ttlm_elem) + 1 (presence byte) + npresent * bm_size + * @presence: link_map_presence bitmask; each set bit => one map follows + * @bm_size: bytes per map (1 or 2); 2 => LINK_MAP_SIZE bit clear + * @maps: array of npresent u16 maps, one per set bit in presence + * + * Control field encodes direction=BOTH; no switch-time, no expected-dur, + * no DEF_LINK_MAP. LINK_MAP_SIZE bit is set iff bm_size==1. + * + * Returns pointer to the ieee80211_ttlm_elem at buf. + */ +static const struct ieee80211_ttlm_elem * +build_neg_ttlm_elem(u8 *buf, size_t elem_size, + u8 presence, u8 bm_size, const u16 *maps) +{ + struct ieee80211_ttlm_elem *t = (void *)buf; + u8 control; + u8 *pos; + int i, tid; + + memset(buf, 0, elem_size); + + control = IEEE80211_TTLM_DIRECTION_BOTH; /* bits [1:0] = 2 */ + if (bm_size == 1) + control |= IEEE80211_TTLM_CONTROL_LINK_MAP_SIZE; + + t->control = control; + + pos = (u8 *)t->optional; + *pos++ = presence; + + i = 0; + for (tid = 0; tid < IEEE80211_TTLM_NUM_TIDS; tid++) { + if (!(presence & BIT(tid))) + continue; + if (bm_size == 1) + *pos = (u8)maps[i]; + else + put_unaligned_le16(maps[i], pos); + pos += bm_size; + i++; + } + + return t; +} + +/* + * sparse_presence_no_oob_read - BIT(0)|BIT(7) presence, bm_size=2 + * + * Only TID 0 and TID 7 have maps; TIDs 1-6 are absent. Element length + * is exactly 6 bytes (1 control + 1 presence + 2 * 2-byte maps). + * + * Pre-fix the parser advanced pos by bm_size AFTER the switch() block + * (i.e. unconditionally for every TID), so when processing TID 7 it + * had already advanced 6 * bm_size = 12 bytes past the presence byte + * for the absent TIDs before reading the TID-7 map - 14 bytes past the + * end of the 2-byte TID-7 map. Under KASAN that is a slab-out-of-bounds. + * + * After the fix pos is advanced only inside the presence-bit branch so + * the cursor lands exactly at end-of-element after processing TID 7. + */ +static void sparse_presence_no_oob_read(struct kunit *test) +{ + /* + * presence = BIT(0)|BIT(7): 2 maps present. + * elem_size = sizeof(ttlm_elem) + 1 (presence) + 2*2 (maps) = 6. + */ + const u8 presence = BIT(0) | BIT(7); + const u8 bm_size = 2; + const int npresent = 2; + const size_t elem_size = sizeof(struct ieee80211_ttlm_elem) + + 1 + npresent * bm_size; + /* + * Allocate exact-size buffer so a pre-fix OOB read walks into the + * KASAN red zone immediately after the allocation. + */ + u8 *buf = kunit_kzalloc(test, elem_size, GFP_KERNEL); + const struct ieee80211_ttlm_elem *ttlm; + struct ieee80211_neg_ttlm neg_ttlm = {}; + /* Non-zero maps so the parser does not reject with -EINVAL. */ + const u16 maps[2] = { 0x0001, 0x0001 }; + u8 direction = 0; + int ret; + + KUNIT_ASSERT_NOT_NULL(test, buf); + + ttlm = build_neg_ttlm_elem(buf, elem_size, presence, bm_size, maps); + + /* + * Pass NULL for sdata: the only sdata dereference in this code path + * is inside mlme_dbg() on error returns, which are guarded by + * MAC80211_MLME_DEBUG == 0 in non-debug builds and by the dead-code + * eliminator in KUnit builds. The success path does not touch sdata. + */ + ret = ieee80211_parse_neg_ttlm(NULL, ttlm, &neg_ttlm, &direction); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)direction, IEEE80211_TTLM_DIRECTION_BOTH); + /* TID 0: map present */ + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.downlink[0], 0x0001); + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.uplink[0], 0x0001); + /* TID 3: absent => map should be 0 */ + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.downlink[3], 0); + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.uplink[3], 0); + /* TID 7: map present */ + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.downlink[7], 0x0001); + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.uplink[7], 0x0001); +} + +/* + * dense_presence_baseline - presence=0xff (all 8 TIDs), bm_size=2 + * + * Every TID has a map; this is the dense layout the parser handled + * correctly even before the fix. Confirms the cursor-advance fix + * does not regress the already-correct path. + */ +static void dense_presence_baseline(struct kunit *test) +{ + const u8 presence = 0xff; + const u8 bm_size = 2; + const int npresent = 8; + const size_t elem_size = sizeof(struct ieee80211_ttlm_elem) + + 1 + npresent * bm_size; + u8 *buf = kunit_kzalloc(test, elem_size, GFP_KERNEL); + const struct ieee80211_ttlm_elem *ttlm; + struct ieee80211_neg_ttlm neg_ttlm = {}; + const u16 maps[8] = { + 0x0003, 0x0003, 0x0003, 0x0003, + 0x0003, 0x0003, 0x0003, 0x0003, + }; + u8 direction = 0; + int ret; + + KUNIT_ASSERT_NOT_NULL(test, buf); + + ttlm = build_neg_ttlm_elem(buf, elem_size, presence, bm_size, maps); + + ret = ieee80211_parse_neg_ttlm(NULL, ttlm, &neg_ttlm, &direction); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)direction, IEEE80211_TTLM_DIRECTION_BOTH); + /* All TIDs present: every downlink/uplink entry must be 0x0003. */ + for (int tid = 0; tid < IEEE80211_TTLM_NUM_TIDS; tid++) { + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.downlink[tid], 0x0003); + KUNIT_EXPECT_EQ(test, (int)neg_ttlm.uplink[tid], 0x0003); + } +} + +static struct kunit_case mac80211_ttlm_test_cases[] = { + KUNIT_CASE(sparse_presence_no_oob_read), + KUNIT_CASE(dense_presence_baseline), + {} +}; + +static struct kunit_suite mac80211_ttlm = { + .name = "mac80211-ttlm", + .test_cases = mac80211_ttlm_test_cases, +}; + +kunit_test_suite(mac80211_ttlm); diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 255905f971c8..2a7ab269687a 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -4260,7 +4260,10 @@ static int ieee80211_fill_ifcomb_params(struct ieee80211_local *local, struct iface_combination_params *params, const struct cfg80211_chan_def *chandef, - struct ieee80211_sub_if_data *sdata) + struct ieee80211_sub_if_data *sdata, + bool (*chanctx_filter)(struct ieee80211_chanctx *ctx, + void *filter_data), + void *filter_data) { struct ieee80211_sub_if_data *sdata_iter; struct ieee80211_chanctx *ctx; @@ -4281,6 +4284,10 @@ ieee80211_fill_ifcomb_params(struct ieee80211_local *local, cfg80211_chandef_compatible(chandef, &ctx->conf.def)) continue; + if (chanctx_filter && + chanctx_filter(ctx, filter_data)) + continue; + params->num_different_channels++; } @@ -4305,26 +4312,25 @@ ieee80211_fill_ifcomb_params(struct ieee80211_local *local, return total; } -int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, - const struct cfg80211_chan_def *chandef, - enum ieee80211_chanctx_mode chanmode, - u8 radar_detect, int radio_idx) +int ieee80211_check_combinations_ext(struct ieee80211_sub_if_data *sdata, + struct ieee80211_check_combinations_data *data) { - bool shared = chanmode == IEEE80211_CHANCTX_SHARED; + const struct cfg80211_chan_def *chandef = data->chandef; + bool shared = data->chanmode == IEEE80211_CHANCTX_SHARED; struct ieee80211_local *local = sdata->local; enum nl80211_iftype iftype = sdata->wdev.iftype; struct iface_combination_params params = { - .radar_detect = radar_detect, - .radio_idx = radio_idx, + .radar_detect = data->radar_detect, + .radio_idx = data->radio_idx, }; int total; lockdep_assert_wiphy(local->hw.wiphy); - if (WARN_ON(hweight32(radar_detect) > 1)) + if (WARN_ON(hweight32(data->radar_detect) > 1)) return -EINVAL; - if (WARN_ON(chandef && chanmode == IEEE80211_CHANCTX_SHARED && + if (WARN_ON(chandef && data->chanmode == IEEE80211_CHANCTX_SHARED && !chandef->chan)) return -EINVAL; @@ -4343,7 +4349,7 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, /* Always allow software iftypes */ if (cfg80211_iftype_allowed(local->hw.wiphy, iftype, 0, 1)) { - if (radar_detect) + if (data->radar_detect) return -EINVAL; return 0; } @@ -4356,7 +4362,9 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata, total = ieee80211_fill_ifcomb_params(local, ¶ms, shared ? chandef : NULL, - sdata); + sdata, + data->chanctx_filter, + data->filter_data); if (total == 1 && !params.radar_detect) return 0; @@ -4383,7 +4391,7 @@ int ieee80211_max_num_channels(struct ieee80211_local *local, int radio_idx) lockdep_assert_wiphy(local->hw.wiphy); - ieee80211_fill_ifcomb_params(local, ¶ms, NULL, NULL); + ieee80211_fill_ifcomb_params(local, ¶ms, NULL, NULL, NULL, NULL); err = cfg80211_iter_combinations(local->hw.wiphy, ¶ms, ieee80211_iter_max_chans, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index c272a2fbad03..cdb5e9b77143 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -13730,6 +13730,16 @@ static int nl80211_testmode_dump(struct sk_buff *skb, err = -ENOENT; goto out_err; } + + /* + * The wiphy may have moved netns between dumpit + * invocations (via NL80211_CMD_SET_WIPHY_NETNS), so + * re-check that it still matches the caller's netns. + */ + if (!net_eq(wiphy_net(&rdev->wiphy), sock_net(skb->sk))) { + err = -ENODEV; + goto out_err; + } } else { attrbuf = kzalloc_objs(*attrbuf, NUM_NL80211_ATTR); if (!attrbuf) { @@ -17771,6 +17781,15 @@ static int nl80211_prepare_vendor_dump(struct sk_buff *skb, if (!wiphy) return -ENODEV; + + /* + * The wiphy may have moved netns between dumpit + * invocations (via NL80211_CMD_SET_WIPHY_NETNS), so + * re-check that it still matches the caller's netns. + */ + if (!net_eq(wiphy_net(wiphy), sock_net(skb->sk))) + return -ENODEV; + *rdev = wiphy_to_rdev(wiphy); *wdev = NULL; diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c index c3aa56977243..5dbf3ef4b257 100644 --- a/net/wireless/wext-compat.c +++ b/net/wireless/wext-compat.c @@ -16,6 +16,7 @@ #include <linux/if_arp.h> #include <linux/etherdevice.h> #include <linux/slab.h> +#include <linux/string.h> #include <net/iw_handler.h> #include <net/cfg80211.h> #include <net/cfg80211-wext.h> @@ -27,7 +28,7 @@ int cfg80211_wext_giwname(struct net_device *dev, struct iw_request_info *info, union iwreq_data *wrqu, char *extra) { - strcpy(wrqu->name, "IEEE 802.11"); + strscpy(wrqu->name, "IEEE 802.11"); return 0; } |
