* [PATCH v10 06/21] wifi: nxpwifi: add scan support
From: Jeff Chen @ 2026-03-05 14:39 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, johannes, francesco, wyatt.hsu, s.hauer, Jeff Chen
In-Reply-To: <20260305143939.3724868-1-jeff.chen_1@nxp.com>
Add scan support for active, passive, and background scanning across
station and AP roles.
Build scan commands with SSID and BSSID filters, channel lists, probe
parameters, and optional vendor elements. Parse firmware scan results
and update BSS descriptors, including security, QoS, and HE/HT/VHT
capabilities.
Integrate with cfg80211 for reporting scan results, handling hidden SSIDs,
and managing scan state transitions. Add background scan configuration and
query support, including RSSI thresholds, repeat counts, and scan
intervals. Implement scan abort, completion handling, and internal scan
queue management.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/scan.c | 2763 +++++++++++++++++++++++
1 file changed, 2763 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/scan.c
diff --git a/drivers/net/wireless/nxp/nxpwifi/scan.c b/drivers/net/wireless/nxp/nxpwifi/scan.c
new file mode 100644
index 000000000000..d2313df13069
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/scan.c
@@ -0,0 +1,2763 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nxpwifi: scan ioctl and command handling
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#include "cfg.h"
+#include "util.h"
+#include "fw.h"
+#include "main.h"
+#include "cmdevt.h"
+#include "11n.h"
+#include "11ac.h"
+#include "11ax.h"
+#include "cfg80211.h"
+
+/* The maximum number of channels the firmware can scan per command */
+#define NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN 14
+
+#define NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD 4
+
+/* Memory needed to store a max sized Channel List TLV for a firmware scan */
+#define CHAN_TLV_MAX_SIZE (sizeof(struct nxpwifi_ie_types_header) \
+ + (NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN \
+ * sizeof(struct nxpwifi_chan_scan_param_set)))
+
+/* Memory needed to store supported rate */
+#define RATE_TLV_MAX_SIZE (sizeof(struct nxpwifi_ie_types_rates_param_set) \
+ + HOSTCMD_SUPPORTED_RATES)
+
+/* Memory needed to store a max number/size WildCard SSID TLV for a firmware scan */
+#define WILDCARD_SSID_TLV_MAX_SIZE \
+ (NXPWIFI_MAX_SSID_LIST_LENGTH * \
+ (sizeof(struct nxpwifi_ie_types_wildcard_ssid_params) \
+ + IEEE80211_MAX_SSID_LEN))
+
+/* Maximum memory needed for a nxpwifi_scan_cmd_config with all TLVs at max */
+#define MAX_SCAN_CFG_ALLOC (sizeof(struct nxpwifi_scan_cmd_config) \
+ + sizeof(struct nxpwifi_ie_types_num_probes) \
+ + sizeof(struct nxpwifi_ie_types_htcap) \
+ + sizeof(struct nxpwifi_ie_types_vhtcap) \
+ + sizeof(struct nxpwifi_ie_types_he_cap) \
+ + CHAN_TLV_MAX_SIZE \
+ + RATE_TLV_MAX_SIZE \
+ + WILDCARD_SSID_TLV_MAX_SIZE)
+
+union nxpwifi_scan_cmd_config_tlv {
+ /* Scan configuration (variable length) */
+ struct nxpwifi_scan_cmd_config config;
+ /* Max allocated block */
+ u8 config_alloc_buf[MAX_SCAN_CFG_ALLOC];
+};
+
+#define NXPWIFI_WPA_CIPHER_SUITE_TKIP SUITE(WLAN_OUI_MICROSOFT, 2)
+#define NXPWIFI_WPA_CIPHER_SUITE_CCMP SUITE(WLAN_OUI_MICROSOFT, 4)
+
+static void
+_dbg_security_flags(int log_level, const char *func, const char *desc,
+ struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ _nxpwifi_dbg(priv->adapter, log_level,
+ "info: %s: %s:\twpa_ie=%#x wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s\tEncMode=%#x privacy=%#x\n",
+ func, desc,
+ bss_desc->bcn_wpa_ie ?
+ bss_desc->bcn_wpa_ie->vend_hdr.element_id : 0,
+ bss_desc->bcn_rsn_ie ?
+ bss_desc->bcn_rsn_ie->id : 0,
+ priv->sec_info.wep_enabled ? "e" : "d",
+ priv->sec_info.wpa_enabled ? "e" : "d",
+ priv->sec_info.wpa2_enabled ? "e" : "d",
+ priv->sec_info.encryption_mode,
+ bss_desc->privacy);
+}
+
+#define dbg_security_flags(mask, desc, priv, bss_desc) \
+ _dbg_security_flags(NXPWIFI_DBG_##mask, __func__, desc, priv, bss_desc)
+
+/* Parse a WPA/RSN element and check whether its PTK list contains the OUI */
+static u8
+nxpwifi_search_oui_in_ie(struct ie_body *iebody, u8 *oui)
+{
+ u8 count;
+
+ count = iebody->ptk_cnt[0];
+
+ /*
+ * PTK may contain multiple OUIs; iterate through the list and compare
+ * each one
+ */
+ while (count) {
+ if (!memcmp(iebody->ptk_body, oui, sizeof(iebody->ptk_body)))
+ return NXPWIFI_OUI_PRESENT;
+
+ --count;
+ if (count)
+ iebody = (struct ie_body *)((u8 *)iebody +
+ sizeof(iebody->ptk_body));
+ }
+
+ pr_debug("info: %s: OUI is not found in PTK\n", __func__);
+ return NXPWIFI_OUI_NOT_PRESENT;
+}
+
+/* Check whether the RSN IE is present and if its PTK list contains the OUI */
+static u8
+nxpwifi_is_rsn_oui_present(struct nxpwifi_bssdescriptor *bss_desc,
+ u32 cipher)
+{
+ struct ie_body *iebody;
+ u8 ret = NXPWIFI_OUI_NOT_PRESENT;
+ __be32 oui = cpu_to_be32(cipher);
+
+ if (bss_desc->bcn_rsn_ie) {
+ iebody = (struct ie_body *)
+ (((u8 *)bss_desc->bcn_rsn_ie->data) +
+ RSN_GTK_OUI_OFFSET);
+ ret = nxpwifi_search_oui_in_ie(iebody, (u8 *)&oui);
+ if (ret)
+ return ret;
+ }
+ return ret;
+}
+
+/* Check if the WPA IE exists and whether its PTK list contains the OUI */
+static u8
+nxpwifi_is_wpa_oui_present(struct nxpwifi_bssdescriptor *bss_desc, u32 cipher)
+{
+ struct ie_body *iebody;
+ u8 ret = NXPWIFI_OUI_NOT_PRESENT;
+ __be32 oui = cpu_to_be32(cipher);
+
+ if (bss_desc->bcn_wpa_ie) {
+ iebody = (struct ie_body *)((u8 *)bss_desc->bcn_wpa_ie->data +
+ WPA_GTK_OUI_OFFSET);
+ ret = nxpwifi_search_oui_in_ie(iebody, (u8 *)&oui);
+ if (ret)
+ return ret;
+ }
+ return ret;
+}
+
+/* Check whether both driver and BSS operate with no security */
+static bool
+nxpwifi_is_bss_no_sec(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled &&
+ !priv->sec_info.wpa2_enabled &&
+ !bss_desc->bcn_rsn_ie &&
+ !bss_desc->bcn_wpa_ie &&
+ !priv->sec_info.encryption_mode && !bss_desc->privacy) {
+ return true;
+ }
+ return false;
+}
+
+/* Check whether static WEP is enabled and the BSS privacy setting matches */
+static bool
+nxpwifi_is_bss_static_wep(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ if (priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled &&
+ !priv->sec_info.wpa2_enabled && bss_desc->privacy) {
+ return true;
+ }
+ return false;
+}
+
+/* Check whether WPA is enabled and the BSS contains a WPA IE */
+static bool
+nxpwifi_is_bss_wpa(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ if (!priv->sec_info.wep_enabled && priv->sec_info.wpa_enabled &&
+ !priv->sec_info.wpa2_enabled &&
+ bss_desc->bcn_wpa_ie) {
+ dbg_security_flags(INFO, "WPA", priv, bss_desc);
+ return true;
+ }
+ return false;
+}
+
+/* Check whether WPA2 is enabled and the BSS includes an RSN IE */
+static bool
+nxpwifi_is_bss_wpa2(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled &&
+ priv->sec_info.wpa2_enabled &&
+ bss_desc->bcn_rsn_ie) {
+ /*
+ * Some APs (e.g., WRT54G) may omit the privacy bit even when
+ * using WPA2
+ */
+ dbg_security_flags(ERROR, "WPA2", priv, bss_desc);
+ return true;
+ }
+ return false;
+}
+
+/* Check dynamic WEP: enabled in driver, privacy set, and no WPA/RSN IE present */
+static bool
+nxpwifi_is_bss_dynamic_wep(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled &&
+ !priv->sec_info.wpa2_enabled &&
+ !bss_desc->bcn_wpa_ie &&
+ !bss_desc->bcn_rsn_ie &&
+ priv->sec_info.encryption_mode && bss_desc->privacy) {
+ dbg_security_flags(INFO, "dynamic", priv, bss_desc);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Check whether a scanned network is compatible with the driver's security
+ * configuration. The decision considers WEP, WPA, WPA2, privacy settings,
+ * and whether HT must be disabled when required (e.g., no AES).
+ *
+ * General rules:
+ * - Open networks: always compatible.
+ * - WPA-only: compatible; HT disabled if AES is not supported.
+ * - WPA2-only: compatible; HT disabled if AES is not supported.
+ * - Static WEP: compatible; HT disabled.
+ * - Dynamic WEP: compatible when privacy is enabled.
+ *
+ * Note: Compatibility is not enforced during roaming except for security mode.
+ */
+static int
+nxpwifi_is_network_compatible(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc, u32 mode)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+
+ bss_desc->disable_11n = false;
+
+ /* Skip compatibility checks while roaming */
+ if (priv->media_connected &&
+ priv->bss_mode == NL80211_IFTYPE_STATION &&
+ bss_desc->bss_mode == NL80211_IFTYPE_STATION)
+ return 0;
+
+ if (priv->wps.session_enable) {
+ nxpwifi_dbg(adapter, IOCTL,
+ "info: return success directly in WPS period\n");
+ return 0;
+ }
+
+ if (bss_desc->chan_sw_ie_present) {
+ nxpwifi_dbg(adapter, INFO,
+ "Don't connect to AP with WLAN_EID_CHANNEL_SWITCH\n");
+ return -EPERM;
+ }
+
+ if (bss_desc->bss_mode == mode) {
+ if (nxpwifi_is_bss_no_sec(priv, bss_desc)) {
+ return 0;
+ } else if (nxpwifi_is_bss_static_wep(priv, bss_desc)) {
+ nxpwifi_dbg(adapter, INFO,
+ "info: Disable 11n in WEP mode.\n");
+ bss_desc->disable_11n = true;
+ return 0;
+ } else if (nxpwifi_is_bss_wpa(priv, bss_desc)) {
+ if (((priv->config_bands & BAND_GN ||
+ priv->config_bands & BAND_AN) &&
+ bss_desc->bcn_ht_cap) &&
+ !nxpwifi_is_wpa_oui_present(bss_desc,
+ NXPWIFI_WPA_CIPHER_SUITE_CCMP)) {
+ if (nxpwifi_is_wpa_oui_present
+ (bss_desc, NXPWIFI_WPA_CIPHER_SUITE_TKIP)) {
+ nxpwifi_dbg(adapter, INFO,
+ "info: Disable 11n if AES\t"
+ "is not supported by AP\n");
+ bss_desc->disable_11n = true;
+ } else {
+ return -EINVAL;
+ }
+ }
+ return 0;
+ } else if (nxpwifi_is_bss_wpa2(priv, bss_desc)) {
+ if (((priv->config_bands & BAND_GN ||
+ priv->config_bands & BAND_AN) &&
+ bss_desc->bcn_ht_cap) &&
+ !nxpwifi_is_rsn_oui_present(bss_desc,
+ WLAN_CIPHER_SUITE_CCMP)) {
+ if (nxpwifi_is_rsn_oui_present
+ (bss_desc, WLAN_CIPHER_SUITE_TKIP)) {
+ nxpwifi_dbg(adapter, INFO,
+ "info: Disable 11n if AES\t"
+ "is not supported by AP\n");
+ bss_desc->disable_11n = true;
+ } else if (nxpwifi_is_rsn_oui_present
+ (bss_desc, WLAN_CIPHER_SUITE_GCMP_256) ||
+ nxpwifi_is_rsn_oui_present
+ (bss_desc, WLAN_CIPHER_SUITE_CCMP_256)) {
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+ }
+ return 0;
+ } else if (nxpwifi_is_bss_dynamic_wep(priv, bss_desc)) {
+ return 0;
+ }
+
+ /* Security mismatch */
+ dbg_security_flags(ERROR, "failed", priv, bss_desc);
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * Build the channel list for scanning based on region and band settings.
+ * Used when a scan request does not specify its own channel list.
+ */
+static int
+nxpwifi_scan_create_channel_list(struct nxpwifi_private *priv,
+ const struct nxpwifi_user_scan_cfg
+ *user_scan_in,
+ struct nxpwifi_chan_scan_param_set
+ *scan_chan_list,
+ u8 filtered_scan)
+{
+ enum nl80211_band band;
+ struct ieee80211_supported_band *sband;
+ struct ieee80211_channel *ch;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ int chan_idx = 0, i;
+ u16 scan_time = 0;
+
+ if (user_scan_in)
+ scan_time = (u16)user_scan_in->chan_list[0].scan_time;
+
+ for (band = 0; (band < NUM_NL80211_BANDS) ; band++) {
+ if (!priv->wdev.wiphy->bands[band])
+ continue;
+
+ sband = priv->wdev.wiphy->bands[band];
+
+ for (i = 0; (i < sband->n_channels) ; i++) {
+ ch = &sband->channels[i];
+ if (ch->flags & IEEE80211_CHAN_DISABLED)
+ continue;
+ scan_chan_list[chan_idx].band_cfg = band;
+
+ if (scan_time)
+ scan_chan_list[chan_idx].max_scan_time =
+ cpu_to_le16(scan_time);
+ else if ((ch->flags & IEEE80211_CHAN_NO_IR) ||
+ (ch->flags & IEEE80211_CHAN_RADAR))
+ scan_chan_list[chan_idx].max_scan_time =
+ cpu_to_le16(adapter->passive_scan_time);
+ else
+ scan_chan_list[chan_idx].max_scan_time =
+ cpu_to_le16(adapter->active_scan_time);
+
+ if (ch->flags & IEEE80211_CHAN_NO_IR)
+ scan_chan_list[chan_idx].chan_scan_mode_bmap |=
+ (NXPWIFI_PASSIVE_SCAN | NXPWIFI_HIDDEN_SSID_REPORT);
+ else
+ scan_chan_list[chan_idx].chan_scan_mode_bmap &=
+ ~NXPWIFI_PASSIVE_SCAN;
+
+ scan_chan_list[chan_idx].chan_number = (u32)ch->hw_value;
+ scan_chan_list[chan_idx].chan_scan_mode_bmap |=
+ NXPWIFI_DISABLE_CHAN_FILT;
+
+ if (filtered_scan &&
+ !((ch->flags & IEEE80211_CHAN_NO_IR) ||
+ (ch->flags & IEEE80211_CHAN_RADAR)))
+ scan_chan_list[chan_idx].max_scan_time =
+ cpu_to_le16(adapter->specific_scan_time);
+
+ chan_idx++;
+ }
+ }
+ return chan_idx;
+}
+
+/*
+ * Build the channel-list TLV for bgscan based on region and band settings.
+ */
+static int
+nxpwifi_bgscan_create_channel_list(struct nxpwifi_private *priv,
+ const struct nxpwifi_bg_scan_cfg
+ *bgscan_cfg_in,
+ struct nxpwifi_chan_scan_param_set
+ *scan_chan_list)
+{
+ enum nl80211_band band;
+ struct ieee80211_supported_band *sband;
+ struct ieee80211_channel *ch;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ int chan_idx = 0, i;
+ u16 scan_time = 0, specific_scan_time = adapter->specific_scan_time;
+
+ if (bgscan_cfg_in)
+ scan_time = (u16)bgscan_cfg_in->chan_list[0].scan_time;
+
+ for (band = 0; (band < NUM_NL80211_BANDS); band++) {
+ if (!priv->wdev.wiphy->bands[band])
+ continue;
+
+ sband = priv->wdev.wiphy->bands[band];
+
+ for (i = 0; (i < sband->n_channels) ; i++) {
+ ch = &sband->channels[i];
+ if (ch->flags & IEEE80211_CHAN_DISABLED)
+ continue;
+ scan_chan_list[chan_idx].band_cfg = band;
+
+ if (scan_time)
+ scan_chan_list[chan_idx].max_scan_time =
+ cpu_to_le16(scan_time);
+ else if (ch->flags & IEEE80211_CHAN_NO_IR)
+ scan_chan_list[chan_idx].max_scan_time =
+ cpu_to_le16(adapter->passive_scan_time);
+ else
+ scan_chan_list[chan_idx].max_scan_time =
+ cpu_to_le16(specific_scan_time);
+
+ if (ch->flags & IEEE80211_CHAN_NO_IR)
+ scan_chan_list[chan_idx].chan_scan_mode_bmap |=
+ NXPWIFI_PASSIVE_SCAN;
+ else
+ scan_chan_list[chan_idx].chan_scan_mode_bmap &=
+ ~NXPWIFI_PASSIVE_SCAN;
+
+ scan_chan_list[chan_idx].chan_number = (u32)ch->hw_value;
+ chan_idx++;
+ }
+ }
+ return chan_idx;
+}
+
+/* Append the rate TLV to the scan configuration command */
+static int
+nxpwifi_append_rate_tlv(struct nxpwifi_private *priv,
+ struct nxpwifi_scan_cmd_config *scan_cfg_out,
+ u8 radio)
+{
+ struct nxpwifi_ie_types_rates_param_set *rates_tlv;
+ u8 rates[NXPWIFI_SUPPORTED_RATES], *tlv_pos;
+ u32 rates_size;
+
+ memset(rates, 0, sizeof(rates));
+
+ tlv_pos = (u8 *)scan_cfg_out->tlv_buf + scan_cfg_out->tlv_buf_len;
+
+ if (priv->scan_request)
+ rates_size = nxpwifi_get_rates_from_cfg80211(priv, rates,
+ radio);
+ else
+ rates_size = nxpwifi_get_supported_rates(priv, rates);
+
+ nxpwifi_dbg(priv->adapter, CMD,
+ "info: SCAN_CMD: Rates size = %d\n",
+ rates_size);
+ rates_tlv = (struct nxpwifi_ie_types_rates_param_set *)tlv_pos;
+ rates_tlv->header.type = cpu_to_le16(WLAN_EID_SUPP_RATES);
+ rates_tlv->header.len = cpu_to_le16((u16)rates_size);
+ memcpy(rates_tlv->rates, rates, rates_size);
+ scan_cfg_out->tlv_buf_len += sizeof(rates_tlv->header) + rates_size;
+
+ return rates_size;
+}
+
+/*
+ * Build and send multiple scan commands by chunking channel TLVs per scan
+ * limit.
+ */
+static int
+nxpwifi_scan_channel_list(struct nxpwifi_private *priv,
+ u32 max_chan_per_scan, u8 filtered_scan,
+ struct nxpwifi_scan_cmd_config *scan_cfg_out,
+ struct nxpwifi_ie_types_chan_list_param_set *tlv_o,
+ struct nxpwifi_chan_scan_param_set *scan_chan_list)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ int ret = 0;
+ struct nxpwifi_chan_scan_param_set *tmp_chan_list;
+ u32 tlv_idx, rates_size, cmd_no;
+ u32 total_scan_time;
+ u32 done_early;
+ u8 radio_type;
+
+ if (!scan_cfg_out || !tlv_o || !scan_chan_list) {
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "info: Scan: Null detect: %p, %p, %p\n",
+ scan_cfg_out, tlv_o, scan_chan_list);
+ return -EINVAL;
+ }
+
+ /* Check csa channel expiry before preparing scan list */
+ nxpwifi_11h_get_csa_closed_channel(priv);
+
+ tlv_o->header.type = cpu_to_le16(TLV_TYPE_CHANLIST);
+
+ tmp_chan_list = scan_chan_list;
+
+ /*
+ * Iterate through the channel list and send a firmware scan command for
+ * each group of max_chan_per_scan channels, or individually for
+ * channels 1, 6, and 11 when configured.
+ */
+ while (tmp_chan_list->chan_number) {
+ tlv_idx = 0;
+ total_scan_time = 0;
+ radio_type = 0;
+ tlv_o->header.len = 0;
+ done_early = false;
+
+ /*
+ * Build the channel TLV for the scan command. Continue adding
+ * channel TLVs until one of the following conditions is met:
+ * - tlv_idx reaches the maximum allowed per scan command
+ * - the next channel is 0 (end of the desired channel list)
+ * - done_early is set (used for per-channel scanning of 1, 6,
+ * and 11)
+ */
+ while (tlv_idx < max_chan_per_scan &&
+ tmp_chan_list->chan_number && !done_early) {
+ if (tmp_chan_list->chan_number == priv->csa_chan) {
+ tmp_chan_list++;
+ continue;
+ }
+
+ radio_type = tmp_chan_list->band_cfg;
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: Scan: Chan(%3d), Band(%d),\t"
+ "Mode(%d, %d), Dur(%d)\n",
+ tmp_chan_list->chan_number,
+ tmp_chan_list->band_cfg,
+ tmp_chan_list->chan_scan_mode_bmap
+ & NXPWIFI_PASSIVE_SCAN,
+ (tmp_chan_list->chan_scan_mode_bmap
+ & NXPWIFI_DISABLE_CHAN_FILT) >> 1,
+ le16_to_cpu(tmp_chan_list->max_scan_time));
+
+ /* Copy the current channel TLV into the command being prepared */
+ memcpy(&tlv_o->chan_scan_param[tlv_idx], tmp_chan_list,
+ sizeof(*tlv_o->chan_scan_param));
+
+ /*
+ * Increment the TLV header length by the size
+ * appended
+ */
+ le16_unaligned_add_cpu(&tlv_o->header.len,
+ sizeof(*tlv_o->chan_scan_param));
+
+ /*
+ * The tlv buffer length is set to the number of bytes
+ * of the between the channel tlv pointer and the start
+ * of the tlv buffer. This compensates for any TLVs
+ * that were appended before the channel list.
+ */
+ scan_cfg_out->tlv_buf_len =
+ (u32)((u8 *)tlv_o - scan_cfg_out->tlv_buf);
+
+ scan_cfg_out->tlv_buf_len +=
+ (sizeof(tlv_o->header)
+ + le16_to_cpu(tlv_o->header.len));
+
+ /* Advance the index for the channel TLV being constructed. */
+ tlv_idx++;
+
+ /* Count the total scan time per command */
+ total_scan_time +=
+ le16_to_cpu(tmp_chan_list->max_scan_time);
+
+ done_early = false;
+
+ /*
+ * Stop the loop if the current channel is one of 1, 6,
+ * or 11 and no SSID or BSSID filter is applied.
+ */
+ if (!filtered_scan &&
+ (tmp_chan_list->chan_number == 1 ||
+ tmp_chan_list->chan_number == 6 ||
+ tmp_chan_list->chan_number == 11))
+ done_early = true;
+
+ /* Advance the tmp pointer to the next channel to be scanned. */
+ tmp_chan_list++;
+
+ /*
+ * Stop the loop if the next channel is one of 1, 6,
+ * or 11. This causes that channel to be scanned alone
+ * in the next iteration.
+ */
+ if (!filtered_scan &&
+ (tmp_chan_list->chan_number == 1 ||
+ tmp_chan_list->chan_number == 6 ||
+ tmp_chan_list->chan_number == 11))
+ done_early = true;
+ }
+
+ /* Ensure the total scan time does not exceed the scan-command timeout. */
+ if (total_scan_time > NXPWIFI_MAX_TOTAL_SCAN_TIME) {
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "total scan time %dms\t"
+ "is over limit (%dms), scan skipped\n",
+ total_scan_time,
+ NXPWIFI_MAX_TOTAL_SCAN_TIME);
+ ret = -EINVAL;
+ break;
+ }
+
+ rates_size = nxpwifi_append_rate_tlv(priv, scan_cfg_out,
+ radio_type);
+
+ if (priv->adapter->ext_scan)
+ cmd_no = HOST_CMD_802_11_SCAN_EXT;
+ else
+ cmd_no = HOST_CMD_802_11_SCAN;
+
+ ret = nxpwifi_send_cmd(priv, cmd_no, HOST_ACT_GEN_SET,
+ 0, scan_cfg_out, false);
+
+ /*
+ * The rate element is updated for each scan command, but the
+ * same starting pointer is reused, so the previous rate element
+ * in scan_cfg_out->buf is overwritten.
+ */
+ scan_cfg_out->tlv_buf_len -=
+ sizeof(struct nxpwifi_ie_types_header) + rates_size;
+
+ if (ret) {
+ nxpwifi_cancel_pending_scan_cmd(adapter);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Build final scan config from user params, disabling missing filters and using
+ * defaults.
+ */
+static void
+nxpwifi_config_scan(struct nxpwifi_private *priv,
+ const struct nxpwifi_user_scan_cfg *user_scan_in,
+ struct nxpwifi_scan_cmd_config *scan_cfg_out,
+ struct nxpwifi_ie_types_chan_list_param_set **chan_list_out,
+ struct nxpwifi_chan_scan_param_set *scan_chan_list,
+ u8 *max_chan_per_scan, u8 *filtered_scan,
+ u8 *scan_current_only)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct nxpwifi_ie_types_num_probes *num_probes_tlv;
+ struct nxpwifi_ie_types_scan_chan_gap *chan_gap_tlv;
+ struct nxpwifi_ie_types_random_mac *random_mac_tlv;
+ struct nxpwifi_ie_types_wildcard_ssid_params *wildcard_ssid_tlv;
+ struct nxpwifi_ie_types_bssid_list *bssid_tlv;
+ struct nxpwifi_ie_types_extcap *ext_cap;
+ u8 *ext_capab;
+ u8 *tlv_pos;
+ u32 num_probes;
+ u32 ssid_len;
+ u32 chan_idx;
+ u32 scan_time;
+ u32 scan_type;
+ u16 scan_dur;
+ u8 channel;
+ u8 radio_type;
+ int i, vsid;
+ u8 ssid_filter;
+ struct nxpwifi_ie_types_htcap *ht_cap;
+ struct nxpwifi_ie_types_bss_mode *bss_mode;
+ struct nxpwifi_ie_types_vhtcap *vht_cap;
+ struct nxpwifi_ie_types_he_cap *he_cap;
+
+ /*
+ * tlv_buf_len is recalculated for each scan command. TLVs added in this
+ * routine are preserved because the send routine appends channel TLVs
+ * at chan_list_out. The difference between chan_list_out and the start
+ * of the TLV buffer determines the size of the TLVs added here.
+ */
+ scan_cfg_out->tlv_buf_len = 0;
+
+ /*
+ * Running TLV pointer. It is assigned to chan_list_out at the end of
+ * the function so later routines know where channel TLVs can be
+ * appended in the command buffer.
+ */
+ tlv_pos = scan_cfg_out->tlv_buf;
+
+ /*
+ * Initialize the scan as un-filtered; the flag is later set to TRUE
+ * below if a SSID or BSSID filter is sent in the command
+ */
+ *filtered_scan = false;
+
+ /*
+ * Initialize the scan as not being only on the current channel. If
+ * the channel list is customized, only contains one channel, and is
+ * the active channel, this is set true and data flow is not halted.
+ */
+ *scan_current_only = false;
+
+ if (user_scan_in) {
+ u8 tmpaddr[ETH_ALEN];
+
+ /*
+ * Default the ssid_filter flag to TRUE, set false under
+ * certain wildcard conditions and qualified by the existence
+ * of an SSID list before marking the scan as filtered
+ */
+ ssid_filter = true;
+
+ /*
+ * Set the BSS type scan filter, use Adapter setting if
+ * unset
+ */
+ scan_cfg_out->bss_mode =
+ (u8)(user_scan_in->bss_mode ?: adapter->scan_mode);
+
+ /*
+ * Set the number of probes to send, use Adapter setting
+ * if unset
+ */
+ num_probes = user_scan_in->num_probes ?: adapter->scan_probes;
+
+ /*
+ * Set the BSSID filter to the incoming configuration,
+ * if non-zero. If not set, it will remain disabled
+ * (all zeros).
+ */
+ memcpy(scan_cfg_out->specific_bssid,
+ user_scan_in->specific_bssid,
+ sizeof(scan_cfg_out->specific_bssid));
+
+ memcpy(tmpaddr, scan_cfg_out->specific_bssid, ETH_ALEN);
+
+ if (adapter->ext_scan &&
+ !is_zero_ether_addr(tmpaddr)) {
+ bssid_tlv =
+ (struct nxpwifi_ie_types_bssid_list *)tlv_pos;
+ bssid_tlv->header.type = cpu_to_le16(TLV_TYPE_BSSID);
+ bssid_tlv->header.len = cpu_to_le16(ETH_ALEN);
+ memcpy(bssid_tlv->bssid, user_scan_in->specific_bssid,
+ ETH_ALEN);
+ tlv_pos += sizeof(struct nxpwifi_ie_types_bssid_list);
+ }
+
+ for (i = 0; i < user_scan_in->num_ssids; i++) {
+ ssid_len = user_scan_in->ssid_list[i].ssid_len;
+
+ wildcard_ssid_tlv =
+ (struct nxpwifi_ie_types_wildcard_ssid_params *)
+ tlv_pos;
+ wildcard_ssid_tlv->header.type =
+ cpu_to_le16(TLV_TYPE_WILDCARDSSID);
+ wildcard_ssid_tlv->header.len =
+ cpu_to_le16((u16)(ssid_len + sizeof(u8)));
+
+ /*
+ * max_ssid_length = 0 tells firmware to perform
+ * specific scan for the SSID filled, whereas
+ * max_ssid_length = IEEE80211_MAX_SSID_LEN is for
+ * wildcard scan.
+ */
+ if (ssid_len)
+ wildcard_ssid_tlv->max_ssid_length = 0;
+ else
+ wildcard_ssid_tlv->max_ssid_length =
+ IEEE80211_MAX_SSID_LEN;
+
+ if (!memcmp(user_scan_in->ssid_list[i].ssid,
+ "DIRECT-", 7))
+ wildcard_ssid_tlv->max_ssid_length = 0xfe;
+
+ memcpy(wildcard_ssid_tlv->ssid,
+ user_scan_in->ssid_list[i].ssid, ssid_len);
+
+ tlv_pos += (sizeof(wildcard_ssid_tlv->header)
+ + le16_to_cpu(wildcard_ssid_tlv->header.len));
+
+ nxpwifi_dbg(adapter, INFO,
+ "info: scan: ssid[%d]: %s, %d\n",
+ i, wildcard_ssid_tlv->ssid,
+ wildcard_ssid_tlv->max_ssid_length);
+
+ /*
+ * Empty wildcard ssid with a maxlen will match many or
+ * potentially all SSIDs (maxlen == 32), therefore do
+ * not treat the scan as
+ * filtered.
+ */
+ if (!ssid_len && wildcard_ssid_tlv->max_ssid_length)
+ ssid_filter = false;
+ }
+
+ /*
+ * The default number of channels sent in the command is low to
+ * ensure the response buffer from the firmware does not
+ * truncate scan results. That is not an issue with an SSID
+ * or BSSID filter applied to the scan results in the firmware.
+ */
+ memcpy(tmpaddr, scan_cfg_out->specific_bssid, ETH_ALEN);
+ if ((i && ssid_filter) ||
+ !is_zero_ether_addr(tmpaddr))
+ *filtered_scan = true;
+
+ if (user_scan_in->scan_chan_gap) {
+ nxpwifi_dbg(adapter, INFO,
+ "info: scan: channel gap = %d\n",
+ user_scan_in->scan_chan_gap);
+ *max_chan_per_scan =
+ NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN;
+
+ chan_gap_tlv = (void *)tlv_pos;
+ chan_gap_tlv->header.type =
+ cpu_to_le16(TLV_TYPE_SCAN_CHANNEL_GAP);
+ chan_gap_tlv->header.len =
+ cpu_to_le16(sizeof(chan_gap_tlv->chan_gap));
+ chan_gap_tlv->chan_gap =
+ cpu_to_le16((user_scan_in->scan_chan_gap));
+ tlv_pos +=
+ sizeof(struct nxpwifi_ie_types_scan_chan_gap);
+ }
+
+ if (!is_zero_ether_addr(user_scan_in->random_mac)) {
+ random_mac_tlv = (void *)tlv_pos;
+ random_mac_tlv->header.type =
+ cpu_to_le16(TLV_TYPE_RANDOM_MAC);
+ random_mac_tlv->header.len =
+ cpu_to_le16(sizeof(random_mac_tlv->mac));
+ ether_addr_copy(random_mac_tlv->mac,
+ user_scan_in->random_mac);
+ tlv_pos +=
+ sizeof(struct nxpwifi_ie_types_random_mac);
+ }
+ } else {
+ scan_cfg_out->bss_mode = (u8)adapter->scan_mode;
+ num_probes = adapter->scan_probes;
+ }
+
+ /*
+ * If a specific BSSID or SSID is used, the number of channels in the
+ * scan command will be increased to the absolute maximum.
+ */
+ if (*filtered_scan) {
+ *max_chan_per_scan = NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN;
+ } else {
+ if (!priv->media_connected)
+ *max_chan_per_scan = NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD;
+ else
+ *max_chan_per_scan =
+ NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD / 2;
+ }
+
+ if (adapter->ext_scan) {
+ bss_mode = (struct nxpwifi_ie_types_bss_mode *)tlv_pos;
+ bss_mode->header.type = cpu_to_le16(TLV_TYPE_BSS_MODE);
+ bss_mode->header.len = cpu_to_le16(sizeof(bss_mode->bss_mode));
+ bss_mode->bss_mode = scan_cfg_out->bss_mode;
+ tlv_pos += sizeof(bss_mode->header) +
+ le16_to_cpu(bss_mode->header.len);
+ }
+
+ /*
+ * If the input config or adapter has the number of Probes set,
+ * add tlv
+ */
+ if (num_probes) {
+ nxpwifi_dbg(adapter, INFO,
+ "info: scan: num_probes = %d\n",
+ num_probes);
+
+ num_probes_tlv = (struct nxpwifi_ie_types_num_probes *)tlv_pos;
+ num_probes_tlv->header.type = cpu_to_le16(TLV_TYPE_NUMPROBES);
+ num_probes_tlv->header.len =
+ cpu_to_le16(sizeof(num_probes_tlv->num_probes));
+ num_probes_tlv->num_probes = cpu_to_le16((u16)num_probes);
+
+ tlv_pos += sizeof(num_probes_tlv->header) +
+ le16_to_cpu(num_probes_tlv->header.len);
+ }
+
+ if (ISSUPP_11NENABLED(priv->adapter->fw_cap_info) &&
+ (priv->config_bands & BAND_GN ||
+ priv->config_bands & BAND_AN)) {
+ ht_cap = (struct nxpwifi_ie_types_htcap *)tlv_pos;
+ memset(ht_cap, 0, sizeof(struct nxpwifi_ie_types_htcap));
+ ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY);
+ ht_cap->header.len =
+ cpu_to_le16(sizeof(struct ieee80211_ht_cap));
+ radio_type =
+ nxpwifi_band_to_radio_type(priv->config_bands);
+ nxpwifi_fill_cap_info(priv, radio_type, &ht_cap->ht_cap);
+ tlv_pos += sizeof(struct nxpwifi_ie_types_htcap);
+ }
+
+ if (ISSUPP_11ACENABLED(adapter->fw_cap_info) &&
+ (priv->config_bands & BAND_AAC)) {
+ vht_cap = (struct nxpwifi_ie_types_vhtcap *)tlv_pos;
+ memset(vht_cap, 0, sizeof(struct nxpwifi_ie_types_vhtcap));
+ vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY);
+ vht_cap->header.len = cpu_to_le16(sizeof(struct ieee80211_vht_cap));
+ nxpwifi_fill_vht_cap_tlv(priv, &vht_cap->vht_cap, priv->config_bands);
+ tlv_pos += sizeof(*vht_cap);
+ }
+
+ if (ISSUPP_11AXENABLED(adapter->fw_cap_ext) &&
+ (priv->config_bands & BAND_GAX ||
+ priv->config_bands & BAND_AAX)) {
+ he_cap = (struct nxpwifi_ie_types_he_cap *)tlv_pos;
+ memset(he_cap, 0, sizeof(struct nxpwifi_ie_types_he_cap));
+ tlv_pos += nxpwifi_fill_he_cap_tlv(priv, he_cap, priv->config_bands);
+ }
+
+ if (nxpwifi_is_sta_11ax_twt_req_supported(priv)) {
+ for (vsid = 0; vsid < NXPWIFI_MAX_VSIE_NUM; vsid++) {
+ if (priv->vs_ie[vsid].mask & NXPWIFI_VSIE_MASK_SCAN) {
+ ext_capab = (u8 *)cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY,
+ priv->vs_ie[vsid].ie,
+ sizeof(priv->vs_ie[vsid].ie));
+ break;
+ }
+ }
+
+ if (ext_capab) {
+ ext_capab += 2;
+ } else {
+ ext_cap = (struct nxpwifi_ie_types_extcap *)tlv_pos;
+ memset(ext_cap, 0, sizeof(struct nxpwifi_ie_types_extcap) +
+ NXPWIFI_EXT_CAPAB_IE_LEN);
+ ext_cap->header.type = cpu_to_le16(WLAN_EID_EXT_CAPABILITY);
+ ext_cap->header.len = cpu_to_le16(NXPWIFI_EXT_CAPAB_IE_LEN);
+ ext_capab = ext_cap->ext_capab;
+ tlv_pos += sizeof(struct nxpwifi_ie_types_extcap) +
+ le16_to_cpu(ext_cap->header.len);
+ }
+
+ ext_capab[9] |= WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT;
+ }
+
+ /* Append vendor specific element TLV */
+ nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_SCAN, &tlv_pos);
+
+ /*
+ * Set the channel TLV output pointer to the end of the newly added TLVs
+ * (SSID, num_probes). Channel TLVs for each scan will be appended after
+ * these, preserving previously added TLVs.
+ */
+ *chan_list_out =
+ (struct nxpwifi_ie_types_chan_list_param_set *)tlv_pos;
+
+ if (user_scan_in && user_scan_in->chan_list[0].chan_number) {
+ nxpwifi_dbg(adapter, INFO,
+ "info: Scan: Using supplied channel list\n");
+
+ for (chan_idx = 0;
+ chan_idx < NXPWIFI_USER_SCAN_CHAN_MAX &&
+ user_scan_in->chan_list[chan_idx].chan_number;
+ chan_idx++) {
+ channel = user_scan_in->chan_list[chan_idx].chan_number;
+ scan_chan_list[chan_idx].chan_number = channel;
+
+ radio_type =
+ user_scan_in->chan_list[chan_idx].radio_type;
+ scan_chan_list[chan_idx].band_cfg = radio_type;
+
+ scan_type = user_scan_in->chan_list[chan_idx].scan_type;
+
+ if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE)
+ scan_chan_list[chan_idx].chan_scan_mode_bmap |=
+ (NXPWIFI_PASSIVE_SCAN |
+ NXPWIFI_HIDDEN_SSID_REPORT);
+ else
+ scan_chan_list[chan_idx].chan_scan_mode_bmap &=
+ ~NXPWIFI_PASSIVE_SCAN;
+
+ scan_chan_list[chan_idx].chan_scan_mode_bmap |=
+ NXPWIFI_DISABLE_CHAN_FILT;
+
+ scan_time = user_scan_in->chan_list[chan_idx].scan_time;
+
+ if (scan_time) {
+ scan_dur = (u16)scan_time;
+ } else {
+ if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE)
+ scan_dur = adapter->passive_scan_time;
+ else if (*filtered_scan)
+ scan_dur = adapter->specific_scan_time;
+ else
+ scan_dur = adapter->active_scan_time;
+ }
+
+ scan_chan_list[chan_idx].min_scan_time =
+ cpu_to_le16(scan_dur);
+ scan_chan_list[chan_idx].max_scan_time =
+ cpu_to_le16(scan_dur);
+ }
+
+ /* Check if we are only scanning the current channel */
+ if (chan_idx == 1 &&
+ user_scan_in->chan_list[0].chan_number ==
+ priv->curr_bss_params.bss_descriptor.channel) {
+ *scan_current_only = true;
+ nxpwifi_dbg(adapter, INFO,
+ "info: Scan: Scanning current channel only\n");
+ }
+ } else {
+ nxpwifi_dbg(adapter, INFO,
+ "info: Scan: Creating full region channel list\n");
+ nxpwifi_scan_create_channel_list(priv, user_scan_in,
+ scan_chan_list,
+ *filtered_scan);
+ }
+}
+
+/*
+ * Parse the scan response buffer for expected TLV pointers.
+ * TLVs may be appended after the BSS info and can be returned if found.
+ */
+static void
+nxpwifi_ret_802_11_scan_get_tlv_ptrs(struct nxpwifi_adapter *adapter,
+ struct nxpwifi_ie_types_data *tlv,
+ u32 tlv_buf_size, u32 req_tlv_type,
+ struct nxpwifi_ie_types_data **tlv_data)
+{
+ struct nxpwifi_ie_types_data *current_tlv;
+ u32 tlv_buf_left;
+ u32 tlv_type;
+ u32 tlv_len;
+
+ current_tlv = tlv;
+ tlv_buf_left = tlv_buf_size;
+ *tlv_data = NULL;
+
+ nxpwifi_dbg(adapter, INFO,
+ "info: SCAN_RESP: tlv_buf_size = %d\n",
+ tlv_buf_size);
+
+ while (tlv_buf_left >= sizeof(struct nxpwifi_ie_types_header)) {
+ tlv_type = le16_to_cpu(current_tlv->header.type);
+ tlv_len = le16_to_cpu(current_tlv->header.len);
+
+ if (sizeof(tlv->header) + tlv_len > tlv_buf_left) {
+ nxpwifi_dbg(adapter, ERROR,
+ "SCAN_RESP: TLV buffer corrupt\n");
+ break;
+ }
+
+ if (req_tlv_type == tlv_type) {
+ switch (tlv_type) {
+ case TLV_TYPE_TSFTIMESTAMP:
+ nxpwifi_dbg(adapter, INFO,
+ "info: SCAN_RESP: TSF\t"
+ "timestamp TLV, len = %d\n",
+ tlv_len);
+ *tlv_data = current_tlv;
+ break;
+ case TLV_TYPE_CHANNELBANDLIST:
+ nxpwifi_dbg(adapter, INFO,
+ "info: SCAN_RESP: channel\t"
+ "band list TLV, len = %d\n",
+ tlv_len);
+ *tlv_data = current_tlv;
+ break;
+ default:
+ nxpwifi_dbg(adapter, ERROR,
+ "SCAN_RESP: unhandled TLV = %d\n",
+ tlv_type);
+ /* Give up, this seems corrupted */
+ return;
+ }
+ }
+
+ if (*tlv_data)
+ break;
+
+ tlv_buf_left -= (sizeof(tlv->header) + tlv_len);
+ current_tlv =
+ (struct nxpwifi_ie_types_data *)(current_tlv->data +
+ tlv_len);
+ } /* while */
+}
+
+/* Parse the beacon buffer and update the BSS descriptor fields. */
+int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
+ struct nxpwifi_bssdescriptor *bss_entry)
+{
+ u8 element_id;
+ u16 elem_size = sizeof(struct element);
+ struct ieee_types_fh_param_set *fh_param_set;
+ struct ieee_types_ds_param_set *ds_param_set;
+ struct ieee_types_cf_param_set *cf_param_set;
+ u8 *current_ptr;
+ u8 *rate;
+ u8 element_len;
+ u16 total_ie_len;
+ u8 bytes_to_copy;
+ u8 rate_size;
+ u8 found_data_rate_ie;
+ u32 bytes_left;
+ struct ieee_types_vendor_specific *vendor_ie;
+ const u8 wpa_oui[4] = { 0x00, 0x50, 0xf2, 0x01 };
+ const u8 wmm_oui[4] = { 0x00, 0x50, 0xf2, 0x02 };
+ struct element *elem;
+
+ found_data_rate_ie = false;
+ rate_size = 0;
+ current_ptr = bss_entry->beacon_buf;
+ bytes_left = bss_entry->beacon_buf_size;
+
+ /* Process variable element */
+ while (bytes_left >= 2) {
+ element_id = *current_ptr;
+ element_len = *(current_ptr + 1);
+ total_ie_len = element_len + elem_size;
+
+ if (bytes_left < total_ie_len) {
+ nxpwifi_dbg(adapter, ERROR,
+ "err: InterpretIE: in processing\t"
+ "element, bytes left < element length\n");
+ return -EINVAL;
+ }
+ switch (element_id) {
+ case WLAN_EID_SSID:
+ if (element_len > IEEE80211_MAX_SSID_LEN)
+ return -EINVAL;
+ bss_entry->ssid.ssid_len = element_len;
+ memcpy(bss_entry->ssid.ssid, (current_ptr + 2),
+ element_len);
+ nxpwifi_dbg(adapter, INFO,
+ "info: InterpretIE: ssid: %-32s\n",
+ bss_entry->ssid.ssid);
+ break;
+
+ case WLAN_EID_SUPP_RATES:
+ if (element_len > NXPWIFI_SUPPORTED_RATES)
+ return -EINVAL;
+ memcpy(bss_entry->data_rates, current_ptr + 2,
+ element_len);
+ memcpy(bss_entry->supported_rates, current_ptr + 2,
+ element_len);
+ rate_size = element_len;
+ found_data_rate_ie = true;
+ break;
+
+ case WLAN_EID_FH_PARAMS:
+ if (total_ie_len < sizeof(*fh_param_set))
+ return -EINVAL;
+ fh_param_set =
+ (struct ieee_types_fh_param_set *)current_ptr;
+ memcpy(&bss_entry->phy_param_set.fh_param_set,
+ fh_param_set,
+ sizeof(struct ieee_types_fh_param_set));
+ break;
+
+ case WLAN_EID_DS_PARAMS:
+ if (total_ie_len < sizeof(*ds_param_set))
+ return -EINVAL;
+ ds_param_set =
+ (struct ieee_types_ds_param_set *)current_ptr;
+
+ bss_entry->channel = ds_param_set->current_chan;
+
+ memcpy(&bss_entry->phy_param_set.ds_param_set,
+ ds_param_set,
+ sizeof(struct ieee_types_ds_param_set));
+ break;
+
+ case WLAN_EID_CF_PARAMS:
+ if (total_ie_len < sizeof(*cf_param_set))
+ return -EINVAL;
+ cf_param_set =
+ (struct ieee_types_cf_param_set *)current_ptr;
+ memcpy(&bss_entry->cf_param_set,
+ cf_param_set,
+ sizeof(struct ieee_types_cf_param_set));
+ break;
+
+ case WLAN_EID_ERP_INFO:
+ if (!element_len)
+ return -EINVAL;
+ bss_entry->erp_flags = *(current_ptr + 2);
+ break;
+
+ case WLAN_EID_PWR_CONSTRAINT:
+ if (!element_len)
+ return -EINVAL;
+ bss_entry->local_constraint = *(current_ptr + 2);
+ bss_entry->sensed_11h = true;
+ break;
+
+ case WLAN_EID_CHANNEL_SWITCH:
+ bss_entry->chan_sw_ie_present = true;
+ fallthrough;
+ case WLAN_EID_PWR_CAPABILITY:
+ case WLAN_EID_TPC_REPORT:
+ case WLAN_EID_QUIET:
+ bss_entry->sensed_11h = true;
+ break;
+
+ case WLAN_EID_EXT_SUPP_RATES:
+ /*
+ * Only process extended supported rate
+ * if data rate is already found.
+ * Data rate element should come before
+ * extended supported rate element
+ */
+ if (found_data_rate_ie) {
+ if ((element_len + rate_size) >
+ NXPWIFI_SUPPORTED_RATES)
+ bytes_to_copy =
+ (NXPWIFI_SUPPORTED_RATES -
+ rate_size);
+ else
+ bytes_to_copy = element_len;
+
+ rate = (u8 *)bss_entry->data_rates;
+ rate += rate_size;
+ memcpy(rate, current_ptr + 2, bytes_to_copy);
+
+ rate = (u8 *)bss_entry->supported_rates;
+ rate += rate_size;
+ memcpy(rate, current_ptr + 2, bytes_to_copy);
+ }
+ break;
+
+ case WLAN_EID_VENDOR_SPECIFIC:
+ vendor_ie = (struct ieee_types_vendor_specific *)
+ current_ptr;
+
+ /* 802.11 requires at least 3-byte OUI. */
+ if (element_len < sizeof(vendor_ie->vend_hdr.oui))
+ return -EINVAL;
+
+ /* Not long enough for a match? Skip it. */
+ if (element_len < sizeof(wpa_oui))
+ break;
+
+ if (!memcmp(&vendor_ie->vend_hdr.oui, wpa_oui,
+ sizeof(wpa_oui))) {
+ bss_entry->bcn_wpa_ie =
+ (struct ieee_types_vendor_specific *)
+ current_ptr;
+ bss_entry->wpa_offset =
+ (u16)(current_ptr -
+ bss_entry->beacon_buf);
+ } else if (!memcmp(&vendor_ie->vend_hdr.oui, wmm_oui,
+ sizeof(wmm_oui))) {
+ if (total_ie_len ==
+ sizeof(struct ieee80211_wmm_param_ie) ||
+ total_ie_len ==
+ sizeof(struct ieee_types_wmm_info))
+ /*
+ * Only accept and copy the WMM element if
+ * it matches the size expected for the
+ * WMM Info element or the WMM Parameter element.
+ */
+ memcpy((u8 *)&bss_entry->wmm_ie,
+ current_ptr, total_ie_len);
+ }
+ break;
+ case WLAN_EID_RSN:
+ bss_entry->bcn_rsn_ie =
+ (struct element *)current_ptr;
+ bss_entry->rsn_offset =
+ (u16)(current_ptr - bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_RSNX:
+ bss_entry->bcn_rsnx_ie =
+ (struct element *)current_ptr;
+ bss_entry->rsnx_offset =
+ (u16)(current_ptr - bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_HT_CAPABILITY:
+ bss_entry->bcn_ht_cap =
+ (struct ieee80211_ht_cap *)(current_ptr +
+ elem_size);
+ bss_entry->ht_cap_offset =
+ (u16)(current_ptr + elem_size -
+ bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_HT_OPERATION:
+ bss_entry->bcn_ht_oper =
+ (struct ieee80211_ht_operation *)(current_ptr +
+ elem_size);
+ bss_entry->ht_info_offset =
+ (u16)(current_ptr + elem_size -
+ bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_VHT_CAPABILITY:
+ bss_entry->disable_11ac = false;
+ bss_entry->bcn_vht_cap = (void *)(current_ptr +
+ elem_size);
+ bss_entry->vht_cap_offset =
+ (u16)((u8 *)bss_entry->bcn_vht_cap -
+ bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_VHT_OPERATION:
+ bss_entry->bcn_vht_oper =
+ (void *)(current_ptr + elem_size);
+ bss_entry->vht_info_offset =
+ (u16)((u8 *)bss_entry->bcn_vht_oper -
+ bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_BSS_COEX_2040:
+ bss_entry->bcn_bss_co_2040 = current_ptr;
+ bss_entry->bss_co_2040_offset =
+ (u16)(current_ptr - bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_EXT_CAPABILITY:
+ bss_entry->bcn_ext_cap = current_ptr;
+ bss_entry->ext_cap_offset =
+ (u16)(current_ptr - bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_OPMODE_NOTIF:
+ bss_entry->oper_mode = (void *)current_ptr;
+ bss_entry->oper_mode_offset =
+ (u16)(current_ptr - bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_EXTENSION:
+ elem = (struct element *)current_ptr;
+
+ switch (elem->data[0]) {
+ case WLAN_EID_EXT_HE_CAPABILITY:
+ bss_entry->disable_11ax = false;
+ bss_entry->bcn_he_cap =
+ (void *)(current_ptr + elem_size + 1);
+ bss_entry->he_cap_offset =
+ (u16)((u8 *)bss_entry->bcn_he_cap -
+ bss_entry->beacon_buf);
+ break;
+ case WLAN_EID_EXT_HE_OPERATION:
+ bss_entry->bcn_he_oper =
+ (void *)(current_ptr + elem_size + 1);
+ bss_entry->he_info_offset =
+ (u16)((u8 *)bss_entry->bcn_he_oper -
+ bss_entry->beacon_buf);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ current_ptr += total_ie_len;
+ bytes_left -= total_ie_len;
+
+ } /* while (bytes_left > 2) */
+ return 0;
+}
+
+/* Convert the radio-type scan parameter to the join command's band config. */
+static u8
+nxpwifi_radio_type_to_band(u8 radio_type)
+{
+ switch (radio_type) {
+ case HOST_SCAN_RADIO_TYPE_A:
+ return BAND_A;
+ case HOST_SCAN_RADIO_TYPE_BG:
+ default:
+ return BAND_G;
+ }
+}
+
+/* Internal helper to start a scan using the given configuration. */
+int nxpwifi_scan_networks(struct nxpwifi_private *priv,
+ const struct nxpwifi_user_scan_cfg *user_scan_in)
+{
+ int ret;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct cmd_ctrl_node *cmd_node;
+ union nxpwifi_scan_cmd_config_tlv *scan_cfg_out;
+ struct nxpwifi_ie_types_chan_list_param_set *chan_list_out;
+ struct nxpwifi_chan_scan_param_set *scan_chan_list;
+ u8 filtered_scan;
+ u8 scan_current_chan_only;
+ u8 max_chan_per_scan;
+
+ if (adapter->scan_processing) {
+ nxpwifi_dbg(adapter, WARN,
+ "cmd: Scan already in process...\n");
+ return -EBUSY;
+ }
+
+ if (priv->scan_block) {
+ nxpwifi_dbg(adapter, WARN,
+ "cmd: Scan is blocked during association...\n");
+ return -EBUSY;
+ }
+
+ if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) ||
+ test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "Ignore scan. Card removed or firmware in bad state\n");
+ return -EPERM;
+ }
+
+ spin_lock_bh(&adapter->nxpwifi_cmd_lock);
+ adapter->scan_processing = true;
+ spin_unlock_bh(&adapter->nxpwifi_cmd_lock);
+
+ scan_cfg_out = kzalloc_obj(union nxpwifi_scan_cmd_config_tlv,
+ GFP_KERNEL);
+ if (!scan_cfg_out) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ scan_chan_list = kzalloc_objs(struct nxpwifi_chan_scan_param_set,
+ NXPWIFI_USER_SCAN_CHAN_MAX, GFP_KERNEL);
+ if (!scan_chan_list) {
+ kfree(scan_cfg_out);
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ nxpwifi_config_scan(priv, user_scan_in, &scan_cfg_out->config,
+ &chan_list_out, scan_chan_list, &max_chan_per_scan,
+ &filtered_scan, &scan_current_chan_only);
+
+ ret = nxpwifi_scan_channel_list(priv, max_chan_per_scan, filtered_scan,
+ &scan_cfg_out->config, chan_list_out,
+ scan_chan_list);
+
+ /* Get scan command from scan_pending_q and put to cmd_pending_q */
+ if (!ret) {
+ spin_lock_bh(&adapter->scan_pending_q_lock);
+ if (!list_empty(&adapter->scan_pending_q)) {
+ cmd_node = list_first_entry(&adapter->scan_pending_q,
+ struct cmd_ctrl_node, list);
+ list_del(&cmd_node->list);
+ spin_unlock_bh(&adapter->scan_pending_q_lock);
+ nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node);
+ nxpwifi_queue_work(adapter, &adapter->main_work);
+
+ /* Perform internal scan synchronously */
+ if (!priv->scan_request) {
+ nxpwifi_dbg(adapter, INFO,
+ "wait internal scan\n");
+ nxpwifi_wait_queue_complete(adapter, cmd_node);
+ }
+ } else {
+ spin_unlock_bh(&adapter->scan_pending_q_lock);
+ }
+ }
+
+ kfree(scan_cfg_out);
+ kfree(scan_chan_list);
+done:
+ if (ret) {
+ spin_lock_bh(&adapter->nxpwifi_cmd_lock);
+ adapter->scan_processing = false;
+ spin_unlock_bh(&adapter->nxpwifi_cmd_lock);
+ }
+ return ret;
+}
+
+/*
+ * Build the firmware scan command from the given configuration, including
+ * fixed fields and TLVs, and set the command ID, size, and endianness.
+ */
+int nxpwifi_cmd_802_11_scan(struct host_cmd_ds_command *cmd,
+ struct nxpwifi_scan_cmd_config *scan_cfg)
+{
+ struct host_cmd_ds_802_11_scan *scan_cmd = &cmd->params.scan;
+
+ /* Set fixed field variables in scan command */
+ scan_cmd->bss_mode = scan_cfg->bss_mode;
+ memcpy(scan_cmd->bssid, scan_cfg->specific_bssid,
+ sizeof(scan_cmd->bssid));
+ memcpy(scan_cmd->tlv_buffer, scan_cfg->tlv_buf, scan_cfg->tlv_buf_len);
+
+ cmd->command = cpu_to_le16(HOST_CMD_802_11_SCAN);
+
+ /* Size is equal to the sizeof(fixed portions) + the TLV len + header */
+ cmd->size = cpu_to_le16((u16)(sizeof(scan_cmd->bss_mode)
+ + sizeof(scan_cmd->bssid)
+ + scan_cfg->tlv_buf_len + S_DS_GEN));
+
+ return 0;
+}
+
+/* Check compatibility of the requested network with current driver settings. */
+int nxpwifi_check_network_compatibility(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ int ret = 0;
+
+ if (!bss_desc)
+ return -EINVAL;
+
+ if ((nxpwifi_get_cfp(priv, (u8)bss_desc->bss_band,
+ (u16)bss_desc->channel, 0))) {
+ switch (priv->bss_mode) {
+ case NL80211_IFTYPE_STATION:
+ ret = nxpwifi_is_network_compatible(priv, bss_desc,
+ priv->bss_mode);
+ if (ret)
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Incompatible network settings\n");
+ break;
+ default:
+ ret = 0;
+ }
+ }
+
+ return ret;
+}
+
+/* Check if the SSID length is zero or all bytes are zero. */
+static bool nxpwifi_is_hidden_ssid(struct cfg80211_ssid *ssid)
+{
+ int idx;
+
+ for (idx = 0; idx < ssid->ssid_len; idx++) {
+ if (ssid->ssid[idx])
+ return false;
+ }
+
+ return true;
+}
+
+/* Find hidden SSIDs on passive channels and save those channels for active scan. */
+static int nxpwifi_save_hidden_ssid_channels(struct nxpwifi_private *priv,
+ struct cfg80211_bss *bss)
+{
+ struct nxpwifi_bssdescriptor *bss_desc;
+ int ret;
+ int chid;
+
+ /* Allocate and fill new bss descriptor */
+ bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL);
+ if (!bss_desc)
+ return -ENOMEM;
+
+ ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc);
+ if (ret)
+ goto done;
+
+ if (nxpwifi_is_hidden_ssid(&bss_desc->ssid)) {
+ nxpwifi_dbg(priv->adapter, INFO, "found hidden SSID\n");
+ for (chid = 0 ; chid < NXPWIFI_USER_SCAN_CHAN_MAX; chid++) {
+ if (priv->hidden_chan[chid].chan_number ==
+ bss->channel->hw_value)
+ break;
+
+ if (!priv->hidden_chan[chid].chan_number) {
+ priv->hidden_chan[chid].chan_number =
+ bss->channel->hw_value;
+ priv->hidden_chan[chid].radio_type =
+ bss->channel->band;
+ priv->hidden_chan[chid].scan_type =
+ NXPWIFI_SCAN_TYPE_ACTIVE;
+ break;
+ }
+ }
+ }
+
+done:
+ /* Free beacon_ie allocated by nxpwifi_fill_new_bss_desc(). */
+ kfree(bss_desc->beacon_buf);
+ kfree(bss_desc);
+ return ret;
+}
+
+static int nxpwifi_update_curr_bss_params(struct nxpwifi_private *priv,
+ struct cfg80211_bss *bss)
+{
+ struct nxpwifi_bssdescriptor *bss_desc;
+ int ret;
+
+ /* Allocate and fill new bss descriptor */
+ bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL);
+ if (!bss_desc)
+ return -ENOMEM;
+
+ ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc);
+ if (ret)
+ goto done;
+
+ ret = nxpwifi_check_network_compatibility(priv, bss_desc);
+ if (ret)
+ goto done;
+
+ spin_lock_bh(&priv->curr_bcn_buf_lock);
+ /* Make a copy of current BSSID descriptor */
+ memcpy(&priv->curr_bss_params.bss_descriptor, bss_desc,
+ sizeof(priv->curr_bss_params.bss_descriptor));
+
+ /* beacon_ie will be copied to its own buffer in nxpwifi_save_curr_bcn(). */
+ nxpwifi_save_curr_bcn(priv);
+ spin_unlock_bh(&priv->curr_bcn_buf_lock);
+
+done:
+ /* Free beacon_ie allocated by nxpwifi_fill_new_bss_desc(). */
+ kfree(bss_desc->beacon_buf);
+ kfree(bss_desc);
+ return ret;
+}
+
+static int
+nxpwifi_parse_single_response_buf(struct nxpwifi_private *priv, u8 **bss_info,
+ u32 *bytes_left, u64 fw_tsf, u8 *radio_type,
+ bool ext_scan, s32 rssi_val)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct nxpwifi_chan_freq_power *cfp;
+ struct cfg80211_bss *bss;
+ u8 bssid[ETH_ALEN];
+ s32 rssi;
+ const u8 *ie_buf;
+ size_t ie_len;
+ u16 channel = 0;
+ u16 beacon_size = 0;
+ u32 curr_bcn_bytes;
+ u32 freq;
+ u16 beacon_period;
+ u16 cap_info_bitmap;
+ u8 *current_ptr;
+ u64 timestamp;
+ struct nxpwifi_fixed_bcn_param *bcn_param;
+ struct nxpwifi_bss_priv *bss_priv;
+
+ if (*bytes_left >= sizeof(beacon_size)) {
+ /* Extract & convert beacon size from command buffer */
+ beacon_size = get_unaligned_le16((*bss_info));
+ *bytes_left -= sizeof(beacon_size);
+ *bss_info += sizeof(beacon_size);
+ }
+
+ if (!beacon_size || beacon_size > *bytes_left) {
+ *bss_info += *bytes_left;
+ *bytes_left = 0;
+ return -EINVAL;
+ }
+
+ /*
+ * Initialize the current working beacon pointer for this BSS
+ * iteration
+ */
+ current_ptr = *bss_info;
+
+ /* Advance the return beacon pointer past the current beacon */
+ *bss_info += beacon_size;
+ *bytes_left -= beacon_size;
+
+ curr_bcn_bytes = beacon_size;
+
+ /*
+ * First 5 fields are bssid, RSSI(for legacy scan only),
+ * time stamp, beacon interval, and capability information
+ */
+ if (curr_bcn_bytes < ETH_ALEN + sizeof(u8) +
+ sizeof(struct nxpwifi_fixed_bcn_param)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "InterpretIE: not enough bytes left\n");
+ return -EINVAL;
+ }
+
+ memcpy(bssid, current_ptr, ETH_ALEN);
+ current_ptr += ETH_ALEN;
+ curr_bcn_bytes -= ETH_ALEN;
+
+ if (!ext_scan) {
+ rssi = (s32)*current_ptr;
+ rssi = (-rssi) * 100; /* Convert dBm to mBm */
+ current_ptr += sizeof(u8);
+ curr_bcn_bytes -= sizeof(u8);
+ nxpwifi_dbg(adapter, INFO,
+ "info: InterpretIE: RSSI=%d\n", rssi);
+ } else {
+ rssi = rssi_val;
+ }
+
+ bcn_param = (struct nxpwifi_fixed_bcn_param *)current_ptr;
+ current_ptr += sizeof(*bcn_param);
+ curr_bcn_bytes -= sizeof(*bcn_param);
+
+ timestamp = le64_to_cpu(bcn_param->timestamp);
+ beacon_period = le16_to_cpu(bcn_param->beacon_period);
+
+ cap_info_bitmap = le16_to_cpu(bcn_param->cap_info_bitmap);
+ nxpwifi_dbg(adapter, INFO,
+ "info: InterpretIE: capabilities=0x%X\n",
+ cap_info_bitmap);
+
+ /* Rest of the current buffer are element's */
+ ie_buf = current_ptr;
+ ie_len = curr_bcn_bytes;
+ nxpwifi_dbg(adapter, INFO,
+ "info: InterpretIE: IELength for this AP = %d\n",
+ curr_bcn_bytes);
+
+ while (curr_bcn_bytes >= sizeof(struct element)) {
+ u8 element_id, element_len;
+
+ element_id = *current_ptr;
+ element_len = *(current_ptr + 1);
+ if (curr_bcn_bytes < element_len +
+ sizeof(struct element)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "%s: bytes left < element length\n", __func__);
+ return -EFAULT;
+ }
+ if (element_id == WLAN_EID_DS_PARAMS) {
+ channel = *(current_ptr +
+ sizeof(struct element));
+ break;
+ }
+
+ current_ptr += element_len + sizeof(struct element);
+ curr_bcn_bytes -= element_len +
+ sizeof(struct element);
+ }
+
+ if (channel) {
+ struct ieee80211_channel *chan;
+ struct nxpwifi_bssdescriptor *bss_desc;
+ u8 band;
+
+ /* Skip entry if on csa closed channel */
+ if (channel == priv->csa_chan) {
+ nxpwifi_dbg(adapter, WARN,
+ "Dropping entry on csa closed channel\n");
+ return 0;
+ }
+
+ band = BAND_G;
+ if (radio_type)
+ band = nxpwifi_radio_type_to_band(*radio_type &
+ (BIT(0) | BIT(1)));
+
+ cfp = nxpwifi_get_cfp(priv, band, channel, 0);
+
+ freq = cfp ? cfp->freq : 0;
+
+ chan = ieee80211_get_channel(priv->wdev.wiphy, freq);
+
+ if (chan && !(chan->flags & IEEE80211_CHAN_DISABLED)) {
+ bss = cfg80211_inform_bss(priv->wdev.wiphy, chan,
+ CFG80211_BSS_FTYPE_UNKNOWN,
+ bssid, timestamp,
+ cap_info_bitmap,
+ beacon_period,
+ ie_buf, ie_len, rssi,
+ GFP_ATOMIC);
+ if (bss) {
+ bss_priv = (struct nxpwifi_bss_priv *)bss->priv;
+ bss_priv->band = band;
+ bss_priv->fw_tsf = fw_tsf;
+ bss_desc =
+ &priv->curr_bss_params.bss_descriptor;
+ if (priv->media_connected &&
+ !memcmp(bssid, bss_desc->mac_address,
+ ETH_ALEN))
+ nxpwifi_update_curr_bss_params(priv,
+ bss);
+
+ if ((chan->flags & IEEE80211_CHAN_RADAR) ||
+ (chan->flags & IEEE80211_CHAN_NO_IR)) {
+ nxpwifi_dbg(adapter, INFO,
+ "radar or passive channel %d\n",
+ channel);
+ nxpwifi_save_hidden_ssid_channels(priv,
+ bss);
+ }
+
+ cfg80211_put_bss(priv->wdev.wiphy, bss);
+ }
+ }
+ } else {
+ nxpwifi_dbg(adapter, WARN, "missing BSS channel element\n");
+ }
+
+ return 0;
+}
+
+static void nxpwifi_complete_scan(struct nxpwifi_private *priv)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+
+ adapter->survey_idx = 0;
+ if (adapter->curr_cmd->wait_q_enabled) {
+ adapter->cmd_wait_q.status = 0;
+ if (!priv->scan_request) {
+ nxpwifi_dbg(adapter, INFO,
+ "complete internal scan\n");
+ nxpwifi_complete_cmd(adapter, adapter->curr_cmd);
+ }
+ }
+}
+
+/* Find hidden SSIDs on passive channels and run active scans on them. */
+static int
+nxpwifi_active_scan_req_for_passive_chan(struct nxpwifi_private *priv)
+{
+ int ret;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ u8 id = 0;
+ struct nxpwifi_user_scan_cfg *user_scan_cfg;
+
+ if (adapter->active_scan_triggered || !priv->scan_request ||
+ priv->scan_aborting) {
+ adapter->active_scan_triggered = false;
+ return 0;
+ }
+
+ if (!priv->hidden_chan[0].chan_number) {
+ nxpwifi_dbg(adapter, INFO, "No BSS with hidden SSID found on DFS channels\n");
+ return 0;
+ }
+ user_scan_cfg = kzalloc_obj(*user_scan_cfg, GFP_KERNEL);
+
+ if (!user_scan_cfg)
+ return -ENOMEM;
+
+ for (id = 0; id < NXPWIFI_USER_SCAN_CHAN_MAX; id++) {
+ if (!priv->hidden_chan[id].chan_number)
+ break;
+ memcpy(&user_scan_cfg->chan_list[id],
+ &priv->hidden_chan[id],
+ sizeof(struct nxpwifi_user_scan_chan));
+ }
+
+ adapter->active_scan_triggered = true;
+ if (priv->scan_request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR)
+ ether_addr_copy(user_scan_cfg->random_mac,
+ priv->scan_request->mac_addr);
+ user_scan_cfg->num_ssids = priv->scan_request->n_ssids;
+ user_scan_cfg->ssid_list = priv->scan_request->ssids;
+
+ ret = nxpwifi_scan_networks(priv, user_scan_cfg);
+ kfree(user_scan_cfg);
+
+ memset(&priv->hidden_chan, 0, sizeof(priv->hidden_chan));
+
+ if (ret)
+ nxpwifi_dbg(adapter, ERROR, "scan failed: %d\n", ret);
+
+ return ret;
+}
+
+static void nxpwifi_check_next_scan_command(struct nxpwifi_private *priv)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct cmd_ctrl_node *cmd_node;
+
+ spin_lock_bh(&adapter->scan_pending_q_lock);
+ if (list_empty(&adapter->scan_pending_q)) {
+ spin_unlock_bh(&adapter->scan_pending_q_lock);
+
+ spin_lock_bh(&adapter->nxpwifi_cmd_lock);
+ adapter->scan_processing = false;
+ spin_unlock_bh(&adapter->nxpwifi_cmd_lock);
+
+ nxpwifi_active_scan_req_for_passive_chan(priv);
+
+ if (!adapter->ext_scan)
+ nxpwifi_complete_scan(priv);
+
+ if (priv->scan_request) {
+ struct cfg80211_scan_info info = {
+ .aborted = false,
+ };
+
+ nxpwifi_dbg(adapter, INFO,
+ "info: notifying scan done\n");
+ cfg80211_scan_done(priv->scan_request, &info);
+ priv->scan_request = NULL;
+ priv->scan_aborting = false;
+ } else {
+ priv->scan_aborting = false;
+ nxpwifi_dbg(adapter, INFO,
+ "info: scan already aborted\n");
+ }
+ } else if ((priv->scan_aborting && !priv->scan_request) ||
+ priv->scan_block) {
+ spin_unlock_bh(&adapter->scan_pending_q_lock);
+
+ nxpwifi_cancel_pending_scan_cmd(adapter);
+
+ spin_lock_bh(&adapter->nxpwifi_cmd_lock);
+ adapter->scan_processing = false;
+ spin_unlock_bh(&adapter->nxpwifi_cmd_lock);
+
+ if (!adapter->active_scan_triggered) {
+ if (priv->scan_request) {
+ struct cfg80211_scan_info info = {
+ .aborted = true,
+ };
+
+ nxpwifi_dbg(adapter, INFO,
+ "info: aborting scan\n");
+ cfg80211_scan_done(priv->scan_request, &info);
+ priv->scan_request = NULL;
+ priv->scan_aborting = false;
+ } else {
+ priv->scan_aborting = false;
+ nxpwifi_dbg(adapter, INFO,
+ "info: scan already aborted\n");
+ }
+ }
+ } else {
+ /* Move a scan command from scan_pending_q to cmd_pending_q. */
+ cmd_node = list_first_entry(&adapter->scan_pending_q,
+ struct cmd_ctrl_node, list);
+ list_del(&cmd_node->list);
+ spin_unlock_bh(&adapter->scan_pending_q_lock);
+ nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node);
+ }
+}
+
+void nxpwifi_cancel_scan(struct nxpwifi_adapter *adapter)
+{
+ struct nxpwifi_private *priv;
+ int i;
+
+ nxpwifi_cancel_pending_scan_cmd(adapter);
+
+ if (adapter->scan_processing) {
+ spin_lock_bh(&adapter->nxpwifi_cmd_lock);
+ adapter->scan_processing = false;
+ spin_unlock_bh(&adapter->nxpwifi_cmd_lock);
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+ if (priv->scan_request) {
+ struct cfg80211_scan_info info = {
+ .aborted = true,
+ };
+
+ nxpwifi_dbg(adapter, INFO,
+ "info: aborting scan\n");
+ cfg80211_scan_done(priv->scan_request, &info);
+ priv->scan_request = NULL;
+ priv->scan_aborting = false;
+ }
+ }
+ }
+}
+
+/*
+ * Handle the scan command response.
+ *
+ * The scan response buffer has the following layout:
+ *
+ * -------------------------------------------------------------
+ * | Header (4 * t_u16): standard command response header |
+ * -------------------------------------------------------------
+ * | BufSize (t_u16): size of the BSS description data |
+ * -------------------------------------------------------------
+ * | NumOfSet (t_u8): number of returned BSS descriptions |
+ * -------------------------------------------------------------
+ * | BSS description data (variable, size = BufSize) |
+ * -------------------------------------------------------------
+ * | TLV data (variable, size = cmd_size - fixed fields) |
+ * -------------------------------------------------------------
+ */
+int nxpwifi_ret_802_11_scan(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp)
+{
+ int ret = 0;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct host_cmd_ds_802_11_scan_rsp *scan_rsp;
+ struct nxpwifi_ie_types_data *tlv_data;
+ struct nxpwifi_ie_types_tsf_timestamp *tsf_tlv;
+ u8 *bss_info;
+ u32 scan_resp_size;
+ u32 bytes_left;
+ u32 idx;
+ u32 tlv_buf_size;
+ struct nxpwifi_ie_types_chan_band_list_param_set *chan_band_tlv;
+ struct chan_band_param_set *chan_band;
+ u8 is_bgscan_resp;
+ __le64 fw_tsf = 0;
+ u8 *radio_type;
+ struct cfg80211_wowlan_nd_match *pmatch;
+ struct cfg80211_sched_scan_request *nd_config = NULL;
+
+ is_bgscan_resp = (le16_to_cpu(resp->command)
+ == HOST_CMD_802_11_BG_SCAN_QUERY);
+ if (is_bgscan_resp)
+ scan_rsp = &resp->params.bg_scan_query_resp.scan_resp;
+ else
+ scan_rsp = &resp->params.scan_resp;
+
+ if (scan_rsp->number_of_sets > NXPWIFI_MAX_AP) {
+ nxpwifi_dbg(adapter, ERROR,
+ "SCAN_RESP: too many AP returned (%d)\n",
+ scan_rsp->number_of_sets);
+ ret = -EINVAL;
+ goto check_next_scan;
+ }
+
+ /* Check csa channel expiry before parsing scan response */
+ nxpwifi_11h_get_csa_closed_channel(priv);
+
+ bytes_left = le16_to_cpu(scan_rsp->bss_descript_size);
+ nxpwifi_dbg(adapter, INFO,
+ "info: SCAN_RESP: bss_descript_size %d\n",
+ bytes_left);
+
+ scan_resp_size = le16_to_cpu(resp->size);
+
+ nxpwifi_dbg(adapter, INFO,
+ "info: SCAN_RESP: returned %d APs before parsing\n",
+ scan_rsp->number_of_sets);
+
+ bss_info = scan_rsp->bss_desc_and_tlv_buffer;
+
+ /*
+ * TLV buffer size = scan_resp_size minus the fixed fields, BSS
+ * description data, and the command response header (S_DS_GEN).
+ */
+ tlv_buf_size = scan_resp_size - (bytes_left
+ + sizeof(scan_rsp->bss_descript_size)
+ + sizeof(scan_rsp->number_of_sets)
+ + S_DS_GEN);
+
+ tlv_data = (struct nxpwifi_ie_types_data *)
+ (scan_rsp->bss_desc_and_tlv_buffer + bytes_left);
+
+ /*
+ * Search the TLV buffer space in the scan response for any valid
+ * TLVs
+ */
+ nxpwifi_ret_802_11_scan_get_tlv_ptrs(adapter, tlv_data, tlv_buf_size,
+ TLV_TYPE_TSFTIMESTAMP,
+ (struct nxpwifi_ie_types_data **)
+ &tsf_tlv);
+
+ /*
+ * Search the TLV buffer space in the scan response for any valid
+ * TLVs
+ */
+ nxpwifi_ret_802_11_scan_get_tlv_ptrs(adapter, tlv_data, tlv_buf_size,
+ TLV_TYPE_CHANNELBANDLIST,
+ (struct nxpwifi_ie_types_data **)
+ &chan_band_tlv);
+
+#ifdef CONFIG_PM
+ if (priv->wdev.wiphy->wowlan_config)
+ nd_config = priv->wdev.wiphy->wowlan_config->nd_config;
+#endif
+
+ if (nd_config) {
+ adapter->nd_info =
+ kzalloc_flex(*adapter->nd_info, matches,
+ scan_rsp->number_of_sets, GFP_ATOMIC);
+
+ if (adapter->nd_info)
+ adapter->nd_info->n_matches = scan_rsp->number_of_sets;
+ }
+
+ for (idx = 0; idx < scan_rsp->number_of_sets && bytes_left; idx++) {
+ /*
+ * If a TSF TLV is present, save its TSF value in fw_tsf. This
+ * is the firmware TSF at the time the beacon or probe response
+ * was received.
+ */
+ if (tsf_tlv)
+ memcpy(&fw_tsf, &tsf_tlv->tsf_data[idx * TSF_DATA_SIZE],
+ sizeof(fw_tsf));
+
+ if (chan_band_tlv) {
+ chan_band = &chan_band_tlv->chan_band_param[idx];
+ radio_type = &chan_band->radio_type;
+ } else {
+ radio_type = NULL;
+ }
+
+ if (chan_band_tlv && adapter->nd_info) {
+ adapter->nd_info->matches[idx] =
+ kzalloc(sizeof(*pmatch) + sizeof(u32),
+ GFP_ATOMIC);
+
+ pmatch = adapter->nd_info->matches[idx];
+
+ if (pmatch) {
+ pmatch->n_channels = 1;
+ pmatch->channels[0] = chan_band->chan_number;
+ }
+ }
+
+ ret = nxpwifi_parse_single_response_buf(priv, &bss_info,
+ &bytes_left,
+ le64_to_cpu(fw_tsf),
+ radio_type, false, 0);
+ if (ret)
+ goto check_next_scan;
+ }
+
+check_next_scan:
+ nxpwifi_check_next_scan_command(priv);
+ return ret;
+}
+
+/*
+ * Prepare the extended scan command using the provided scan configuration
+ * and build the structure to be sent to firmware.
+ */
+int nxpwifi_cmd_802_11_scan_ext(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ void *data_buf)
+{
+ struct host_cmd_ds_802_11_scan_ext *ext_scan = &cmd->params.ext_scan;
+ struct nxpwifi_scan_cmd_config *scan_cfg = data_buf;
+
+ memcpy(ext_scan->tlv_buffer, scan_cfg->tlv_buf, scan_cfg->tlv_buf_len);
+
+ cmd->command = cpu_to_le16(HOST_CMD_802_11_SCAN_EXT);
+
+ /* Size is equal to the sizeof(fixed portions) + the TLV len + header */
+ cmd->size = cpu_to_le16((u16)(sizeof(ext_scan->reserved)
+ + scan_cfg->tlv_buf_len + S_DS_GEN));
+
+ return 0;
+}
+
+/* Prepare the background scan config command to send to firmware. */
+int nxpwifi_cmd_802_11_bg_scan_config(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ void *data_buf)
+{
+ struct host_cmd_ds_802_11_bg_scan_config *bgscan_config =
+ &cmd->params.bg_scan_config;
+ struct nxpwifi_bg_scan_cfg *bgscan_cfg_in = data_buf;
+ u8 *tlv_pos = bgscan_config->tlv;
+ u8 num_probes;
+ u32 ssid_len, chan_idx, scan_time, scan_type, scan_dur, chan_num;
+ int i;
+ struct nxpwifi_ie_types_num_probes *num_probes_tlv;
+ struct nxpwifi_ie_types_repeat_count *repeat_count_tlv;
+ struct nxpwifi_ie_types_min_rssi_threshold *rssi_threshold_tlv;
+ struct nxpwifi_ie_types_bgscan_start_later *start_later_tlv;
+ struct nxpwifi_ie_types_wildcard_ssid_params *wildcard_ssid_tlv;
+ struct nxpwifi_ie_types_chan_list_param_set *tlv_l;
+ struct nxpwifi_chan_scan_param_set *temp_chan;
+
+ cmd->command = cpu_to_le16(HOST_CMD_802_11_BG_SCAN_CONFIG);
+ cmd->size = cpu_to_le16(sizeof(*bgscan_config) + S_DS_GEN);
+
+ bgscan_config->action = cpu_to_le16(bgscan_cfg_in->action);
+ bgscan_config->enable = bgscan_cfg_in->enable;
+ bgscan_config->bss_type = bgscan_cfg_in->bss_type;
+ bgscan_config->scan_interval =
+ cpu_to_le32(bgscan_cfg_in->scan_interval);
+ bgscan_config->report_condition =
+ cpu_to_le32(bgscan_cfg_in->report_condition);
+
+ /* stop sched scan */
+ if (!bgscan_config->enable)
+ return 0;
+
+ bgscan_config->chan_per_scan = bgscan_cfg_in->chan_per_scan;
+
+ num_probes = (bgscan_cfg_in->num_probes ?
+ bgscan_cfg_in->num_probes : priv->adapter->scan_probes);
+
+ if (num_probes) {
+ num_probes_tlv = (struct nxpwifi_ie_types_num_probes *)tlv_pos;
+ num_probes_tlv->header.type = cpu_to_le16(TLV_TYPE_NUMPROBES);
+ num_probes_tlv->header.len =
+ cpu_to_le16(sizeof(num_probes_tlv->num_probes));
+ num_probes_tlv->num_probes = cpu_to_le16((u16)num_probes);
+
+ tlv_pos += sizeof(num_probes_tlv->header) +
+ le16_to_cpu(num_probes_tlv->header.len);
+ }
+
+ if (bgscan_cfg_in->repeat_count) {
+ repeat_count_tlv =
+ (struct nxpwifi_ie_types_repeat_count *)tlv_pos;
+ repeat_count_tlv->header.type =
+ cpu_to_le16(TLV_TYPE_REPEAT_COUNT);
+ repeat_count_tlv->header.len =
+ cpu_to_le16(sizeof(repeat_count_tlv->repeat_count));
+ repeat_count_tlv->repeat_count =
+ cpu_to_le16(bgscan_cfg_in->repeat_count);
+
+ tlv_pos += sizeof(repeat_count_tlv->header) +
+ le16_to_cpu(repeat_count_tlv->header.len);
+ }
+
+ if (bgscan_cfg_in->rssi_threshold) {
+ rssi_threshold_tlv =
+ (struct nxpwifi_ie_types_min_rssi_threshold *)tlv_pos;
+ rssi_threshold_tlv->header.type =
+ cpu_to_le16(TLV_TYPE_RSSI_LOW);
+ rssi_threshold_tlv->header.len =
+ cpu_to_le16(sizeof(rssi_threshold_tlv->rssi_threshold));
+ rssi_threshold_tlv->rssi_threshold =
+ cpu_to_le16(bgscan_cfg_in->rssi_threshold);
+
+ tlv_pos += sizeof(rssi_threshold_tlv->header) +
+ le16_to_cpu(rssi_threshold_tlv->header.len);
+ }
+
+ for (i = 0; i < bgscan_cfg_in->num_ssids; i++) {
+ ssid_len = bgscan_cfg_in->ssid_list[i].ssid.ssid_len;
+
+ wildcard_ssid_tlv =
+ (struct nxpwifi_ie_types_wildcard_ssid_params *)tlv_pos;
+ wildcard_ssid_tlv->header.type =
+ cpu_to_le16(TLV_TYPE_WILDCARDSSID);
+ wildcard_ssid_tlv->header.len =
+ cpu_to_le16((u16)(ssid_len + sizeof(u8)));
+
+ /*
+ * max_ssid_length = 0 tells firmware to scan only for the given
+ * SSID. max_ssid_length = IEEE80211_MAX_SSID_LEN triggers a
+ * wildcard scan.
+ */
+ if (ssid_len)
+ wildcard_ssid_tlv->max_ssid_length = 0;
+ else
+ wildcard_ssid_tlv->max_ssid_length =
+ IEEE80211_MAX_SSID_LEN;
+
+ memcpy(wildcard_ssid_tlv->ssid,
+ bgscan_cfg_in->ssid_list[i].ssid.ssid, ssid_len);
+
+ tlv_pos += (sizeof(wildcard_ssid_tlv->header) +
+ le16_to_cpu(wildcard_ssid_tlv->header.len));
+ }
+
+ tlv_l = (struct nxpwifi_ie_types_chan_list_param_set *)tlv_pos;
+
+ if (bgscan_cfg_in->chan_list[0].chan_number) {
+ nxpwifi_dbg(priv->adapter, INFO, "info: bgscan: Using supplied channel list\n");
+
+ tlv_l->header.type = cpu_to_le16(TLV_TYPE_CHANLIST);
+
+ for (chan_idx = 0;
+ chan_idx < NXPWIFI_BG_SCAN_CHAN_MAX &&
+ bgscan_cfg_in->chan_list[chan_idx].chan_number;
+ chan_idx++) {
+ temp_chan = &tlv_l->chan_scan_param[chan_idx];
+
+ /* Increment the TLV header length by size appended */
+ le16_unaligned_add_cpu(&tlv_l->header.len,
+ sizeof(*tlv_l->chan_scan_param));
+
+ temp_chan->chan_number =
+ bgscan_cfg_in->chan_list[chan_idx].chan_number;
+ temp_chan->band_cfg =
+ bgscan_cfg_in->chan_list[chan_idx].radio_type;
+
+ scan_type =
+ bgscan_cfg_in->chan_list[chan_idx].scan_type;
+
+ if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE)
+ temp_chan->chan_scan_mode_bmap |=
+ NXPWIFI_PASSIVE_SCAN;
+ else
+ temp_chan->chan_scan_mode_bmap &=
+ ~NXPWIFI_PASSIVE_SCAN;
+
+ scan_time = bgscan_cfg_in->chan_list[chan_idx].scan_time;
+
+ if (scan_time) {
+ scan_dur = (u16)scan_time;
+ } else {
+ scan_dur = (scan_type ==
+ NXPWIFI_SCAN_TYPE_PASSIVE) ?
+ priv->adapter->passive_scan_time :
+ priv->adapter->specific_scan_time;
+ }
+
+ temp_chan->min_scan_time = cpu_to_le16(scan_dur);
+ temp_chan->max_scan_time = cpu_to_le16(scan_dur);
+ }
+ } else {
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: bgscan: Creating full region channel list\n");
+ chan_num =
+ nxpwifi_bgscan_create_channel_list
+ (priv, bgscan_cfg_in,
+ tlv_l->chan_scan_param);
+ le16_unaligned_add_cpu(&tlv_l->header.len,
+ chan_num *
+ sizeof(*tlv_l->chan_scan_param));
+ }
+
+ tlv_pos += (sizeof(tlv_l->header)
+ + le16_to_cpu(tlv_l->header.len));
+
+ if (bgscan_cfg_in->start_later) {
+ start_later_tlv =
+ (struct nxpwifi_ie_types_bgscan_start_later *)tlv_pos;
+ start_later_tlv->header.type =
+ cpu_to_le16(TLV_TYPE_BGSCAN_START_LATER);
+ start_later_tlv->header.len =
+ cpu_to_le16(sizeof(start_later_tlv->start_later));
+ start_later_tlv->start_later =
+ cpu_to_le16(bgscan_cfg_in->start_later);
+
+ tlv_pos += sizeof(start_later_tlv->header) +
+ le16_to_cpu(start_later_tlv->header.len);
+ }
+
+ /* Append vendor specific element TLV */
+ nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_BGSCAN, &tlv_pos);
+
+ le16_unaligned_add_cpu(&cmd->size, tlv_pos - bgscan_config->tlv);
+
+ return 0;
+}
+
+int nxpwifi_stop_bg_scan(struct nxpwifi_private *priv)
+{
+ struct nxpwifi_bg_scan_cfg *bgscan_cfg;
+ int ret;
+
+ if (!priv->sched_scanning) {
+ nxpwifi_dbg(priv->adapter, MSG, "bgscan already stopped!\n");
+ return 0;
+ }
+
+ bgscan_cfg = kzalloc_obj(*bgscan_cfg, GFP_KERNEL);
+ if (!bgscan_cfg)
+ return -ENOMEM;
+
+ bgscan_cfg->bss_type = NXPWIFI_BSS_MODE_INFRA;
+ bgscan_cfg->action = NXPWIFI_BGSCAN_ACT_SET;
+ bgscan_cfg->enable = false;
+
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_CONFIG,
+ HOST_ACT_GEN_SET, 0, bgscan_cfg, true);
+ if (!ret)
+ priv->sched_scanning = false;
+
+ kfree(bgscan_cfg);
+ return ret;
+}
+
+static void
+nxpwifi_update_chan_statistics(struct nxpwifi_private *priv,
+ struct nxpwifi_ietypes_chanstats *tlv_stat)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ u8 i, num_chan;
+ struct nxpwifi_fw_chan_stats *fw_chan_stats;
+ struct nxpwifi_chan_stats chan_stats;
+
+ fw_chan_stats = (void *)((u8 *)tlv_stat +
+ sizeof(struct nxpwifi_ie_types_header));
+ num_chan = le16_to_cpu(tlv_stat->header.len) /
+ sizeof(struct nxpwifi_chan_stats);
+
+ for (i = 0 ; i < num_chan; i++) {
+ if (adapter->survey_idx >= adapter->num_in_chan_stats) {
+ nxpwifi_dbg(adapter, WARN,
+ "FW reported too many channel results (max %d)\n",
+ adapter->num_in_chan_stats);
+ return;
+ }
+ chan_stats.chan_num = fw_chan_stats->chan_num;
+ chan_stats.bandcfg = fw_chan_stats->bandcfg;
+ chan_stats.flags = fw_chan_stats->flags;
+ chan_stats.noise = fw_chan_stats->noise;
+ chan_stats.total_bss = le16_to_cpu(fw_chan_stats->total_bss);
+ chan_stats.cca_scan_dur =
+ le16_to_cpu(fw_chan_stats->cca_scan_dur);
+ chan_stats.cca_busy_dur =
+ le16_to_cpu(fw_chan_stats->cca_busy_dur);
+ nxpwifi_dbg(adapter, INFO,
+ "chan=%d, noise=%d, total_network=%d scan_duration=%d, busy_duration=%d\n",
+ chan_stats.chan_num,
+ chan_stats.noise,
+ chan_stats.total_bss,
+ chan_stats.cca_scan_dur,
+ chan_stats.cca_busy_dur);
+ memcpy(&adapter->chan_stats[adapter->survey_idx++], &chan_stats,
+ sizeof(struct nxpwifi_chan_stats));
+ fw_chan_stats++;
+ }
+}
+
+/* Handle the extended scan command response. */
+int nxpwifi_ret_802_11_scan_ext(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct host_cmd_ds_802_11_scan_ext *ext_scan_resp;
+ struct nxpwifi_ie_types_header *tlv;
+ struct nxpwifi_ietypes_chanstats *tlv_stat;
+ u16 buf_left, type, len;
+
+ struct host_cmd_ds_command *cmd_ptr;
+ struct cmd_ctrl_node *cmd_node;
+ bool complete_scan = false;
+
+ nxpwifi_dbg(adapter, INFO, "info: EXT scan returns successfully\n");
+
+ ext_scan_resp = &resp->params.ext_scan;
+
+ tlv = (void *)ext_scan_resp->tlv_buffer;
+ buf_left = le16_to_cpu(resp->size) - (sizeof(*ext_scan_resp) + S_DS_GEN);
+
+ while (buf_left >= sizeof(struct nxpwifi_ie_types_header)) {
+ type = le16_to_cpu(tlv->type);
+ len = le16_to_cpu(tlv->len);
+
+ if (buf_left < (sizeof(struct nxpwifi_ie_types_header) + len)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "error processing scan response TLVs");
+ break;
+ }
+
+ switch (type) {
+ case TLV_TYPE_CHANNEL_STATS:
+ tlv_stat = (void *)tlv;
+ nxpwifi_update_chan_statistics(priv, tlv_stat);
+ break;
+ default:
+ break;
+ }
+
+ buf_left -= len + sizeof(struct nxpwifi_ie_types_header);
+ tlv = (void *)((u8 *)tlv + len +
+ sizeof(struct nxpwifi_ie_types_header));
+ }
+
+ spin_lock_bh(&adapter->cmd_pending_q_lock);
+ spin_lock_bh(&adapter->scan_pending_q_lock);
+ if (list_empty(&adapter->scan_pending_q)) {
+ complete_scan = true;
+ list_for_each_entry(cmd_node, &adapter->cmd_pending_q, list) {
+ cmd_ptr = (void *)cmd_node->cmd_skb->data;
+ if (le16_to_cpu(cmd_ptr->command) ==
+ HOST_CMD_802_11_SCAN_EXT) {
+ nxpwifi_dbg(adapter, INFO,
+ "Scan pending in command pending list");
+ complete_scan = false;
+ break;
+ }
+ }
+ }
+ spin_unlock_bh(&adapter->scan_pending_q_lock);
+ spin_unlock_bh(&adapter->cmd_pending_q_lock);
+
+ if (complete_scan)
+ nxpwifi_complete_scan(priv);
+
+ return 0;
+}
+
+/*
+ * Handle the extended scan report event: parse the results and notify
+ * cfg80211.
+ */
+int nxpwifi_handle_event_ext_scan_report(struct nxpwifi_private *priv,
+ void *buf)
+{
+ int ret = 0;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ u8 *bss_info;
+ u32 bytes_left, bytes_left_for_tlv, idx;
+ u16 type, len;
+ struct nxpwifi_ie_types_data *tlv;
+ struct nxpwifi_ie_types_scan_rsp *scan_rsp_tlv;
+ struct nxpwifi_ie_types_scan_inf *scan_info_tlv;
+ u8 *radio_type;
+ u64 fw_tsf = 0;
+ s32 rssi = 0;
+ struct nxpwifi_event_scan_result *event_scan = buf;
+ u8 num_of_set = event_scan->num_of_set;
+ u8 *scan_resp = buf + sizeof(struct nxpwifi_event_scan_result);
+ u16 scan_resp_size = le16_to_cpu(event_scan->buf_size);
+
+ if (num_of_set > NXPWIFI_MAX_AP) {
+ nxpwifi_dbg(adapter, ERROR,
+ "EXT_SCAN: Invalid number of AP returned (%d)!!\n",
+ num_of_set);
+ ret = -EINVAL;
+ goto check_next_scan;
+ }
+
+ bytes_left = scan_resp_size;
+ nxpwifi_dbg(adapter, INFO,
+ "EXT_SCAN: size %d, returned %d APs...",
+ scan_resp_size, num_of_set);
+ nxpwifi_dbg_dump(adapter, CMD_D, "EXT_SCAN buffer:", buf,
+ scan_resp_size +
+ sizeof(struct nxpwifi_event_scan_result));
+
+ tlv = (struct nxpwifi_ie_types_data *)scan_resp;
+
+ for (idx = 0; idx < num_of_set && bytes_left; idx++) {
+ type = le16_to_cpu(tlv->header.type);
+ len = le16_to_cpu(tlv->header.len);
+ if (bytes_left < sizeof(struct nxpwifi_ie_types_header) + len) {
+ nxpwifi_dbg(adapter, ERROR,
+ "EXT_SCAN: Error bytes left < TLV length\n");
+ break;
+ }
+ scan_rsp_tlv = NULL;
+ scan_info_tlv = NULL;
+ bytes_left_for_tlv = bytes_left;
+
+ /*
+ * BSS response TLV with beacon or probe response buffer
+ * at the initial position of each descriptor
+ */
+ if (type != TLV_TYPE_BSS_SCAN_RSP)
+ break;
+
+ bss_info = (u8 *)tlv;
+ scan_rsp_tlv = (struct nxpwifi_ie_types_scan_rsp *)tlv;
+ tlv = (struct nxpwifi_ie_types_data *)(tlv->data + len);
+ bytes_left_for_tlv -=
+ (len + sizeof(struct nxpwifi_ie_types_header));
+
+ while (bytes_left_for_tlv >=
+ sizeof(struct nxpwifi_ie_types_header) &&
+ le16_to_cpu(tlv->header.type) != TLV_TYPE_BSS_SCAN_RSP) {
+ type = le16_to_cpu(tlv->header.type);
+ len = le16_to_cpu(tlv->header.len);
+ if (bytes_left_for_tlv <
+ sizeof(struct nxpwifi_ie_types_header) + len) {
+ nxpwifi_dbg(adapter, ERROR,
+ "EXT_SCAN: Error in processing TLV,\t"
+ "bytes left < TLV length\n");
+ scan_rsp_tlv = NULL;
+ bytes_left_for_tlv = 0;
+ continue;
+ }
+ switch (type) {
+ case TLV_TYPE_BSS_SCAN_INFO:
+ scan_info_tlv =
+ (struct nxpwifi_ie_types_scan_inf *)tlv;
+ if (len !=
+ sizeof(struct nxpwifi_ie_types_scan_inf) -
+ sizeof(struct nxpwifi_ie_types_header)) {
+ bytes_left_for_tlv = 0;
+ continue;
+ }
+ break;
+ default:
+ break;
+ }
+ tlv = (struct nxpwifi_ie_types_data *)(tlv->data + len);
+ bytes_left -=
+ (len + sizeof(struct nxpwifi_ie_types_header));
+ bytes_left_for_tlv -=
+ (len + sizeof(struct nxpwifi_ie_types_header));
+ }
+
+ if (!scan_rsp_tlv)
+ break;
+
+ /*
+ * Advance pointer to the beacon buffer length and
+ * update the bytes count so that the function
+ * wlan_interpret_bss_desc_with_ie() can handle the
+ * scan buffer withut any change
+ */
+ bss_info += sizeof(u16);
+ bytes_left -= sizeof(u16);
+
+ if (scan_info_tlv) {
+ rssi = (s32)(s16)(le16_to_cpu(scan_info_tlv->rssi));
+ rssi *= 100; /* Convert dBm to mBm */
+ nxpwifi_dbg(adapter, INFO,
+ "info: InterpretIE: RSSI=%d\n", rssi);
+ fw_tsf = le64_to_cpu(scan_info_tlv->tsf);
+ radio_type = &scan_info_tlv->radio_type;
+ } else {
+ radio_type = NULL;
+ }
+ ret = nxpwifi_parse_single_response_buf(priv, &bss_info,
+ &bytes_left, fw_tsf,
+ radio_type, true, rssi);
+ if (ret)
+ goto check_next_scan;
+ }
+
+check_next_scan:
+ if (!event_scan->more_event)
+ nxpwifi_check_next_scan_command(priv);
+
+ return ret;
+}
+
+/*
+ * Prepare the background scan query command. Sets the command ID, size,
+ * flush parameter, and fixes endianness.
+ */
+int nxpwifi_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd)
+{
+ struct host_cmd_ds_802_11_bg_scan_query *bg_query =
+ &cmd->params.bg_scan_query;
+
+ cmd->command = cpu_to_le16(HOST_CMD_802_11_BG_SCAN_QUERY);
+ cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_bg_scan_query)
+ + S_DS_GEN);
+
+ bg_query->flush = 1;
+
+ return 0;
+}
+
+/* Insert a scan command node into the scan_pending_q. */
+void
+nxpwifi_queue_scan_cmd(struct nxpwifi_private *priv,
+ struct cmd_ctrl_node *cmd_node)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+
+ cmd_node->wait_q_enabled = true;
+ cmd_node->condition = &adapter->scan_wait_q_woken;
+ spin_lock_bh(&adapter->scan_pending_q_lock);
+ list_add_tail(&cmd_node->list, &adapter->scan_pending_q);
+ spin_unlock_bh(&adapter->scan_pending_q_lock);
+}
+
+/* Append a vendor-specific element TLV to the buffer. */
+int
+nxpwifi_cmd_append_vsie_tlv(struct nxpwifi_private *priv,
+ u16 vsie_mask, u8 **buffer)
+{
+ int id, ret_len = 0;
+ struct nxpwifi_ie_types_vendor_param_set *vs_param_set;
+
+ if (!buffer)
+ return 0;
+ if (!(*buffer))
+ return 0;
+
+ /*
+ * Traverse through the saved vendor specific element array and append
+ * the selected(scan/assoc) element as TLV to the command
+ */
+ for (id = 0; id < NXPWIFI_MAX_VSIE_NUM; id++) {
+ if (priv->vs_ie[id].mask & vsie_mask) {
+ vs_param_set =
+ (struct nxpwifi_ie_types_vendor_param_set *)
+ *buffer;
+ vs_param_set->header.type =
+ cpu_to_le16(TLV_TYPE_PASSTHROUGH);
+ vs_param_set->header.len =
+ cpu_to_le16((((u16)priv->vs_ie[id].ie[1])
+ & 0x00FF) + 2);
+ if (le16_to_cpu(vs_param_set->header.len) >
+ NXPWIFI_MAX_VSIE_LEN) {
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Invalid param length!\n");
+ break;
+ }
+
+ memcpy(vs_param_set->ie, priv->vs_ie[id].ie,
+ le16_to_cpu(vs_param_set->header.len));
+ *buffer += le16_to_cpu(vs_param_set->header.len) +
+ sizeof(struct nxpwifi_ie_types_header);
+ ret_len += le16_to_cpu(vs_param_set->header.len) +
+ sizeof(struct nxpwifi_ie_types_header);
+ }
+ }
+ return ret_len;
+}
+
+/*
+ * Save the beacon buffer of the current BSS descriptor.
+ *
+ * The buffer is preserved so it can be restored when the current SSID's
+ * beacon is missing, such as when:
+ * - the SSID was not found in the latest scan, or
+ * - the SSID was the last entry in the scan table and was overwritten.
+ */
+void
+nxpwifi_save_curr_bcn(struct nxpwifi_private *priv)
+{
+ struct nxpwifi_bssdescriptor *curr_bss =
+ &priv->curr_bss_params.bss_descriptor;
+
+ if (!curr_bss->beacon_buf_size)
+ return;
+
+ /* allocate beacon buffer at 1st time; or if it's size has changed */
+ if (!priv->curr_bcn_buf ||
+ priv->curr_bcn_size != curr_bss->beacon_buf_size) {
+ priv->curr_bcn_size = curr_bss->beacon_buf_size;
+
+ kfree(priv->curr_bcn_buf);
+ priv->curr_bcn_buf = kmalloc(curr_bss->beacon_buf_size,
+ GFP_ATOMIC);
+ if (!priv->curr_bcn_buf)
+ return;
+ }
+
+ memcpy(priv->curr_bcn_buf, curr_bss->beacon_buf,
+ curr_bss->beacon_buf_size);
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: current beacon saved %d\n",
+ priv->curr_bcn_size);
+
+ curr_bss->beacon_buf = priv->curr_bcn_buf;
+
+ /* adjust the pointers in the current BSS descriptor */
+ if (curr_bss->bcn_wpa_ie)
+ curr_bss->bcn_wpa_ie =
+ (struct ieee_types_vendor_specific *)
+ (curr_bss->beacon_buf +
+ curr_bss->wpa_offset);
+
+ if (curr_bss->bcn_rsn_ie)
+ curr_bss->bcn_rsn_ie =
+ (struct element *)(curr_bss->beacon_buf +
+ curr_bss->rsn_offset);
+
+ if (curr_bss->bcn_ht_cap)
+ curr_bss->bcn_ht_cap = (struct ieee80211_ht_cap *)
+ (curr_bss->beacon_buf +
+ curr_bss->ht_cap_offset);
+
+ if (curr_bss->bcn_ht_oper)
+ curr_bss->bcn_ht_oper = (struct ieee80211_ht_operation *)
+ (curr_bss->beacon_buf +
+ curr_bss->ht_info_offset);
+
+ if (curr_bss->bcn_vht_cap)
+ curr_bss->bcn_vht_cap = (void *)(curr_bss->beacon_buf +
+ curr_bss->vht_cap_offset);
+
+ if (curr_bss->bcn_vht_oper)
+ curr_bss->bcn_vht_oper = (void *)(curr_bss->beacon_buf +
+ curr_bss->vht_info_offset);
+
+ if (curr_bss->bcn_he_cap)
+ curr_bss->bcn_he_cap = (void *)(curr_bss->beacon_buf +
+ curr_bss->he_cap_offset);
+
+ if (curr_bss->bcn_he_oper)
+ curr_bss->bcn_he_oper = (void *)(curr_bss->beacon_buf +
+ curr_bss->he_info_offset);
+
+ if (curr_bss->bcn_bss_co_2040)
+ curr_bss->bcn_bss_co_2040 =
+ (curr_bss->beacon_buf + curr_bss->bss_co_2040_offset);
+
+ if (curr_bss->bcn_ext_cap)
+ curr_bss->bcn_ext_cap = curr_bss->beacon_buf +
+ curr_bss->ext_cap_offset;
+
+ if (curr_bss->oper_mode)
+ curr_bss->oper_mode = (void *)(curr_bss->beacon_buf +
+ curr_bss->oper_mode_offset);
+}
+
+/* Free the beacon buffer in the current BSS descriptor. */
+void
+nxpwifi_free_curr_bcn(struct nxpwifi_private *priv)
+{
+ kfree(priv->curr_bcn_buf);
+ priv->curr_bcn_buf = NULL;
+}
--
2.34.1
^ permalink raw reply related
* [PATCH v10 05/21] wifi: nxpwifi: add WMM support for QoS-based traffic handling
From: Jeff Chen @ 2026-03-05 14:39 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, johannes, francesco, wyatt.hsu, s.hauer, Jeff Chen
In-Reply-To: <20260305143939.3724868-1-jeff.chen_1@nxp.com>
Introduce WMM functionality to provide QoS-based traffic prioritization
and enhanced queue management for both STA and AP roles.
Parse WMM parameter elements from the AP and configure access category
(AC) parameters accordingly. Implement TID-to-AC mapping, admission
control handling, and downgrade logic for non-admitted traffic.
Integrate AMSDU and AMPDU aggregation with per-TID RA list queues, and
add scheduling logic for packet queuing, reordering, and transmission
based on priority and aggregation eligibility.
Handle firmware interactions for WMM status reporting and association
request TLV construction.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/wmm.c | 1308 ++++++++++++++++++++++++
drivers/net/wireless/nxp/nxpwifi/wmm.h | 77 ++
2 files changed, 1385 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.h
diff --git a/drivers/net/wireless/nxp/nxpwifi/wmm.c b/drivers/net/wireless/nxp/nxpwifi/wmm.c
new file mode 100644
index 000000000000..ad003dd31191
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/wmm.c
@@ -0,0 +1,1308 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * NXP Wireless LAN device driver: WMM
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#include "cfg.h"
+#include "util.h"
+#include "fw.h"
+#include "main.h"
+#include "wmm.h"
+#include "11n.h"
+
+/* Maximum value FW can accept for driver delay in packet transmission */
+#define DRV_PKT_DELAY_TO_FW_MAX 512
+
+#define WMM_QUEUED_PACKET_LOWER_LIMIT 180
+
+#define WMM_QUEUED_PACKET_UPPER_LIMIT 200
+
+/* Offset for TOS field in the IP header */
+#define IPTOS_OFFSET 5
+
+static bool disable_tx_amsdu;
+
+/*
+ * This table inverses the tos_to_tid operation to get a priority
+ * which is in sequential order, and can be compared.
+ * Use this to compare the priority of two different TIDs.
+ */
+const u8 tos_to_tid_inv[] = {
+ 0x02, /* from tos_to_tid[2] = 0 */
+ 0x00, /* from tos_to_tid[0] = 1 */
+ 0x01, /* from tos_to_tid[1] = 2 */
+ 0x03,
+ 0x04,
+ 0x05,
+ 0x06,
+ 0x07
+};
+
+/* WMM information element */
+static const u8 wmm_info_ie[] = { WLAN_EID_VENDOR_SPECIFIC, 0x07,
+ 0x00, 0x50, 0xf2, 0x02,
+ 0x00, 0x01, 0x00
+};
+
+static const u8 wmm_aci_to_qidx_map[] = { WMM_AC_BE,
+ WMM_AC_BK,
+ WMM_AC_VI,
+ WMM_AC_VO
+};
+
+static u8 tos_to_tid[] = {
+ /* TID DSCP_P2 DSCP_P1 DSCP_P0 WMM_AC */
+ 0x01, /* 0 1 0 AC_BK */
+ 0x02, /* 0 0 0 AC_BK */
+ 0x00, /* 0 0 1 AC_BE */
+ 0x03, /* 0 1 1 AC_BE */
+ 0x04, /* 1 0 0 AC_VI */
+ 0x05, /* 1 0 1 AC_VI */
+ 0x06, /* 1 1 0 AC_VO */
+ 0x07 /* 1 1 1 AC_VO */
+};
+
+static u8 ac_to_tid[4][2] = { {1, 2}, {0, 3}, {4, 5}, {6, 7} };
+
+/* Debug prints the priority parameters for a WMM AC. */
+static void
+nxpwifi_wmm_ac_debug_print(const struct ieee80211_wmm_ac_param *ac_param)
+{
+ static const char * const ac_str[] = { "BK", "BE", "VI", "VO" };
+
+ pr_debug("info: WMM AC_%s: ACI=%d, ACM=%d, Aifsn=%d, ",
+ ac_str[wmm_aci_to_qidx_map[(ac_param->aci_aifsn
+ & NXPWIFI_ACI) >> 5]],
+ (ac_param->aci_aifsn & NXPWIFI_ACI) >> 5,
+ (ac_param->aci_aifsn & NXPWIFI_ACM) >> 4,
+ ac_param->aci_aifsn & NXPWIFI_AIFSN);
+ pr_debug("EcwMin=%d, EcwMax=%d, TxopLimit=%d\n",
+ ac_param->cw & NXPWIFI_ECW_MIN,
+ (ac_param->cw & NXPWIFI_ECW_MAX) >> 4,
+ le16_to_cpu(ac_param->txop_limit));
+}
+
+/* Allocates a route address list. */
+static struct nxpwifi_ra_list_tbl *
+nxpwifi_wmm_allocate_ralist_node(struct nxpwifi_adapter *adapter, const u8 *ra)
+{
+ struct nxpwifi_ra_list_tbl *ra_list;
+
+ ra_list = kzalloc_obj(*ra_list, GFP_ATOMIC);
+ if (!ra_list)
+ return NULL;
+
+ INIT_LIST_HEAD(&ra_list->list);
+ skb_queue_head_init(&ra_list->skb_head);
+
+ memcpy(ra_list->ra, ra, ETH_ALEN);
+
+ ra_list->total_pkt_count = 0;
+
+ nxpwifi_dbg(adapter, INFO, "info: allocated ra_list %p\n", ra_list);
+
+ return ra_list;
+}
+
+/*
+ * Returns random no between 16 and 32 to be used as threshold for no of
+ * packets after which BA setup is initiated.
+ */
+static u8 nxpwifi_get_random_ba_threshold(void)
+{
+ u64 ns;
+ /*
+ * setup ba_packet_threshold here random number between
+ * [BA_SETUP_PACKET_OFFSET,
+ * BA_SETUP_PACKET_OFFSET+BA_SETUP_MAX_PACKET_THRESHOLD-1]
+ */
+ ns = ktime_get_ns();
+ ns += (ns >> 32) + (ns >> 16);
+
+ return ((u8)ns % BA_SETUP_MAX_PACKET_THRESHOLD) + BA_SETUP_PACKET_OFFSET;
+}
+
+/* Allocates and adds a RA list for all TIDs with the given RA. */
+void nxpwifi_ralist_add(struct nxpwifi_private *priv, const u8 *ra)
+{
+ int i;
+ struct nxpwifi_ra_list_tbl *ra_list;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct nxpwifi_sta_node *node;
+
+ for (i = 0; i < MAX_NUM_TID; ++i) {
+ ra_list = nxpwifi_wmm_allocate_ralist_node(adapter, ra);
+ nxpwifi_dbg(adapter, INFO,
+ "info: created ra_list %p\n", ra_list);
+
+ if (!ra_list)
+ break;
+
+ ra_list->is_11n_enabled = 0;
+ ra_list->ba_status = BA_SETUP_NONE;
+ ra_list->amsdu_in_ampdu = false;
+ if (!nxpwifi_queuing_ra_based(priv)) {
+ ra_list->is_11n_enabled = IS_11N_ENABLED(priv);
+ } else {
+ rcu_read_lock();
+ node = nxpwifi_get_sta_entry(priv, ra);
+ if (node)
+ ra_list->tx_paused = node->tx_pause;
+ ra_list->is_11n_enabled =
+ nxpwifi_is_sta_11n_enabled(priv, node);
+ if (ra_list->is_11n_enabled)
+ ra_list->max_amsdu = node->max_amsdu;
+ rcu_read_unlock();
+ }
+
+ nxpwifi_dbg(adapter, DATA, "data: ralist %p: is_11n_enabled=%d\n",
+ ra_list, ra_list->is_11n_enabled);
+
+ if (ra_list->is_11n_enabled) {
+ ra_list->ba_pkt_count = 0;
+ ra_list->ba_packet_thr =
+ nxpwifi_get_random_ba_threshold();
+ }
+ list_add_tail(&ra_list->list,
+ &priv->wmm.tid_tbl_ptr[i].ra_list);
+ }
+}
+
+/* Sets the WMM queue priorities to their default values. */
+static void nxpwifi_wmm_default_queue_priorities(struct nxpwifi_private *priv)
+{
+ /* Default queue priorities: VO->VI->BE->BK */
+ priv->wmm.queue_priority[0] = WMM_AC_VO;
+ priv->wmm.queue_priority[1] = WMM_AC_VI;
+ priv->wmm.queue_priority[2] = WMM_AC_BE;
+ priv->wmm.queue_priority[3] = WMM_AC_BK;
+}
+
+/* Map ACs to TIDs. */
+static void
+nxpwifi_wmm_queue_priorities_tid(struct nxpwifi_private *priv)
+{
+ struct nxpwifi_wmm_desc *wmm = &priv->wmm;
+ u8 *queue_priority = wmm->queue_priority;
+ int i;
+
+ for (i = 0; i < 4; ++i) {
+ tos_to_tid[7 - (i * 2)] = ac_to_tid[queue_priority[i]][1];
+ tos_to_tid[6 - (i * 2)] = ac_to_tid[queue_priority[i]][0];
+ }
+
+ for (i = 0; i < MAX_NUM_TID; ++i)
+ priv->tos_to_tid_inv[tos_to_tid[i]] = (u8)i;
+
+ atomic_set(&wmm->highest_queued_prio, HIGH_PRIO_TID);
+}
+
+/* Initializes WMM priority queues. */
+void
+nxpwifi_wmm_setup_queue_priorities(struct nxpwifi_private *priv,
+ struct ieee80211_wmm_param_ie *wmm_ie)
+{
+ u16 cw_min, avg_back_off, tmp[4];
+ u32 i, j, num_ac;
+ u8 ac_idx;
+
+ if (!wmm_ie || !priv->wmm_enabled) {
+ /* WMM is not enabled, just set the defaults and return */
+ nxpwifi_wmm_default_queue_priorities(priv);
+ return;
+ }
+
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: WMM Parameter element: version=%d,\t"
+ "qos_info Parameter Set Count=%d, Reserved=%#x\n",
+ wmm_ie->version, wmm_ie->qos_info &
+ IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK,
+ wmm_ie->reserved);
+
+ for (num_ac = 0; num_ac < ARRAY_SIZE(wmm_ie->ac); num_ac++) {
+ u8 ecw = wmm_ie->ac[num_ac].cw;
+ u8 aci_aifsn = wmm_ie->ac[num_ac].aci_aifsn;
+
+ cw_min = (1 << (ecw & NXPWIFI_ECW_MIN)) - 1;
+ avg_back_off = (cw_min >> 1) + (aci_aifsn & NXPWIFI_AIFSN);
+
+ ac_idx = wmm_aci_to_qidx_map[(aci_aifsn & NXPWIFI_ACI) >> 5];
+ priv->wmm.queue_priority[ac_idx] = ac_idx;
+ tmp[ac_idx] = avg_back_off;
+
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: WMM: CWmax=%d CWmin=%d Avg Back-off=%d\n",
+ (1 << ((ecw & NXPWIFI_ECW_MAX) >> 4)) - 1,
+ cw_min, avg_back_off);
+ nxpwifi_wmm_ac_debug_print(&wmm_ie->ac[num_ac]);
+ }
+
+ /* Bubble sort */
+ for (i = 0; i < num_ac; i++) {
+ for (j = 1; j < num_ac - i; j++) {
+ if (tmp[j - 1] > tmp[j]) {
+ swap(tmp[j - 1], tmp[j]);
+ swap(priv->wmm.queue_priority[j - 1],
+ priv->wmm.queue_priority[j]);
+ } else if (tmp[j - 1] == tmp[j]) {
+ if (priv->wmm.queue_priority[j - 1]
+ < priv->wmm.queue_priority[j])
+ swap(priv->wmm.queue_priority[j - 1],
+ priv->wmm.queue_priority[j]);
+ }
+ }
+ }
+
+ nxpwifi_wmm_queue_priorities_tid(priv);
+}
+
+/* Evaluates whether or not an AC is to be downgraded. */
+static enum nxpwifi_wmm_ac_e
+nxpwifi_wmm_eval_downgrade_ac(struct nxpwifi_private *priv,
+ enum nxpwifi_wmm_ac_e eval_ac)
+{
+ int down_ac;
+ enum nxpwifi_wmm_ac_e ret_ac;
+ struct nxpwifi_wmm_ac_status *ac_status;
+
+ ac_status = &priv->wmm.ac_status[eval_ac];
+
+ if (!ac_status->disabled)
+ /* Okay to use this AC, its enabled */
+ return eval_ac;
+
+ /* Setup a default return value of the lowest priority */
+ ret_ac = WMM_AC_BK;
+
+ /*
+ * Find the highest AC that is enabled and does not require
+ * admission control. The spec disallows downgrading to an AC,
+ * which is enabled due to a completed admission control.
+ * Unadmitted traffic is not to be sent on an AC with admitted
+ * traffic.
+ */
+ for (down_ac = WMM_AC_BK; down_ac < eval_ac; down_ac++) {
+ ac_status = &priv->wmm.ac_status[down_ac];
+
+ if (!ac_status->disabled && !ac_status->flow_required)
+ /*
+ * AC is enabled and does not require admission
+ * control
+ */
+ ret_ac = (enum nxpwifi_wmm_ac_e)down_ac;
+ }
+
+ return ret_ac;
+}
+
+/* Downgrades WMM priority queue. */
+void
+nxpwifi_wmm_setup_ac_downgrade(struct nxpwifi_private *priv)
+{
+ int ac_val;
+
+ nxpwifi_dbg(priv->adapter, INFO, "info: WMM: AC Priorities:\t"
+ "BK(0), BE(1), VI(2), VO(3)\n");
+
+ if (!priv->wmm_enabled) {
+ /* WMM is not enabled, default priorities */
+ for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++)
+ priv->wmm.ac_down_graded_vals[ac_val] =
+ (enum nxpwifi_wmm_ac_e)ac_val;
+ } else {
+ for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) {
+ priv->wmm.ac_down_graded_vals[ac_val] =
+ nxpwifi_wmm_eval_downgrade_ac
+ (priv, (enum nxpwifi_wmm_ac_e)ac_val);
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: WMM: AC PRIO %d maps to %d\n",
+ ac_val,
+ priv->wmm.ac_down_graded_vals[ac_val]);
+ }
+ }
+}
+
+/* Converts the IP TOS field to an WMM AC Queue assignment. */
+static enum nxpwifi_wmm_ac_e
+nxpwifi_wmm_convert_tos_to_ac(struct nxpwifi_adapter *adapter, u32 tos)
+{
+ /* Map of TOS UP values to WMM AC */
+ static const enum nxpwifi_wmm_ac_e tos_to_ac[] = {
+ WMM_AC_BE,
+ WMM_AC_BK,
+ WMM_AC_BK,
+ WMM_AC_BE,
+ WMM_AC_VI,
+ WMM_AC_VI,
+ WMM_AC_VO,
+ WMM_AC_VO
+ };
+
+ if (tos >= ARRAY_SIZE(tos_to_ac))
+ return WMM_AC_BE;
+
+ return tos_to_ac[tos];
+}
+
+/*
+ * Evaluates a given TID and downgrades it to a lower TID if the WMM Parameter
+ * element received from the AP indicates that the AP is disabled (due to call
+ * admission control (ACM bit).
+ */
+u8 nxpwifi_wmm_downgrade_tid(struct nxpwifi_private *priv, u32 tid)
+{
+ enum nxpwifi_wmm_ac_e ac, ac_down;
+ u8 new_tid;
+
+ ac = nxpwifi_wmm_convert_tos_to_ac(priv->adapter, tid);
+ ac_down = priv->wmm.ac_down_graded_vals[ac];
+
+ /*
+ * Send the index to tid array, picking from the array will be
+ * taken care by dequeuing function
+ */
+ new_tid = ac_to_tid[ac_down][tid % 2];
+
+ return new_tid;
+}
+
+/* Initializes the WMM state information and the WMM data path queues. */
+void
+nxpwifi_wmm_init(struct nxpwifi_adapter *adapter)
+{
+ int i, j;
+ struct nxpwifi_private *priv;
+
+ for (j = 0; j < adapter->priv_num; ++j) {
+ priv = adapter->priv[j];
+
+ for (i = 0; i < MAX_NUM_TID; ++i) {
+ if (!disable_tx_amsdu &&
+ adapter->tx_buf_size > NXPWIFI_TX_DATA_BUF_SIZE_2K)
+ priv->aggr_prio_tbl[i].amsdu =
+ priv->tos_to_tid_inv[i];
+ else
+ priv->aggr_prio_tbl[i].amsdu =
+ BA_STREAM_NOT_ALLOWED;
+ priv->aggr_prio_tbl[i].ampdu_ap =
+ priv->tos_to_tid_inv[i];
+ priv->aggr_prio_tbl[i].ampdu_user =
+ priv->tos_to_tid_inv[i];
+ }
+
+ priv->aggr_prio_tbl[6].amsdu =
+ priv->aggr_prio_tbl[6].ampdu_ap =
+ priv->aggr_prio_tbl[6].ampdu_user =
+ BA_STREAM_NOT_ALLOWED;
+
+ priv->aggr_prio_tbl[7].amsdu =
+ priv->aggr_prio_tbl[7].ampdu_ap =
+ priv->aggr_prio_tbl[7].ampdu_user =
+ BA_STREAM_NOT_ALLOWED;
+
+ nxpwifi_set_ba_params(priv);
+ nxpwifi_reset_11n_rx_seq_num(priv);
+
+ priv->wmm.drv_pkt_delay_max = NXPWIFI_WMM_DRV_DELAY_MAX;
+ atomic_set(&priv->wmm.tx_pkts_queued, 0);
+ atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID);
+ }
+}
+
+bool nxpwifi_bypass_txlist_empty(struct nxpwifi_adapter *adapter)
+{
+ struct nxpwifi_private *priv;
+ int i;
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+ if (!skb_queue_empty(&priv->bypass_txq))
+ return false;
+ }
+
+ return true;
+}
+
+/* Checks if WMM Tx queue is empty. */
+bool nxpwifi_wmm_lists_empty(struct nxpwifi_adapter *adapter)
+{
+ int i;
+ struct nxpwifi_private *priv;
+
+ for (i = 0; i < adapter->priv_num; ++i) {
+ priv = adapter->priv[i];
+ if (!priv->port_open)
+ continue;
+ if (atomic_read(&priv->wmm.tx_pkts_queued))
+ return false;
+ }
+
+ return true;
+}
+
+/* Deletes all packets in an RA list node. */
+static void
+nxpwifi_wmm_del_pkts_in_ralist_node(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ra_list)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct sk_buff *skb, *tmp;
+
+ skb_queue_walk_safe(&ra_list->skb_head, skb, tmp) {
+ skb_unlink(skb, &ra_list->skb_head);
+ nxpwifi_write_data_complete(adapter, skb, 0, -1);
+ }
+}
+
+/* Deletes all packets in an RA list. */
+static void
+nxpwifi_wmm_del_pkts_in_ralist(struct nxpwifi_private *priv,
+ struct list_head *ra_list_head)
+{
+ struct nxpwifi_ra_list_tbl *ra_list;
+
+ list_for_each_entry(ra_list, ra_list_head, list)
+ nxpwifi_wmm_del_pkts_in_ralist_node(priv, ra_list);
+}
+
+/* Deletes all packets in all RA lists. */
+static void nxpwifi_wmm_cleanup_queues(struct nxpwifi_private *priv)
+{
+ int i;
+
+ for (i = 0; i < MAX_NUM_TID; i++)
+ nxpwifi_wmm_del_pkts_in_ralist
+ (priv, &priv->wmm.tid_tbl_ptr[i].ra_list);
+
+ atomic_set(&priv->wmm.tx_pkts_queued, 0);
+ atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID);
+}
+
+/* Deletes all route addresses from all RA lists. */
+static void nxpwifi_wmm_delete_all_ralist(struct nxpwifi_private *priv)
+{
+ struct nxpwifi_ra_list_tbl *ra_list, *tmp_node;
+ int i;
+
+ for (i = 0; i < MAX_NUM_TID; ++i) {
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: ra_list: freeing buf for tid %d\n", i);
+ list_for_each_entry_safe(ra_list, tmp_node,
+ &priv->wmm.tid_tbl_ptr[i].ra_list,
+ list) {
+ list_del(&ra_list->list);
+ kfree(ra_list);
+ }
+
+ INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[i].ra_list);
+ }
+}
+
+static int nxpwifi_free_ack_frame(int id, void *p, void *data)
+{
+ pr_warn("Have pending ack frames!\n");
+ kfree_skb(p);
+ return 0;
+}
+
+/* Cleans up the Tx and Rx queues. */
+void
+nxpwifi_clean_txrx(struct nxpwifi_private *priv)
+{
+ struct sk_buff *skb, *tmp;
+ unsigned long index;
+ void *entry;
+
+ nxpwifi_11n_cleanup_reorder_tbl(priv);
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+
+ nxpwifi_wmm_cleanup_queues(priv);
+ nxpwifi_11n_delete_all_tx_ba_stream_tbl(priv);
+
+ if (priv->adapter->if_ops.cleanup_mpa_buf)
+ priv->adapter->if_ops.cleanup_mpa_buf(priv->adapter);
+
+ nxpwifi_wmm_delete_all_ralist(priv);
+ memcpy(tos_to_tid, ac_to_tid, sizeof(tos_to_tid));
+
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+
+ skb_queue_walk_safe(&priv->bypass_txq, skb, tmp) {
+ skb_unlink(skb, &priv->bypass_txq);
+ nxpwifi_write_data_complete(priv->adapter, skb, 0, -1);
+ }
+ atomic_set(&priv->adapter->bypass_tx_pending, 0);
+
+ xa_for_each(&priv->ack_status_frames, index, entry) {
+ nxpwifi_free_ack_frame(index, entry, NULL);
+ xa_erase(&priv->ack_status_frames, index);
+ }
+
+ xa_destroy(&priv->ack_status_frames);
+}
+
+/* Retrieves a particular RA list node, matching with the given TID and RA address. */
+struct nxpwifi_ra_list_tbl *
+nxpwifi_wmm_get_ralist_node(struct nxpwifi_private *priv, u8 tid,
+ const u8 *ra_addr)
+{
+ struct nxpwifi_ra_list_tbl *ra_list;
+
+ list_for_each_entry(ra_list, &priv->wmm.tid_tbl_ptr[tid].ra_list,
+ list) {
+ if (!memcmp(ra_list->ra, ra_addr, ETH_ALEN))
+ return ra_list;
+ }
+
+ return NULL;
+}
+
+void nxpwifi_update_ralist_tx_pause(struct nxpwifi_private *priv, u8 *mac,
+ u8 tx_pause)
+{
+ struct nxpwifi_ra_list_tbl *ra_list;
+ u32 pkt_cnt = 0, tx_pkts_queued;
+ int i;
+
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+
+ for (i = 0; i < MAX_NUM_TID; ++i) {
+ ra_list = nxpwifi_wmm_get_ralist_node(priv, i, mac);
+ if (ra_list && ra_list->tx_paused != tx_pause) {
+ pkt_cnt += ra_list->total_pkt_count;
+ ra_list->tx_paused = tx_pause;
+ if (tx_pause)
+ priv->wmm.pkts_paused[i] +=
+ ra_list->total_pkt_count;
+ else
+ priv->wmm.pkts_paused[i] -=
+ ra_list->total_pkt_count;
+ }
+ }
+
+ if (pkt_cnt) {
+ tx_pkts_queued = atomic_read(&priv->wmm.tx_pkts_queued);
+ if (tx_pause)
+ tx_pkts_queued -= pkt_cnt;
+ else
+ tx_pkts_queued += pkt_cnt;
+
+ atomic_set(&priv->wmm.tx_pkts_queued, tx_pkts_queued);
+ atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID);
+ }
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+}
+
+/* Retrieves an RA list node for a given TID and RA address pair. */
+struct nxpwifi_ra_list_tbl *
+nxpwifi_wmm_get_queue_raptr(struct nxpwifi_private *priv, u8 tid,
+ const u8 *ra_addr)
+{
+ struct nxpwifi_ra_list_tbl *ra_list;
+
+ ra_list = nxpwifi_wmm_get_ralist_node(priv, tid, ra_addr);
+ if (ra_list)
+ return ra_list;
+ nxpwifi_ralist_add(priv, ra_addr);
+
+ return nxpwifi_wmm_get_ralist_node(priv, tid, ra_addr);
+}
+
+/* Deletes RA list nodes for given mac for all TIDs. */
+void
+nxpwifi_wmm_del_peer_ra_list(struct nxpwifi_private *priv, const u8 *ra_addr)
+{
+ struct nxpwifi_ra_list_tbl *ra_list;
+ int i;
+
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+
+ for (i = 0; i < MAX_NUM_TID; ++i) {
+ ra_list = nxpwifi_wmm_get_ralist_node(priv, i, ra_addr);
+
+ if (!ra_list)
+ continue;
+ nxpwifi_wmm_del_pkts_in_ralist_node(priv, ra_list);
+ if (ra_list->tx_paused)
+ priv->wmm.pkts_paused[i] -= ra_list->total_pkt_count;
+ else
+ atomic_sub(ra_list->total_pkt_count,
+ &priv->wmm.tx_pkts_queued);
+ list_del(&ra_list->list);
+ kfree(ra_list);
+ }
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+}
+
+/* Checks if a particular RA list node exists in a given TID table index. */
+bool nxpwifi_is_ralist_valid(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ra_list, int ptr_index)
+{
+ struct nxpwifi_ra_list_tbl *rlist;
+
+ list_for_each_entry(rlist, &priv->wmm.tid_tbl_ptr[ptr_index].ra_list,
+ list) {
+ if (rlist == ra_list)
+ return true;
+ }
+
+ return false;
+}
+
+/* Adds a packet to bypass TX queue. */
+void
+nxpwifi_wmm_add_buf_bypass_txqueue(struct nxpwifi_private *priv,
+ struct sk_buff *skb)
+{
+ skb_queue_tail(&priv->bypass_txq, skb);
+}
+
+/* Adds a packet to WMM queue. */
+void
+nxpwifi_wmm_add_buf_txqueue(struct nxpwifi_private *priv,
+ struct sk_buff *skb)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ u32 tid;
+ struct nxpwifi_ra_list_tbl *ra_list = NULL;
+ struct list_head list_head;
+ u8 ra[ETH_ALEN], tid_down;
+ struct ethhdr *eth_hdr = (struct ethhdr *)skb->data;
+
+ memcpy(ra, eth_hdr->h_dest, ETH_ALEN);
+
+ if (!priv->media_connected && !nxpwifi_is_skb_mgmt_frame(skb)) {
+ nxpwifi_dbg(adapter, DATA, "data: drop packet in disconnect\n");
+ nxpwifi_write_data_complete(adapter, skb, 0, -1);
+ return;
+ }
+
+ tid = skb->priority;
+
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+
+ tid_down = nxpwifi_wmm_downgrade_tid(priv, tid);
+
+ /*
+ * In case of infra as we have already created the list during
+ * association we just don't have to call get_queue_raptr, we will
+ * have only 1 raptr for a tid in case of infra
+ */
+ if (!nxpwifi_queuing_ra_based(priv) &&
+ !nxpwifi_is_skb_mgmt_frame(skb)) {
+ list_head = priv->wmm.tid_tbl_ptr[tid_down].ra_list;
+ ra_list = list_first_entry_or_null(&list_head,
+ struct nxpwifi_ra_list_tbl,
+ list);
+ } else {
+ memcpy(ra, skb->data, ETH_ALEN);
+ if (is_multicast_ether_addr(ra) ||
+ nxpwifi_is_skb_mgmt_frame(skb))
+ eth_broadcast_addr(ra);
+ ra_list = nxpwifi_wmm_get_queue_raptr(priv, tid_down, ra);
+ }
+
+ if (!ra_list) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ nxpwifi_write_data_complete(adapter, skb, 0, -1);
+ return;
+ }
+
+ skb_queue_tail(&ra_list->skb_head, skb);
+
+ ra_list->ba_pkt_count++;
+ ra_list->total_pkt_count++;
+
+ if (atomic_read(&priv->wmm.highest_queued_prio) <
+ priv->tos_to_tid_inv[tid_down])
+ atomic_set(&priv->wmm.highest_queued_prio,
+ priv->tos_to_tid_inv[tid_down]);
+
+ if (ra_list->tx_paused)
+ priv->wmm.pkts_paused[tid_down]++;
+ else
+ atomic_inc(&priv->wmm.tx_pkts_queued);
+
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+}
+
+/* Processes the get WMM status command response from firmware. */
+int nxpwifi_ret_wmm_get_status(struct nxpwifi_private *priv,
+ const struct host_cmd_ds_command *resp)
+{
+ u8 *curr = (u8 *)&resp->params.get_wmm_status;
+ u16 resp_len = le16_to_cpu(resp->size), tlv_len;
+ int mask = IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK;
+ bool valid = true;
+
+ struct nxpwifi_ie_types_data *tlv_hdr;
+ struct nxpwifi_ie_types_wmm_queue_status *wmm_qs;
+ struct ieee80211_wmm_param_ie *wmm_param_ie = NULL;
+ struct nxpwifi_wmm_ac_status *ac_status;
+
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: WMM: WMM_GET_STATUS cmdresp received: %d\n",
+ resp_len);
+
+ while ((resp_len >= sizeof(tlv_hdr->header)) && valid) {
+ tlv_hdr = (struct nxpwifi_ie_types_data *)curr;
+ tlv_len = le16_to_cpu(tlv_hdr->header.len);
+
+ if (resp_len < tlv_len + sizeof(tlv_hdr->header))
+ break;
+
+ switch (le16_to_cpu(tlv_hdr->header.type)) {
+ case TLV_TYPE_WMMQSTATUS:
+ wmm_qs = (struct nxpwifi_ie_types_wmm_queue_status *)
+ tlv_hdr;
+ nxpwifi_dbg(priv->adapter, CMD,
+ "info: CMD_RESP: WMM_GET_STATUS:\t"
+ "QSTATUS TLV: %d, %d, %d\n",
+ wmm_qs->queue_index,
+ wmm_qs->flow_required,
+ wmm_qs->disabled);
+
+ ac_status = &priv->wmm.ac_status[wmm_qs->queue_index];
+ ac_status->disabled = wmm_qs->disabled;
+ ac_status->flow_required = wmm_qs->flow_required;
+ ac_status->flow_created = wmm_qs->flow_created;
+ break;
+
+ case WLAN_EID_VENDOR_SPECIFIC:
+ /*
+ * Point the regular IEEE element 2 bytes into the NXP element
+ * and setup the IEEE element type and length byte fields
+ */
+
+ wmm_param_ie =
+ (struct ieee80211_wmm_param_ie *)(curr + 2);
+ wmm_param_ie->len = (u8)tlv_len;
+ wmm_param_ie->element_id = WLAN_EID_VENDOR_SPECIFIC;
+
+ nxpwifi_dbg(priv->adapter, CMD,
+ "info: CMD_RESP: WMM_GET_STATUS:\t"
+ "WMM Parameter Set Count: %d\n",
+ wmm_param_ie->qos_info & mask);
+
+ if (wmm_param_ie->len + 2 >
+ sizeof(struct ieee80211_wmm_param_ie))
+ break;
+
+ memcpy(&priv->curr_bss_params.bss_descriptor.wmm_ie,
+ wmm_param_ie, wmm_param_ie->len + 2);
+
+ break;
+
+ default:
+ valid = false;
+ break;
+ }
+
+ curr += (tlv_len + sizeof(tlv_hdr->header));
+ resp_len -= (tlv_len + sizeof(tlv_hdr->header));
+ }
+
+ nxpwifi_wmm_setup_queue_priorities(priv, wmm_param_ie);
+ nxpwifi_wmm_setup_ac_downgrade(priv);
+
+ return 0;
+}
+
+/*
+ * Callback handler from the command module to allow insertion of a WMM TLV.
+ *
+ * If the BSS we are associating to supports WMM, this function adds the
+ * required WMM Information element to the association request command buffer in
+ * the form of a NXP extended IEEE element.
+ */
+u32
+nxpwifi_wmm_process_association_req(struct nxpwifi_private *priv,
+ u8 **assoc_buf,
+ struct ieee80211_wmm_param_ie *wmm_ie,
+ struct ieee80211_ht_cap *ht_cap)
+{
+ struct nxpwifi_ie_types_wmm_param_set *wmm_tlv;
+ u32 ret_len = 0;
+
+ /* Null checks */
+ if (!assoc_buf)
+ return 0;
+ if (!(*assoc_buf))
+ return 0;
+
+ if (!wmm_ie)
+ return 0;
+
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: WMM: process assoc req: bss->wmm_ie=%#x\n",
+ wmm_ie->element_id);
+
+ if ((priv->wmm_required ||
+ (ht_cap && (priv->config_bands & BAND_GN ||
+ priv->config_bands & BAND_AN))) &&
+ wmm_ie->element_id == WLAN_EID_VENDOR_SPECIFIC) {
+ wmm_tlv = (struct nxpwifi_ie_types_wmm_param_set *)*assoc_buf;
+ wmm_tlv->header.type = cpu_to_le16((u16)wmm_info_ie[0]);
+ wmm_tlv->header.len = cpu_to_le16((u16)wmm_info_ie[1]);
+ memcpy(wmm_tlv->wmm_ie, &wmm_info_ie[2],
+ le16_to_cpu(wmm_tlv->header.len));
+ if (wmm_ie->qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD)
+ memcpy((u8 *)(wmm_tlv->wmm_ie
+ + le16_to_cpu(wmm_tlv->header.len)
+ - sizeof(priv->wmm_qosinfo)),
+ &priv->wmm_qosinfo, sizeof(priv->wmm_qosinfo));
+
+ ret_len = sizeof(wmm_tlv->header)
+ + le16_to_cpu(wmm_tlv->header.len);
+
+ *assoc_buf += ret_len;
+ }
+
+ return ret_len;
+}
+
+/* Computes the time delay in the driver queues for a given packet. */
+u8
+nxpwifi_wmm_compute_drv_pkt_delay(struct nxpwifi_private *priv,
+ const struct sk_buff *skb)
+{
+ u32 queue_delay = ktime_to_ms(net_timedelta(skb->tstamp));
+ u8 ret_val;
+
+ /*
+ * Queue delay is passed as a uint8 in units of 2ms (ms shifted
+ * by 1). Min value (other than 0) is therefore 2ms, max is 510ms.
+ *
+ * Pass max value if queue_delay is beyond the uint8 range
+ */
+ ret_val = (u8)(min(queue_delay, priv->wmm.drv_pkt_delay_max) >> 1);
+
+ nxpwifi_dbg(priv->adapter, DATA, "data: WMM: Pkt Delay: %d ms,\t"
+ "%d ms sent to FW\n", queue_delay, ret_val);
+
+ return ret_val;
+}
+
+/* Retrieves the highest priority RA list table pointer. */
+static struct nxpwifi_ra_list_tbl *
+nxpwifi_wmm_get_highest_priolist_ptr(struct nxpwifi_adapter *adapter,
+ struct nxpwifi_private **priv, int *tid)
+{
+ struct nxpwifi_private *priv_tmp;
+ struct nxpwifi_ra_list_tbl *ptr;
+ struct nxpwifi_tid_tbl *tid_ptr;
+ atomic_t *hqp;
+ int i, j;
+ u8 to_tid;
+
+ /* check the BSS with highest priority first */
+ for (j = adapter->priv_num - 1; j >= 0; --j) {
+ /* iterate over BSS with the equal priority */
+ list_for_each_entry(adapter->bss_prio_tbl[j].bss_prio_cur,
+ &adapter->bss_prio_tbl[j].bss_prio_head,
+ list) {
+try_again:
+ priv_tmp = adapter->bss_prio_tbl[j].bss_prio_cur->priv;
+
+ if (!priv_tmp->port_open ||
+ (atomic_read(&priv_tmp->wmm.tx_pkts_queued) == 0))
+ continue;
+
+ /* iterate over the WMM queues of the BSS */
+ hqp = &priv_tmp->wmm.highest_queued_prio;
+ for (i = atomic_read(hqp); i >= LOW_PRIO_TID; --i) {
+ spin_lock_bh(&priv_tmp->wmm.ra_list_spinlock);
+
+ to_tid = tos_to_tid[i];
+ tid_ptr = &(priv_tmp)->wmm.tid_tbl_ptr[to_tid];
+
+ /* iterate over receiver addresses */
+ list_for_each_entry(ptr, &tid_ptr->ra_list,
+ list) {
+ if (!ptr->tx_paused &&
+ !skb_queue_empty(&ptr->skb_head))
+ /* holds both locks */
+ goto found;
+ }
+
+ spin_unlock_bh(&priv_tmp->wmm.ra_list_spinlock);
+ }
+
+ if (atomic_read(&priv_tmp->wmm.tx_pkts_queued) != 0) {
+ atomic_set(&priv_tmp->wmm.highest_queued_prio,
+ HIGH_PRIO_TID);
+ /*
+ * Iterate current private once more, since
+ * there still exist packets in data queue
+ */
+ goto try_again;
+ } else {
+ atomic_set(&priv_tmp->wmm.highest_queued_prio,
+ NO_PKT_PRIO_TID);
+ }
+ }
+ }
+
+ return NULL;
+
+found:
+ /* holds ra_list_spinlock */
+ if (atomic_read(hqp) > i)
+ atomic_set(hqp, i);
+ spin_unlock_bh(&priv_tmp->wmm.ra_list_spinlock);
+
+ *priv = priv_tmp;
+ *tid = tos_to_tid[i];
+
+ return ptr;
+}
+
+/* Rotates ra and bss lists so packets are picked round robin. */
+void nxpwifi_rotate_priolists(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ra,
+ int tid)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct nxpwifi_bss_prio_tbl *tbl = adapter->bss_prio_tbl;
+ struct nxpwifi_tid_tbl *tid_ptr = &priv->wmm.tid_tbl_ptr[tid];
+
+ spin_lock_bh(&tbl[priv->bss_priority].bss_prio_lock);
+ /*
+ * dirty trick: we remove 'head' temporarily and reinsert it after
+ * curr bss node. imagine list to stay fixed while head is moved
+ */
+ list_move(&tbl[priv->bss_priority].bss_prio_head,
+ &tbl[priv->bss_priority].bss_prio_cur->list);
+ spin_unlock_bh(&tbl[priv->bss_priority].bss_prio_lock);
+
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+ if (nxpwifi_is_ralist_valid(priv, ra, tid)) {
+ priv->wmm.packets_out[tid]++;
+ /* same as above */
+ list_move(&tid_ptr->ra_list, &ra->list);
+ }
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+}
+
+/* Checks if 11n aggregation is possible. */
+static bool
+nxpwifi_is_11n_aggragation_possible(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ptr,
+ int max_buf_size)
+{
+ int count = 0, total_size = 0;
+ struct sk_buff *skb, *tmp;
+ int max_amsdu_size;
+
+ if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP && priv->ap_11n_enabled &&
+ ptr->is_11n_enabled)
+ max_amsdu_size = min_t(int, ptr->max_amsdu, max_buf_size);
+ else
+ max_amsdu_size = max_buf_size;
+
+ skb_queue_walk_safe(&ptr->skb_head, skb, tmp) {
+ total_size += skb->len;
+ if (total_size >= max_amsdu_size)
+ break;
+ if (++count >= MIN_NUM_AMSDU)
+ return true;
+ }
+
+ return false;
+}
+
+/* Sends a single packet to firmware for transmission. */
+static void
+nxpwifi_send_single_packet(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ptr, int ptr_index)
+__releases(&priv->wmm.ra_list_spinlock)
+{
+ struct sk_buff *skb, *skb_next;
+ struct nxpwifi_tx_param tx_param;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct nxpwifi_txinfo *tx_info;
+
+ if (skb_queue_empty(&ptr->skb_head)) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ nxpwifi_dbg(adapter, DATA, "data: nothing to send\n");
+ return;
+ }
+
+ skb = skb_dequeue(&ptr->skb_head);
+
+ tx_info = NXPWIFI_SKB_TXCB(skb);
+ nxpwifi_dbg(adapter, DATA,
+ "data: dequeuing the packet %p %p\n", ptr, skb);
+
+ ptr->total_pkt_count--;
+
+ if (!skb_queue_empty(&ptr->skb_head))
+ skb_next = skb_peek(&ptr->skb_head);
+ else
+ skb_next = NULL;
+
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+
+ tx_param.next_pkt_len = ((skb_next) ? skb_next->len +
+ sizeof(struct txpd) : 0);
+
+ if (nxpwifi_process_tx(priv, skb, &tx_param) == -EBUSY) {
+ /* Queue the packet back at the head */
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+
+ if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ nxpwifi_write_data_complete(adapter, skb, 0, -1);
+ return;
+ }
+
+ skb_queue_tail(&ptr->skb_head, skb);
+
+ ptr->total_pkt_count++;
+ ptr->ba_pkt_count++;
+ tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT;
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ } else {
+ nxpwifi_rotate_priolists(priv, ptr, ptr_index);
+ atomic_dec(&priv->wmm.tx_pkts_queued);
+ }
+}
+
+/* Checks if the first packet in the given RA list is already processed or not. */
+static bool
+nxpwifi_is_ptr_processed(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ptr)
+{
+ struct sk_buff *skb;
+ struct nxpwifi_txinfo *tx_info;
+
+ if (skb_queue_empty(&ptr->skb_head))
+ return false;
+
+ skb = skb_peek(&ptr->skb_head);
+
+ tx_info = NXPWIFI_SKB_TXCB(skb);
+ if (tx_info->flags & NXPWIFI_BUF_FLAG_REQUEUED_PKT)
+ return true;
+
+ return false;
+}
+
+/* Sends a single processed packet to firmware for transmission. */
+static void
+nxpwifi_send_processed_packet(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ptr, int ptr_index)
+ __releases(&priv->wmm.ra_list_spinlock)
+{
+ struct nxpwifi_tx_param tx_param;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ int ret;
+ struct sk_buff *skb, *skb_next;
+ struct nxpwifi_txinfo *tx_info;
+
+ if (skb_queue_empty(&ptr->skb_head)) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ return;
+ }
+
+ skb = skb_dequeue(&ptr->skb_head);
+
+ if (adapter->data_sent || adapter->tx_lock_flag) {
+ ptr->total_pkt_count--;
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ skb_queue_tail(&adapter->tx_data_q, skb);
+ atomic_dec(&priv->wmm.tx_pkts_queued);
+ atomic_inc(&adapter->tx_queued);
+ return;
+ }
+
+ if (!skb_queue_empty(&ptr->skb_head))
+ skb_next = skb_peek(&ptr->skb_head);
+ else
+ skb_next = NULL;
+
+ tx_info = NXPWIFI_SKB_TXCB(skb);
+
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+
+ tx_param.next_pkt_len =
+ ((skb_next) ? skb_next->len +
+ sizeof(struct txpd) : 0);
+
+ ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA,
+ skb, &tx_param);
+
+ switch (ret) {
+ case -EBUSY:
+ nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n");
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+
+ if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ nxpwifi_write_data_complete(adapter, skb, 0, -1);
+ return;
+ }
+
+ skb_queue_tail(&ptr->skb_head, skb);
+
+ tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT;
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ break;
+ case -EINPROGRESS:
+ break;
+ case 0:
+ nxpwifi_write_data_complete(adapter, skb, 0, ret);
+ break;
+ default:
+ nxpwifi_dbg(adapter, ERROR, "host_to_card failed: %#x\n", ret);
+ adapter->dbg.num_tx_host_to_card_failure++;
+ nxpwifi_write_data_complete(adapter, skb, 0, ret);
+ break;
+ }
+
+ if (ret != -EBUSY) {
+ nxpwifi_rotate_priolists(priv, ptr, ptr_index);
+ atomic_dec(&priv->wmm.tx_pkts_queued);
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+ ptr->total_pkt_count--;
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ }
+}
+
+/* Dequeues a packet from the highest priority list and transmits it. */
+static int
+nxpwifi_dequeue_tx_packet(struct nxpwifi_adapter *adapter)
+{
+ struct nxpwifi_ra_list_tbl *ptr;
+ struct nxpwifi_private *priv = NULL;
+ int ptr_index = 0;
+ u8 ra[ETH_ALEN];
+ int tid_del = 0, tid = 0;
+
+ ptr = nxpwifi_wmm_get_highest_priolist_ptr(adapter, &priv, &ptr_index);
+ if (!ptr)
+ return -ENOENT;
+
+ tid = nxpwifi_get_tid(ptr);
+
+ nxpwifi_dbg(adapter, DATA, "data: tid=%d\n", tid);
+
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+ if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ return -EINVAL;
+ }
+
+ if (nxpwifi_is_ptr_processed(priv, ptr)) {
+ nxpwifi_send_processed_packet(priv, ptr, ptr_index);
+ /*
+ * ra_list_spinlock has been freed in
+ * nxpwifi_send_processed_packet()
+ */
+ return 0;
+ }
+
+ if (!ptr->is_11n_enabled ||
+ ptr->ba_status ||
+ priv->wps.session_enable) {
+ if (ptr->is_11n_enabled &&
+ ptr->ba_status &&
+ ptr->amsdu_in_ampdu &&
+ nxpwifi_is_amsdu_allowed(priv, tid) &&
+ nxpwifi_is_11n_aggragation_possible(priv, ptr,
+ adapter->tx_buf_size))
+ nxpwifi_11n_aggregate_pkt(priv, ptr, ptr_index);
+ /*
+ * ra_list_spinlock has been freed in
+ * nxpwifi_11n_aggregate_pkt()
+ */
+ else
+ nxpwifi_send_single_packet(priv, ptr, ptr_index);
+ /*
+ * ra_list_spinlock has been freed in
+ * nxpwifi_send_single_packet()
+ */
+ } else {
+ if (nxpwifi_is_ampdu_allowed(priv, ptr, tid) &&
+ ptr->ba_pkt_count > ptr->ba_packet_thr) {
+ if (nxpwifi_space_avail_for_new_ba_stream(adapter)) {
+ nxpwifi_create_ba_tbl(priv, ptr->ra, tid,
+ BA_SETUP_INPROGRESS);
+ nxpwifi_send_addba(priv, tid, ptr->ra);
+ } else if (nxpwifi_find_stream_to_delete
+ (priv, tid, &tid_del, ra)) {
+ nxpwifi_create_ba_tbl(priv, ptr->ra, tid,
+ BA_SETUP_INPROGRESS);
+ nxpwifi_send_delba(priv, tid_del, ra, 1);
+ }
+ }
+ if (nxpwifi_is_amsdu_allowed(priv, tid) &&
+ nxpwifi_is_11n_aggragation_possible(priv, ptr,
+ adapter->tx_buf_size))
+ nxpwifi_11n_aggregate_pkt(priv, ptr, ptr_index);
+ /*
+ * ra_list_spinlock has been freed in
+ * nxpwifi_11n_aggregate_pkt()
+ */
+ else
+ nxpwifi_send_single_packet(priv, ptr, ptr_index);
+ /*
+ * ra_list_spinlock has been freed in
+ * nxpwifi_send_single_packet()
+ */
+ }
+ return 0;
+}
+
+void nxpwifi_process_bypass_tx(struct nxpwifi_adapter *adapter)
+{
+ struct nxpwifi_tx_param tx_param;
+ struct sk_buff *skb;
+ struct nxpwifi_txinfo *tx_info;
+ struct nxpwifi_private *priv;
+ int i;
+
+ if (adapter->data_sent || adapter->tx_lock_flag)
+ return;
+
+ for (i = 0; i < adapter->priv_num; ++i) {
+ priv = adapter->priv[i];
+
+ if (skb_queue_empty(&priv->bypass_txq))
+ continue;
+
+ skb = skb_dequeue(&priv->bypass_txq);
+ tx_info = NXPWIFI_SKB_TXCB(skb);
+
+ /* no aggregation for bypass packets */
+ tx_param.next_pkt_len = 0;
+
+ if (nxpwifi_process_tx(priv, skb, &tx_param) == -EBUSY) {
+ skb_queue_head(&priv->bypass_txq, skb);
+ tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT;
+ } else {
+ atomic_dec(&adapter->bypass_tx_pending);
+ }
+ }
+}
+
+/* Transmits the highest priority packet awaiting in the WMM Queues. */
+void
+nxpwifi_wmm_process_tx(struct nxpwifi_adapter *adapter)
+{
+ do {
+ if (nxpwifi_dequeue_tx_packet(adapter))
+ break;
+ if (adapter->iface_type != NXPWIFI_SDIO) {
+ if (adapter->data_sent ||
+ adapter->tx_lock_flag)
+ break;
+ } else {
+ if (atomic_read(&adapter->tx_queued) >=
+ NXPWIFI_MAX_PKTS_TXQ)
+ break;
+ }
+ } while (!nxpwifi_wmm_lists_empty(adapter));
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/wmm.h b/drivers/net/wireless/nxp/nxpwifi/wmm.h
new file mode 100644
index 000000000000..6241c2c5fcc4
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/wmm.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * NXP Wireless LAN device driver: WMM
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#ifndef _NXPWIFI_WMM_H_
+#define _NXPWIFI_WMM_H_
+
+enum ieee_types_wmm_aciaifsn_bitmasks {
+ NXPWIFI_AIFSN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)),
+ NXPWIFI_ACM = BIT(4),
+ NXPWIFI_ACI = (BIT(5) | BIT(6)),
+};
+
+enum ieee_types_wmm_ecw_bitmasks {
+ NXPWIFI_ECW_MIN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)),
+ NXPWIFI_ECW_MAX = (BIT(4) | BIT(5) | BIT(6) | BIT(7)),
+};
+
+extern const u16 nxpwifi_1d_to_wmm_queue[];
+extern const u8 tos_to_tid_inv[];
+
+/* Retrieve the TID of the given RA list. */
+static inline int
+nxpwifi_get_tid(struct nxpwifi_ra_list_tbl *ptr)
+{
+ struct sk_buff *skb;
+
+ if (skb_queue_empty(&ptr->skb_head))
+ return 0;
+
+ skb = skb_peek(&ptr->skb_head);
+
+ return skb->priority;
+}
+
+void nxpwifi_wmm_add_buf_txqueue(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+void nxpwifi_wmm_add_buf_bypass_txqueue(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+void nxpwifi_ralist_add(struct nxpwifi_private *priv, const u8 *ra);
+void nxpwifi_rotate_priolists(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ra, int tid);
+
+bool nxpwifi_wmm_lists_empty(struct nxpwifi_adapter *adapter);
+bool nxpwifi_bypass_txlist_empty(struct nxpwifi_adapter *adapter);
+void nxpwifi_wmm_process_tx(struct nxpwifi_adapter *adapter);
+void nxpwifi_process_bypass_tx(struct nxpwifi_adapter *adapter);
+bool nxpwifi_is_ralist_valid(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ra_list, int tid);
+
+u8 nxpwifi_wmm_compute_drv_pkt_delay(struct nxpwifi_private *priv,
+ const struct sk_buff *skb);
+void nxpwifi_wmm_init(struct nxpwifi_adapter *adapter);
+
+u32 nxpwifi_wmm_process_association_req(struct nxpwifi_private *priv,
+ u8 **assoc_buf,
+ struct ieee80211_wmm_param_ie *wmmie,
+ struct ieee80211_ht_cap *htcap);
+
+void nxpwifi_wmm_setup_queue_priorities(struct nxpwifi_private *priv,
+ struct ieee80211_wmm_param_ie *wmm_ie);
+void nxpwifi_wmm_setup_ac_downgrade(struct nxpwifi_private *priv);
+int nxpwifi_ret_wmm_get_status(struct nxpwifi_private *priv,
+ const struct host_cmd_ds_command *resp);
+struct nxpwifi_ra_list_tbl *
+nxpwifi_wmm_get_queue_raptr(struct nxpwifi_private *priv, u8 tid,
+ const u8 *ra_addr);
+u8 nxpwifi_wmm_downgrade_tid(struct nxpwifi_private *priv, u32 tid);
+void nxpwifi_update_ralist_tx_pause(struct nxpwifi_private *priv, u8 *mac,
+ u8 tx_pause);
+
+struct nxpwifi_ra_list_tbl *nxpwifi_wmm_get_ralist_node(struct nxpwifi_private
+ *priv, u8 tid, const u8 *ra_addr);
+#endif /* !_NXPWIFI_WMM_H_ */
--
2.34.1
^ permalink raw reply related
* [PATCH v10 04/21] wifi: nxpwifi: add 802.11h DFS/TPC support for 5 GHz operation
From: Jeff Chen @ 2026-03-05 14:39 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, johannes, francesco, wyatt.hsu, s.hauer, Jeff Chen
In-Reply-To: <20260305143939.3724868-1-jeff.chen_1@nxp.com>
Introduce 802.11h functionality to enable DFS and Transmit Power Control
(TPC) handling required for 5 GHz regulatory compliance.
Handle DFS Channel Availability Check (CAC), including start, timeout,
and abort flows, and process firmware radar reports via
HOST_CMD_CHAN_REPORT_REQUEST and corresponding events. Support channel
switch operations with AP restart and beacon updates through cfg80211.
Implement TPC handling during association using the power capability and
local power constraint elements. Delegate radar detection to firmware,
while the driver constructs TLVs, processes events, and integrates with
cfg80211 for state updates and notifications.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/11h.c | 339 +++++++++++++++++++++++++
1 file changed, 339 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11h.c
diff --git a/drivers/net/wireless/nxp/nxpwifi/11h.c b/drivers/net/wireless/nxp/nxpwifi/11h.c
new file mode 100644
index 000000000000..44af3a40a45c
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11h.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nxpwifi: 802.11h helpers
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#include "main.h"
+#include "cmdevt.h"
+#include "fw.h"
+#include "cfg80211.h"
+
+void nxpwifi_init_11h_params(struct nxpwifi_private *priv)
+{
+ priv->state_11h.is_11h_enabled = true;
+ priv->state_11h.is_11h_active = false;
+}
+
+inline int nxpwifi_is_11h_active(struct nxpwifi_private *priv)
+{
+ return priv->state_11h.is_11h_active;
+}
+
+/* appends 11h info to a buffer while joining an infrastructure BSS */
+static void
+nxpwifi_11h_process_infra_join(struct nxpwifi_private *priv, u8 **buffer,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ struct nxpwifi_ie_types_header *ie_header;
+ struct nxpwifi_ie_types_pwr_capability *cap;
+ struct nxpwifi_ie_types_local_pwr_constraint *constraint;
+ struct ieee80211_supported_band *sband;
+ u8 radio_type;
+ int i;
+
+ if (!buffer || !(*buffer))
+ return;
+
+ radio_type = nxpwifi_band_to_radio_type((u8)bss_desc->bss_band);
+ sband = priv->wdev.wiphy->bands[radio_type];
+
+ cap = (struct nxpwifi_ie_types_pwr_capability *)*buffer;
+ cap->header.type = cpu_to_le16(WLAN_EID_PWR_CAPABILITY);
+ cap->header.len = cpu_to_le16(2);
+ cap->min_pwr = 0;
+ cap->max_pwr = 0;
+ *buffer += sizeof(*cap);
+
+ constraint = (struct nxpwifi_ie_types_local_pwr_constraint *)*buffer;
+ constraint->header.type = cpu_to_le16(WLAN_EID_PWR_CONSTRAINT);
+ constraint->header.len = cpu_to_le16(2);
+ constraint->chan = bss_desc->channel;
+ constraint->constraint = bss_desc->local_constraint;
+ *buffer += sizeof(*constraint);
+
+ ie_header = (struct nxpwifi_ie_types_header *)*buffer;
+ ie_header->type = cpu_to_le16(TLV_TYPE_PASSTHROUGH);
+ ie_header->len = cpu_to_le16(2 * sband->n_channels + 2);
+ *buffer += sizeof(*ie_header);
+ *(*buffer)++ = WLAN_EID_SUPPORTED_CHANNELS;
+ *(*buffer)++ = 2 * sband->n_channels;
+ for (i = 0; i < sband->n_channels; i++) {
+ u32 center_freq;
+
+ center_freq = sband->channels[i].center_freq;
+ *(*buffer)++ = ieee80211_frequency_to_channel(center_freq);
+ *(*buffer)++ = 1; /* one channel in the subband */
+ }
+}
+
+/* Enable or disable the 11h extensions in the firmware */
+int nxpwifi_11h_activate(struct nxpwifi_private *priv, bool flag)
+{
+ u32 enable = flag;
+
+ /* enable master mode radar detection on AP interface */
+ if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) && enable)
+ enable |= NXPWIFI_MASTER_RADAR_DET_MASK;
+
+ return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB,
+ HOST_ACT_GEN_SET, DOT11H_I, &enable, true);
+}
+
+/*
+ * Process TLV buffer for a pending BSS join. Enable 11h in firmware when the
+ * network advertises spectrum management, and add required TLVs based on the
+ * BSS's 11h capability.
+ */
+void nxpwifi_11h_process_join(struct nxpwifi_private *priv, u8 **buffer,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ if (bss_desc->sensed_11h) {
+ /* Activate 11h functions in firmware, turns on capability bit */
+ nxpwifi_11h_activate(priv, true);
+ priv->state_11h.is_11h_active = true;
+ bss_desc->cap_info_bitmap |= WLAN_CAPABILITY_SPECTRUM_MGMT;
+ nxpwifi_11h_process_infra_join(priv, buffer, bss_desc);
+ } else {
+ /* Deactivate 11h functions in the firmware */
+ nxpwifi_11h_activate(priv, false);
+ priv->state_11h.is_11h_active = false;
+ bss_desc->cap_info_bitmap &= ~WLAN_CAPABILITY_SPECTRUM_MGMT;
+ }
+}
+
+/*
+ * DFS CAC work function. This delayed work emits CAC finished event for cfg80211
+ * if CAC was started earlier
+ */
+void nxpwifi_dfs_cac_work(struct wiphy *wiphy, struct wiphy_work *work)
+{
+ struct cfg80211_chan_def chandef;
+ struct wiphy_delayed_work *delayed_work =
+ container_of(work, struct wiphy_delayed_work, work);
+ struct nxpwifi_private *priv = container_of(delayed_work,
+ struct nxpwifi_private,
+ dfs_cac_work);
+
+ chandef = priv->dfs_chandef;
+ if (priv->wdev.links[0].cac_started) {
+ nxpwifi_dbg(priv->adapter, MSG,
+ "CAC timer finished; No radar detected\n");
+ cfg80211_cac_event(priv->netdev, &chandef,
+ NL80211_RADAR_CAC_FINISHED,
+ GFP_KERNEL, 0);
+ }
+}
+
+/* prepares channel report request command to FW for starting radar detection */
+int nxpwifi_cmd_issue_chan_report_request(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ void *data_buf)
+{
+ struct host_cmd_ds_chan_rpt_req *cr_req = &cmd->params.chan_rpt_req;
+ struct nxpwifi_radar_params *radar_params = (void *)data_buf;
+ u16 size;
+
+ cmd->command = cpu_to_le16(HOST_CMD_CHAN_REPORT_REQUEST);
+ size = S_DS_GEN;
+
+ cr_req->chan_desc.start_freq = cpu_to_le16(NXPWIFI_A_BAND_START_FREQ);
+ nxpwifi_convert_chan_to_band_cfg(priv,
+ &cr_req->chan_desc.band_cfg,
+ radar_params->chandef);
+ cr_req->chan_desc.chan_num = radar_params->chandef->chan->hw_value;
+ cr_req->msec_dwell_time = cpu_to_le32(radar_params->cac_time_ms);
+ size += sizeof(*cr_req);
+
+ if (radar_params->cac_time_ms) {
+ struct nxpwifi_ie_types_chan_rpt_data *rpt;
+
+ rpt = (struct nxpwifi_ie_types_chan_rpt_data *)((u8 *)cmd + size);
+ rpt->header.type = cpu_to_le16(TLV_TYPE_CHANRPT_11H_BASIC);
+ rpt->header.len = cpu_to_le16(sizeof(u8));
+ rpt->meas_rpt_map = 1 << MEAS_RPT_MAP_RADAR_SHIFT_BIT;
+ size += sizeof(*rpt);
+
+ nxpwifi_dbg(priv->adapter, MSG,
+ "11h: issuing DFS Radar check for channel=%d\n",
+ radar_params->chandef->chan->hw_value);
+ } else {
+ nxpwifi_dbg(priv->adapter, MSG, "cancelling CAC\n");
+ }
+
+ cmd->size = cpu_to_le16(size);
+
+ return 0;
+}
+
+int nxpwifi_stop_radar_detection(struct nxpwifi_private *priv,
+ struct cfg80211_chan_def *chandef)
+{
+ struct nxpwifi_radar_params radar_params;
+
+ memset(&radar_params, 0, sizeof(struct nxpwifi_radar_params));
+ radar_params.chandef = chandef;
+ radar_params.cac_time_ms = 0;
+
+ return nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REPORT_REQUEST,
+ HOST_ACT_GEN_SET, 0, &radar_params, true);
+}
+
+/* Abort ongoing CAC when stopping AP operations or during unload */
+void nxpwifi_abort_cac(struct nxpwifi_private *priv)
+{
+ if (priv->wdev.links[0].cac_started) {
+ if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef))
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "failed to stop CAC in FW\n");
+ nxpwifi_dbg(priv->adapter, MSG,
+ "Aborting delayed work for CAC.\n");
+ wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work);
+ cfg80211_cac_event(priv->netdev, &priv->dfs_chandef,
+ NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0);
+ }
+}
+
+/*
+ * handles channel report event from FW during CAC period. If radar is detected
+ * during CAC, driver indicates the same to cfg80211 and also cancels ongoing
+ * delayed work
+ */
+int nxpwifi_11h_handle_chanrpt_ready(struct nxpwifi_private *priv,
+ struct sk_buff *skb)
+{
+ struct host_cmd_ds_chan_rpt_event *rpt_event;
+ struct nxpwifi_ie_types_chan_rpt_data *rpt;
+ u16 event_len, tlv_len;
+
+ rpt_event = (void *)(skb->data + sizeof(u32));
+ event_len = skb->len - (sizeof(struct host_cmd_ds_chan_rpt_event) +
+ sizeof(u32));
+
+ if (le32_to_cpu(rpt_event->result) != HOST_RESULT_OK) {
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Error in channel report event\n");
+ return -EINVAL;
+ }
+
+ while (event_len >= sizeof(struct nxpwifi_ie_types_header)) {
+ rpt = (void *)&rpt_event->tlvbuf;
+ tlv_len = le16_to_cpu(rpt->header.len);
+
+ switch (le16_to_cpu(rpt->header.type)) {
+ case TLV_TYPE_CHANRPT_11H_BASIC:
+ if (rpt->meas_rpt_map & MEAS_RPT_MAP_RADAR_MASK) {
+ nxpwifi_dbg(priv->adapter, MSG,
+ "RADAR Detected on channel %d!\n",
+ priv->dfs_chandef.chan->hw_value);
+
+ wiphy_delayed_work_cancel(priv->adapter->wiphy,
+ &priv->dfs_cac_work);
+ cfg80211_cac_event(priv->netdev,
+ &priv->dfs_chandef,
+ NL80211_RADAR_CAC_ABORTED,
+ GFP_KERNEL, 0);
+ cfg80211_radar_event(priv->adapter->wiphy,
+ &priv->dfs_chandef,
+ GFP_KERNEL);
+ }
+ break;
+ default:
+ break;
+ }
+
+ event_len -= (tlv_len + sizeof(rpt->header));
+ }
+
+ return 0;
+}
+
+/* Handler for radar detected event from FW */
+int nxpwifi_11h_handle_radar_detected(struct nxpwifi_private *priv,
+ struct sk_buff *skb)
+{
+ struct nxpwifi_radar_det_event *rdr_event;
+
+ rdr_event = (void *)(skb->data + sizeof(u32));
+
+ nxpwifi_dbg(priv->adapter, MSG,
+ "radar detected; indicating kernel\n");
+
+ if (priv->wdev.links[0].cac_started) {
+ if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef))
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Failed to stop CAC in FW\n");
+ wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work);
+ cfg80211_cac_event(priv->netdev, &priv->dfs_chandef,
+ NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0);
+ }
+ cfg80211_radar_event(priv->adapter->wiphy, &priv->dfs_chandef,
+ GFP_KERNEL);
+ nxpwifi_dbg(priv->adapter, MSG, "regdomain: %d\n",
+ rdr_event->reg_domain);
+ nxpwifi_dbg(priv->adapter, MSG, "radar detection type: %d\n",
+ rdr_event->det_type);
+
+ return 0;
+}
+
+/*
+ * work function for channel switch handling. takes care of updating new channel
+ * definitin to bss config structure, restart AP and indicate channel switch
+ * success to cfg80211
+ */
+void nxpwifi_dfs_chan_sw_work(struct wiphy *wiphy, struct wiphy_work *work)
+{
+ struct nxpwifi_uap_bss_param *bss_cfg;
+ struct wiphy_delayed_work *delayed_work =
+ container_of(work, struct wiphy_delayed_work, work);
+ struct nxpwifi_private *priv = container_of(delayed_work,
+ struct nxpwifi_private,
+ dfs_chan_sw_work);
+ struct nxpwifi_adapter *adapter = priv->adapter;
+
+ if (nxpwifi_del_mgmt_ies(priv))
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Failed to delete mgmt IEs!\n");
+
+ bss_cfg = &priv->bss_cfg;
+ if (!bss_cfg->beacon_period) {
+ nxpwifi_dbg(adapter, ERROR,
+ "channel switch: AP already stopped\n");
+ return;
+ }
+
+ if (nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP,
+ HOST_ACT_GEN_SET, 0, NULL, true)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "channel switch: Failed to stop the BSS\n");
+ return;
+ }
+
+ if (nxpwifi_cfg80211_change_beacon(adapter->wiphy, priv->netdev,
+ &priv->ap_update_info)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "channel switch: Failed to set beacon\n");
+ return;
+ }
+
+ nxpwifi_uap_set_channel(priv, bss_cfg, priv->dfs_chandef);
+
+ if (nxpwifi_config_start_uap(priv, bss_cfg)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "Failed to start AP after channel switch\n");
+ return;
+ }
+
+ nxpwifi_dbg(adapter, MSG,
+ "indicating channel switch completion to kernel\n");
+
+ cfg80211_ch_switch_notify(priv->netdev, &priv->dfs_chandef, 0);
+
+ if (priv->uap_stop_tx) {
+ netif_carrier_on(priv->netdev);
+ nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter);
+ priv->uap_stop_tx = false;
+ }
+}
--
2.34.1
^ permalink raw reply related
* [PATCH v10 03/21] wifi: nxpwifi: add initial 802.11ax support for STA and AP modes
From: Jeff Chen @ 2026-03-05 14:39 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, johannes, francesco, wyatt.hsu, s.hauer, Jeff Chen
In-Reply-To: <20260305143939.3724868-1-jeff.chen_1@nxp.com>
Introduce initial 802.11ax functionality, enabling HE capability
negotiation and configuration for both STA and AP roles.
Convert HE elements from cfg80211 into firmware TLVs and append them to
HOST_CMD_802_11_ASSOCIATE when operating as a station. For AP mode,
convert HE elements into parameters for HOST_CMD_11AX_CFG and provide
them to the firmware for configuration.
Handle HE MAC/PHY capabilities, MCS maps, and Target Wake Time (TWT)
negotiation. Add support for additional 11ax-specific firmware commands,
including OBSS PD, beamforming, TXOMI, and broadcast TWT.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/11ax.c | 594 ++++++++++++++++++++++++
drivers/net/wireless/nxp/nxpwifi/11ax.h | 73 +++
2 files changed, 667 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.h
diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.c b/drivers/net/wireless/nxp/nxpwifi/11ax.c
new file mode 100644
index 000000000000..cc47c435eb70
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11ax.c
@@ -0,0 +1,594 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* nxpwifi: 802.11ax (HE) support
+ * Copyright (C) 2011-2024 NXP
+ */
+
+#include "cfg.h"
+#include "fw.h"
+#include "main.h"
+#include "11ax.h"
+
+void nxpwifi_update_11ax_cap(struct nxpwifi_adapter *adapter,
+ struct hw_spec_extension *hw_he_cap)
+{
+ struct nxpwifi_private *priv;
+ struct nxpwifi_ie_types_he_cap *he_cap = NULL;
+ struct nxpwifi_ie_types_he_cap *user_he_cap = NULL;
+ u8 header_len = sizeof(struct nxpwifi_ie_types_header);
+ u16 data_len = le16_to_cpu(hw_he_cap->header.len);
+ bool he_cap_2g = false;
+ int i;
+
+ if ((data_len + header_len) > sizeof(adapter->hw_he_cap)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "hw_he_cap too big, len=%d\n",
+ data_len);
+ return;
+ }
+
+ he_cap = (struct nxpwifi_ie_types_he_cap *)hw_he_cap;
+
+ if (he_cap->he_phy_cap[0] &
+ (AX_2G_40MHZ_SUPPORT | AX_2G_20MHZ_SUPPORT)) {
+ adapter->hw_2g_he_cap_len = data_len + header_len;
+ memcpy(adapter->hw_2g_he_cap, (u8 *)hw_he_cap,
+ adapter->hw_2g_he_cap_len);
+ adapter->fw_bands |= BAND_GAX;
+ he_cap_2g = true;
+ nxpwifi_dbg_dump(adapter, CMD_D, "2.4G HE capability element ",
+ adapter->hw_2g_he_cap,
+ adapter->hw_2g_he_cap_len);
+ } else {
+ adapter->hw_he_cap_len = data_len + header_len;
+ memcpy(adapter->hw_he_cap, (u8 *)hw_he_cap,
+ adapter->hw_he_cap_len);
+ adapter->fw_bands |= BAND_AAX;
+ nxpwifi_dbg_dump(adapter, CMD_D, "5G HE capability element ",
+ adapter->hw_he_cap,
+ adapter->hw_he_cap_len);
+ }
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+
+ if (he_cap_2g) {
+ priv->user_2g_he_cap_len = adapter->hw_2g_he_cap_len;
+ memcpy(priv->user_2g_he_cap, adapter->hw_2g_he_cap,
+ sizeof(adapter->hw_2g_he_cap));
+ user_he_cap = (struct nxpwifi_ie_types_he_cap *)
+ priv->user_2g_he_cap;
+ } else {
+ priv->user_he_cap_len = adapter->hw_he_cap_len;
+ memcpy(priv->user_he_cap, adapter->hw_he_cap,
+ sizeof(adapter->hw_he_cap));
+ user_he_cap = (struct nxpwifi_ie_types_he_cap *)
+ priv->user_he_cap;
+ }
+
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA)
+ user_he_cap->he_mac_cap[0] &=
+ ~HE_MAC_CAP_TWT_RESP_SUPPORT;
+ else
+ user_he_cap->he_mac_cap[0] &=
+ ~HE_MAC_CAP_TWT_REQ_SUPPORT;
+ }
+
+ adapter->is_hw_11ax_capable = true;
+}
+
+bool nxpwifi_11ax_bandconfig_allowed(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ u16 bss_band = bss_desc->bss_band;
+
+ if (bss_desc->disable_11n)
+ return false;
+
+ if (bss_band & BAND_G)
+ return (priv->config_bands & BAND_GAX);
+ else if (bss_band & BAND_A)
+ return (priv->config_bands & BAND_AAX);
+
+ return false;
+}
+
+int nxpwifi_fill_he_cap_tlv(struct nxpwifi_private *priv,
+ struct nxpwifi_ie_types_he_cap *he_cap,
+ u16 bands)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct nxpwifi_ie_types_he_cap *hw_he_cap = NULL;
+ u16 rx_nss, tx_nss;
+ u8 nss;
+ u16 cfg_value;
+ u16 hw_value;
+ int ret_len;
+
+ if (bands & BAND_A) {
+ memcpy(he_cap, priv->user_he_cap, priv->user_he_cap_len);
+ hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_he_cap;
+ ret_len = priv->user_he_cap_len;
+ } else {
+ memcpy(he_cap, priv->user_2g_he_cap, priv->user_2g_he_cap_len);
+ hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_2g_he_cap;
+ ret_len = priv->user_2g_he_cap_len;
+ }
+
+ if (bands & BAND_A) {
+ rx_nss = GET_RXMCSSUPP(adapter->user_htstream >> 8);
+ tx_nss = GET_TXMCSSUPP(adapter->user_htstream >> 8) & 0x0f;
+ } else {
+ rx_nss = GET_RXMCSSUPP(adapter->user_htstream);
+ tx_nss = GET_TXMCSSUPP(adapter->user_htstream) & 0x0f;
+ }
+
+ for (nss = 1; nss <= 8; nss++) {
+ cfg_value = nxpwifi_get_he_nss_mcs(he_cap->rx_mcs_80, nss);
+ hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->rx_mcs_80, nss);
+ if (rx_nss != 0 && nss > rx_nss)
+ cfg_value = NO_NSS_SUPPORT;
+ if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT)
+ nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss,
+ NO_NSS_SUPPORT);
+ else
+ nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss,
+ min(cfg_value, hw_value));
+ }
+
+ for (nss = 1; nss <= 8; nss++) {
+ cfg_value = nxpwifi_get_he_nss_mcs(he_cap->tx_mcs_80, nss);
+ hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->tx_mcs_80, nss);
+ if (tx_nss != 0 && nss > tx_nss)
+ cfg_value = NO_NSS_SUPPORT;
+ if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT)
+ nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss,
+ NO_NSS_SUPPORT);
+ else
+ nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss,
+ min(cfg_value, hw_value));
+ }
+
+ return ret_len;
+}
+
+int nxpwifi_cmd_append_11ax_tlv(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc,
+ u8 **buffer)
+{
+ struct nxpwifi_ie_types_he_cap *he_cap = NULL;
+ int ret_len;
+
+ if (!bss_desc->bcn_he_cap)
+ return -EOPNOTSUPP;
+
+ he_cap = (struct nxpwifi_ie_types_he_cap *)*buffer;
+ ret_len = nxpwifi_fill_he_cap_tlv(priv, he_cap, bss_desc->bss_band);
+ *buffer += ret_len;
+
+ return ret_len;
+}
+
+int nxpwifi_cmd_11ax_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_11ax_he_cfg *ax_cfg)
+{
+ struct host_cmd_11ax_cfg *he_cfg = &cmd->params.ax_cfg;
+ u16 cmd_size;
+ struct nxpwifi_ie_types_header *header;
+
+ cmd->command = cpu_to_le16(HOST_CMD_11AX_CFG);
+ cmd_size = sizeof(struct host_cmd_11ax_cfg) + S_DS_GEN;
+
+ he_cfg->action = cpu_to_le16(cmd_action);
+ he_cfg->band_config = ax_cfg->band;
+
+ if (ax_cfg->he_cap_cfg.len &&
+ ax_cfg->he_cap_cfg.ext_id == WLAN_EID_EXT_HE_CAPABILITY) {
+ header = (struct nxpwifi_ie_types_header *)he_cfg->tlv;
+ header->type = cpu_to_le16(ax_cfg->he_cap_cfg.id);
+ header->len = cpu_to_le16(ax_cfg->he_cap_cfg.len);
+ memcpy(he_cfg->tlv + sizeof(*header),
+ &ax_cfg->he_cap_cfg.ext_id,
+ ax_cfg->he_cap_cfg.len);
+ cmd_size += (sizeof(*header) + ax_cfg->he_cap_cfg.len);
+ }
+
+ cmd->size = cpu_to_le16(cmd_size);
+
+ return 0;
+}
+
+int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp,
+ struct nxpwifi_11ax_he_cfg *ax_cfg)
+{
+ struct host_cmd_11ax_cfg *he_cfg = &resp->params.ax_cfg;
+ struct nxpwifi_ie_types_header *header;
+ u16 left_len, tlv_type, tlv_len;
+ u8 ext_id;
+ struct nxpwifi_11ax_he_cap_cfg *he_cap = &ax_cfg->he_cap_cfg;
+
+ left_len = le16_to_cpu(resp->size) - sizeof(*he_cfg) - S_DS_GEN;
+ header = (struct nxpwifi_ie_types_header *)he_cfg->tlv;
+
+ while (left_len > sizeof(*header)) {
+ tlv_type = le16_to_cpu(header->type);
+ tlv_len = le16_to_cpu(header->len);
+
+ if (tlv_type == TLV_TYPE_EXTENSION_ID) {
+ ext_id = *((u8 *)header + sizeof(*header) + 1);
+ if (ext_id == WLAN_EID_EXT_HE_CAPABILITY) {
+ he_cap->id = tlv_type;
+ he_cap->len = tlv_len;
+ memcpy((u8 *)&he_cap->ext_id,
+ (u8 *)header + sizeof(*header) + 1,
+ tlv_len);
+ if (he_cfg->band_config & BIT(1)) {
+ memcpy(priv->user_he_cap,
+ (u8 *)header,
+ sizeof(*header) + tlv_len);
+ priv->user_he_cap_len =
+ sizeof(*header) + tlv_len;
+ } else {
+ memcpy(priv->user_2g_he_cap,
+ (u8 *)header,
+ sizeof(*header) + tlv_len);
+ priv->user_2g_he_cap_len =
+ sizeof(*header) + tlv_len;
+ }
+ }
+ }
+
+ left_len -= (sizeof(*header) + tlv_len);
+ header = (struct nxpwifi_ie_types_header *)((u8 *)header +
+ sizeof(*header) +
+ tlv_len);
+ }
+
+ return 0;
+}
+
+int nxpwifi_cmd_11ax_cmd(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_11ax_cmd_cfg *ax_cmd)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct host_cmd_11ax_cmd *he_cmd = &cmd->params.ax_cmd;
+ u16 cmd_size;
+ struct nxpwifi_11ax_sr_cmd *sr_cmd;
+ struct nxpwifi_ie_types_data *tlv;
+ struct nxpwifi_11ax_beam_cmd *beam_cmd;
+ struct nxpwifi_11ax_htc_cmd *htc_cmd;
+ struct nxpwifi_11ax_txomi_cmd *txmoi_cmd;
+ struct nxpwifi_11ax_toltime_cmd *toltime_cmd;
+ struct nxpwifi_11ax_txop_cmd *txop_cmd;
+ struct nxpwifi_11ax_set_bsrp_cmd *set_bsrp_cmd;
+ struct nxpwifi_11ax_llde_cmd *llde_cmd;
+
+ cmd->command = cpu_to_le16(HOST_CMD_11AX_CMD);
+ cmd_size = sizeof(struct host_cmd_11ax_cmd) + S_DS_GEN;
+
+ he_cmd->action = cpu_to_le16(cmd_action);
+ he_cmd->sub_id = cpu_to_le16(ax_cmd->sub_id);
+
+ switch (ax_cmd->sub_command) {
+ case NXPWIFI_11AXCMD_SR_SUBID:
+ sr_cmd = (struct nxpwifi_11ax_sr_cmd *)&ax_cmd->param;
+
+ tlv = (struct nxpwifi_ie_types_data *)he_cmd->val;
+ tlv->header.type = cpu_to_le16(sr_cmd->type);
+ tlv->header.len = cpu_to_le16(sr_cmd->len);
+ memcpy(tlv->data, sr_cmd->param.obss_pd_offset.offset,
+ sr_cmd->len);
+ cmd_size += (sizeof(tlv->header) + sr_cmd->len);
+ break;
+ case NXPWIFI_11AXCMD_BEAM_SUBID:
+ beam_cmd = (struct nxpwifi_11ax_beam_cmd *)&ax_cmd->param;
+
+ he_cmd->val[0] = beam_cmd->value;
+ cmd_size += sizeof(*beam_cmd);
+ break;
+ case NXPWIFI_11AXCMD_HTC_SUBID:
+ htc_cmd = (struct nxpwifi_11ax_htc_cmd *)&ax_cmd->param;
+
+ he_cmd->val[0] = htc_cmd->value;
+ cmd_size += sizeof(*htc_cmd);
+ break;
+ case NXPWIFI_11AXCMD_TXOMI_SUBID:
+ txmoi_cmd = (struct nxpwifi_11ax_txomi_cmd *)&ax_cmd->param;
+
+ memcpy((void *)he_cmd->val, txmoi_cmd, sizeof(*txmoi_cmd));
+ cmd_size += sizeof(*txmoi_cmd);
+ break;
+ case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID:
+ toltime_cmd = (struct nxpwifi_11ax_toltime_cmd *)&ax_cmd->param;
+
+ memcpy(he_cmd->val, &toltime_cmd->tol_time,
+ sizeof(toltime_cmd->tol_time));
+ cmd_size += sizeof(*toltime_cmd);
+ break;
+ case NXPWIFI_11AXCMD_TXOPRTS_SUBID:
+ txop_cmd = (struct nxpwifi_11ax_txop_cmd *)&ax_cmd->param;
+
+ memcpy(he_cmd->val, &txop_cmd->rts_thres,
+ sizeof(txop_cmd->rts_thres));
+ cmd_size += sizeof(*txop_cmd);
+ break;
+ case NXPWIFI_11AXCMD_SET_BSRP_SUBID:
+ set_bsrp_cmd = (struct nxpwifi_11ax_set_bsrp_cmd *)&ax_cmd->param;
+
+ he_cmd->val[0] = set_bsrp_cmd->value;
+ cmd_size += sizeof(*set_bsrp_cmd);
+ break;
+ case NXPWIFI_11AXCMD_LLDE_SUBID:
+ llde_cmd = (struct nxpwifi_11ax_llde_cmd *)&ax_cmd->param;
+
+ memcpy((void *)he_cmd->val, llde_cmd, sizeof(*llde_cmd));
+ cmd_size += sizeof(*llde_cmd);
+ break;
+ default:
+ nxpwifi_dbg(adapter, ERROR,
+ "%s: Unknown sub command: %d\n",
+ __func__, ax_cmd->sub_command);
+ return -EINVAL;
+ }
+
+ cmd->size = cpu_to_le16(cmd_size);
+
+ return 0;
+}
+
+int nxpwifi_ret_11ax_cmd(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp,
+ struct nxpwifi_11ax_cmd_cfg *ax_cmd)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct host_cmd_11ax_cmd *he_cmd = &resp->params.ax_cmd;
+ struct nxpwifi_ie_types_data *tlv;
+
+ ax_cmd->sub_id = le16_to_cpu(he_cmd->sub_id);
+
+ switch (ax_cmd->sub_command) {
+ case NXPWIFI_11AXCMD_SR_SUBID:
+ tlv = (struct nxpwifi_ie_types_data *)he_cmd->val;
+ memcpy(ax_cmd->param.sr_cfg.param.obss_pd_offset.offset,
+ tlv->data,
+ ax_cmd->param.sr_cfg.len);
+ break;
+ case NXPWIFI_11AXCMD_BEAM_SUBID:
+ ax_cmd->param.beam_cfg.value = *he_cmd->val;
+ break;
+ case NXPWIFI_11AXCMD_HTC_SUBID:
+ ax_cmd->param.htc_cfg.value = *he_cmd->val;
+ break;
+ case NXPWIFI_11AXCMD_TXOMI_SUBID:
+ memcpy(&ax_cmd->param.txomi_cfg,
+ he_cmd->val, sizeof(ax_cmd->param.txomi_cfg));
+ break;
+ case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID:
+ memcpy(&ax_cmd->param.toltime_cfg.tol_time,
+ he_cmd->val, sizeof(ax_cmd->param.toltime_cfg));
+ break;
+ case NXPWIFI_11AXCMD_TXOPRTS_SUBID:
+ memcpy(&ax_cmd->param.txop_cfg.rts_thres,
+ he_cmd->val, sizeof(ax_cmd->param.txop_cfg));
+ break;
+ case NXPWIFI_11AXCMD_SET_BSRP_SUBID:
+ ax_cmd->param.setbsrp_cfg.value = *he_cmd->val;
+ break;
+ case NXPWIFI_11AXCMD_LLDE_SUBID:
+ memcpy(&ax_cmd->param.llde_cfg,
+ he_cmd->val, sizeof(ax_cmd->param.llde_cfg));
+ break;
+ default:
+ nxpwifi_dbg(adapter, ERROR,
+ "%s: Unknown sub command: %d\n",
+ __func__, ax_cmd->sub_command);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static u8 nxpwifi_is_ap_11ax_twt_supported(struct nxpwifi_bssdescriptor *bss_desc)
+{
+ struct element *ext_cap;
+
+ if (!bss_desc->bcn_he_cap)
+ return false;
+ if (!(bss_desc->bcn_he_cap->mac_cap_info[0] & HE_MAC_CAP_TWT_RESP_SUPPORT))
+ return false;
+ if (!bss_desc->bcn_ext_cap)
+ return false;
+ ext_cap = (struct element *)bss_desc->bcn_ext_cap;
+
+ if (!(ext_cap->data[9] & WLAN_EXT_CAPA10_TWT_RESPONDER_SUPPORT))
+ return false;
+ return true;
+}
+
+bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc)
+{
+ struct nxpwifi_ie_types_he_cap *user_he_cap;
+ struct nxpwifi_ie_types_he_cap *hw_he_cap;
+
+ if (bss_desc && (!nxpwifi_is_ap_11ax_twt_supported(bss_desc))) {
+ nxpwifi_dbg(priv->adapter, MSG,
+ "AP don't support twt feature\n");
+ return false;
+ }
+
+ if (bss_desc->bss_band & BAND_A) {
+ hw_he_cap = (struct nxpwifi_ie_types_he_cap *)
+ priv->adapter->hw_he_cap;
+ user_he_cap = (struct nxpwifi_ie_types_he_cap *)
+ priv->user_he_cap;
+ } else {
+ hw_he_cap = (struct nxpwifi_ie_types_he_cap *)
+ priv->adapter->hw_2g_he_cap;
+ user_he_cap = (struct nxpwifi_ie_types_he_cap *)
+ priv->user_2g_he_cap;
+ }
+
+ if (!(hw_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) {
+ nxpwifi_dbg(priv->adapter, MSG,
+ "FW don't support TWT\n");
+ return false;
+ }
+
+ if (!(user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) {
+ nxpwifi_dbg(priv->adapter, MSG,
+ "USER HE_MAC_CAP don't support TWT\n");
+ return false;
+ }
+
+ return true;
+}
+
+u8 nxpwifi_is_sta_11ax_twt_req_supported(struct nxpwifi_private *priv)
+{
+ struct nxpwifi_ie_types_he_cap *user_he_cap;
+ u8 ret = 0;
+
+ if (ISSUPP_11AXENABLED(priv->adapter->fw_cap_ext) &&
+ (priv->config_bands & BAND_GAX || priv->config_bands & BAND_AAX)) {
+ if (priv->config_bands & BAND_AAX)
+ user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_he_cap;
+ else
+ user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_2g_he_cap;
+ ret = user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT;
+ }
+
+ return ret;
+}
+
+int nxpwifi_cmd_twt_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_twt_cfg *twt_cfg)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct host_cmd_twt_cfg *twt_cfg_cmd = &cmd->params.twt_cfg;
+ struct nxpwifi_twt_setup *twt_setup;
+ struct nxpwifi_twt_teardown *twt_teardown;
+ struct nxpwifi_twt_report *twt_report;
+ struct nxpwifi_twt_information *twt_information;
+ struct nxpwifi_btwt_ap_config *btwt_ap_config;
+ u8 i;
+ u16 cmd_size;
+
+ cmd->command = cpu_to_le16(HOST_CMD_TWT_CFG);
+ cmd_size = sizeof(struct host_cmd_twt_cfg) + S_DS_GEN;
+
+ twt_cfg_cmd->action = cpu_to_le16(cmd_action);
+ twt_cfg_cmd->sub_id = cpu_to_le16(twt_cfg->sub_id);
+
+ switch (twt_cfg->sub_id) {
+ case NXPWIFI_11AX_TWT_SETUP_SUBID:
+ twt_setup = (struct nxpwifi_twt_setup *)
+ twt_cfg_cmd->val;
+
+ memset(twt_setup, 0x00, sizeof(struct nxpwifi_twt_setup));
+ twt_setup->implicit = twt_cfg->param.twt_setup.implicit;
+ twt_setup->announced = twt_cfg->param.twt_setup.announced;
+ twt_setup->trigger_enabled = twt_cfg->param.twt_setup.trigger_enabled;
+ twt_setup->twt_info_disabled = twt_cfg->param.twt_setup.twt_info_disabled;
+ twt_setup->negotiation_type = twt_cfg->param.twt_setup.negotiation_type;
+ twt_setup->twt_wakeup_duration =
+ twt_cfg->param.twt_setup.twt_wakeup_duration;
+ twt_setup->flow_identifier = twt_cfg->param.twt_setup.flow_identifier;
+ twt_setup->hard_constraint = twt_cfg->param.twt_setup.hard_constraint;
+ twt_setup->twt_exponent = twt_cfg->param.twt_setup.twt_exponent;
+ twt_setup->twt_mantissa = twt_cfg->param.twt_setup.twt_mantissa;
+ twt_setup->twt_request = twt_cfg->param.twt_setup.twt_request;
+ twt_setup->bcn_miss_threshold = twt_cfg->param.twt_setup.bcn_miss_threshold;
+ cmd_size += sizeof(struct nxpwifi_twt_setup);
+ break;
+ case NXPWIFI_11AX_TWT_TEARDOWN_SUBID:
+ twt_teardown = (struct nxpwifi_twt_teardown *)
+ twt_cfg_cmd->val;
+ memset(twt_teardown, 0x00,
+ sizeof(struct nxpwifi_twt_teardown));
+ twt_teardown->flow_identifier =
+ twt_cfg->param.twt_teardown.flow_identifier;
+ twt_teardown->negotiation_type =
+ twt_cfg->param.twt_teardown.negotiation_type;
+ twt_teardown->teardown_all_twt =
+ twt_cfg->param.twt_teardown.teardown_all_twt;
+ cmd_size += sizeof(struct nxpwifi_twt_teardown);
+ break;
+ case NXPWIFI_11AX_TWT_REPORT_SUBID:
+ twt_report = (struct nxpwifi_twt_report *)
+ twt_cfg_cmd->val;
+ memset(twt_report, 0x00, sizeof(struct nxpwifi_twt_report));
+ twt_report->type = twt_cfg->param.twt_report.type;
+ cmd_size += sizeof(struct nxpwifi_twt_report);
+ break;
+ case NXPWIFI_11AX_TWT_INFORMATION_SUBID:
+ twt_information = (struct nxpwifi_twt_information *)
+ twt_cfg_cmd->val;
+ memset(twt_information, 0x00,
+ sizeof(struct nxpwifi_twt_information));
+ twt_information->flow_identifier =
+ twt_cfg->param.twt_information.flow_identifier;
+ twt_information->suspend_duration =
+ twt_cfg->param.twt_information.suspend_duration;
+ cmd_size += sizeof(struct nxpwifi_twt_information);
+ break;
+ case NXPWIFI_11AX_BTWT_AP_CONFIG_SUBID:
+ btwt_ap_config = (struct nxpwifi_btwt_ap_config *)
+ twt_cfg_cmd->val;
+ memset(btwt_ap_config, 0x00,
+ sizeof(struct nxpwifi_btwt_ap_config));
+ btwt_ap_config->ap_bcast_bet_sta_wait =
+ twt_cfg->param.btwt_ap_config.ap_bcast_bet_sta_wait;
+ btwt_ap_config->ap_bcast_offset =
+ twt_cfg->param.btwt_ap_config.ap_bcast_offset;
+ btwt_ap_config->bcast_twtli =
+ twt_cfg->param.btwt_ap_config.bcast_twtli;
+ btwt_ap_config->count =
+ twt_cfg->param.btwt_ap_config.count;
+ for (i = 0; i < BTWT_AGREEMENT_MAX; i++) {
+ btwt_ap_config->btwt_sets[i].btwt_id =
+ twt_cfg->param.btwt_ap_config.btwt_sets[i].btwt_id;
+ btwt_ap_config->btwt_sets[i].ap_bcast_mantissa =
+ twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_mantissa;
+ btwt_ap_config->btwt_sets[i].ap_bcast_exponent =
+ twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_exponent;
+ btwt_ap_config->btwt_sets[i].nominalwake =
+ twt_cfg->param.btwt_ap_config.btwt_sets[i].nominalwake;
+ }
+
+ cmd_size += sizeof(struct nxpwifi_btwt_ap_config);
+ break;
+ default:
+ nxpwifi_dbg(adapter, ERROR,
+ "Unknown sub id: %d\n", twt_cfg->sub_id);
+ return -EINVAL;
+ }
+
+ cmd->size = cpu_to_le16(cmd_size);
+
+ return 0;
+}
+
+int nxpwifi_ret_twt_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp,
+ struct nxpwifi_twt_cfg *twt_cfg)
+{
+ struct host_cmd_twt_cfg *twt_cfg_cmd = &resp->params.twt_cfg;
+ u16 action;
+
+ action = le16_to_cpu(twt_cfg_cmd->action);
+ twt_cfg->sub_id = le16_to_cpu(twt_cfg_cmd->sub_id);
+
+ if (action == HOST_ACT_GEN_GET &&
+ twt_cfg->sub_id == NXPWIFI_11AX_TWT_REPORT_SUBID) {
+ struct nxpwifi_twt_report *twt_report =
+ (struct nxpwifi_twt_report *)twt_cfg_cmd->val;
+
+ memcpy(&twt_cfg->param.twt_report, twt_report, sizeof(struct nxpwifi_twt_report));
+ }
+
+ return 0;
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.h b/drivers/net/wireless/nxp/nxpwifi/11ax.h
new file mode 100644
index 000000000000..2eda69f19763
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11ax.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * nxpwifi: 802.11ax support
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#ifndef _NXPWIFI_11AX_H_
+#define _NXPWIFI_11AX_H_
+
+/* device support 2.4G 40MHZ */
+#define AX_2G_40MHZ_SUPPORT BIT(1)
+/* device support 2.4G 242 tone RUs */
+#define AX_2G_20MHZ_SUPPORT BIT(5)
+
+/* Get HE MCS map code for n spatial streams (0..3). */
+static inline u16
+nxpwifi_get_he_nss_mcs(__le16 mcs_map_set, int nss) {
+ return ((le16_to_cpu(mcs_map_set) >> (2 * (nss - 1))) & 0x3);
+}
+
+static inline void
+nxpwifi_set_he_nss_mcs(__le16 *mcs_map_set, int nss, int value) {
+ u16 temp;
+
+ temp = le16_to_cpu(*mcs_map_set);
+ temp |= ((value & 0x3) << (2 * (nss - 1)));
+ *mcs_map_set = cpu_to_le16(temp);
+}
+
+bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc);
+
+void nxpwifi_update_11ax_cap(struct nxpwifi_adapter *adapter,
+ struct hw_spec_extension *hw_he_cap);
+
+bool nxpwifi_11ax_bandconfig_allowed(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc);
+
+int nxpwifi_cmd_append_11ax_tlv(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc,
+ u8 **buffer);
+
+int nxpwifi_fill_he_cap_tlv(struct nxpwifi_private *priv,
+ struct nxpwifi_ie_types_he_cap *he_cap,
+ u16 bands);
+int nxpwifi_cmd_11ax_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_11ax_he_cfg *ax_cfg);
+
+int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp,
+ struct nxpwifi_11ax_he_cfg *ax_cfg);
+
+int nxpwifi_cmd_11ax_cmd(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_11ax_cmd_cfg *ax_cmd);
+
+int nxpwifi_ret_11ax_cmd(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp,
+ struct nxpwifi_11ax_cmd_cfg *ax_cmd);
+
+int nxpwifi_cmd_twt_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_twt_cfg *twt_cfg);
+
+int nxpwifi_ret_twt_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp,
+ struct nxpwifi_twt_cfg *twt_cfg);
+
+u8 nxpwifi_is_sta_11ax_twt_req_supported(struct nxpwifi_private *priv);
+
+#endif /* _NXPWIFI_11AX_H_ */
--
2.34.1
^ permalink raw reply related
* [PATCH v10 02/21] wifi: nxpwifi: add initial 802.11ac support for STA and AP modes
From: Jeff Chen @ 2026-03-05 14:39 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, johannes, francesco, wyatt.hsu, s.hauer, Jeff Chen
In-Reply-To: <20260305143939.3724868-1-jeff.chen_1@nxp.com>
Introduce initial 802.11ac functionality, enabling VHT capability
negotiation and configuration for both STA and AP roles.
For station mode, convert VHT elements from cfg80211 into firmware TLVs
and append them to HOST_CMD_802_11_ASSOCIATE. For AP mode, convert
VHT elements into parameters for HOST_CMD_11AC_CFG and pass them to the
firmware for configuration.
Handle VHT capabilities, VHT operation, and operating mode notification
elements to allow proper setup of 802.11ac features.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/11ac.c | 280 ++++++++++++++++++++++++
drivers/net/wireless/nxp/nxpwifi/11ac.h | 33 +++
2 files changed, 313 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.h
diff --git a/drivers/net/wireless/nxp/nxpwifi/11ac.c b/drivers/net/wireless/nxp/nxpwifi/11ac.c
new file mode 100644
index 000000000000..117d06c35401
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11ac.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nxpwifi 802.11ac helpers
+ * Copyright 2011-2024 NXP
+ */
+
+#include "cfg.h"
+#include "fw.h"
+#include "main.h"
+#include "11ac.h"
+
+/* Map VHT MCS/NSS to highest data rate (Mbps), long GI. */
+static const u16 max_rate_lgi_80MHZ[8][3] = {
+ {0x124, 0x15F, 0x186}, /* NSS = 1 */
+ {0x249, 0x2BE, 0x30C}, /* NSS = 2 */
+ {0x36D, 0x41D, 0x492}, /* NSS = 3 */
+ {0x492, 0x57C, 0x618}, /* NSS = 4 */
+ {0x5B6, 0x6DB, 0x79E}, /* NSS = 5 */
+ {0x6DB, 0x83A, 0x0}, /* NSS = 6 */
+ {0x7FF, 0x999, 0xAAA}, /* NSS = 7 */
+ {0x924, 0xAF8, 0xC30} /* NSS = 8 */
+};
+
+static const u16 max_rate_lgi_160MHZ[8][3] = {
+ {0x249, 0x2BE, 0x30C}, /* NSS = 1 */
+ {0x492, 0x57C, 0x618}, /* NSS = 2 */
+ {0x6DB, 0x83A, 0x0}, /* NSS = 3 */
+ {0x924, 0xAF8, 0xC30}, /* NSS = 4 */
+ {0xB6D, 0xDB6, 0xF3C}, /* NSS = 5 */
+ {0xDB6, 0x1074, 0x1248}, /* NSS = 6 */
+ {0xFFF, 0x1332, 0x1554}, /* NSS = 7 */
+ {0x1248, 0x15F0, 0x1860} /* NSS = 8 */
+};
+
+/* Convert 2-bit MCS map to highest long-GI VHT data rate. */
+static u16
+nxpwifi_convert_mcsmap_to_maxrate(struct nxpwifi_private *priv,
+ u16 bands, u16 mcs_map)
+{
+ u8 i, nss, mcs;
+ u16 max_rate = 0;
+ u32 usr_vht_cap_info = 0;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+
+ if (bands & BAND_AAC)
+ usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a;
+ else
+ usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg;
+
+ /* Find max supported NSS. */
+ nss = 1;
+ for (i = 1; i <= 8; i++) {
+ mcs = GET_VHTNSSMCS(mcs_map, i);
+ if (mcs < IEEE80211_VHT_MCS_NOT_SUPPORTED)
+ nss = i;
+ }
+ mcs = GET_VHTNSSMCS(mcs_map, nss);
+
+ /* If not supported, fall back to 0-9. */
+ if (mcs == IEEE80211_VHT_MCS_NOT_SUPPORTED)
+ mcs = IEEE80211_VHT_MCS_SUPPORT_0_9;
+
+ if (u32_get_bits(usr_vht_cap_info, IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK)) {
+ /* Support 160 MHz. */
+ max_rate = max_rate_lgi_160MHZ[nss - 1][mcs];
+ if (!max_rate)
+ /* MCS9 not supported in NSS6. */
+ max_rate = max_rate_lgi_160MHZ[nss - 1][mcs - 1];
+ } else {
+ max_rate = max_rate_lgi_80MHZ[nss - 1][mcs];
+ if (!max_rate)
+ /* MCS9 not supported in NSS3. */
+ max_rate = max_rate_lgi_80MHZ[nss - 1][mcs - 1];
+ }
+
+ return max_rate;
+}
+
+static void
+nxpwifi_fill_vht_cap_info(struct nxpwifi_private *priv,
+ struct ieee80211_vht_cap *vht_cap, u16 bands)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+
+ if (bands & BAND_A)
+ vht_cap->vht_cap_info =
+ cpu_to_le32(adapter->usr_dot_11ac_dev_cap_a);
+ else
+ vht_cap->vht_cap_info =
+ cpu_to_le32(adapter->usr_dot_11ac_dev_cap_bg);
+}
+
+void
+nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv,
+ struct ieee80211_vht_cap *vht_cap, u16 bands)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ u16 mcs_map_user, mcs_map_resp, mcs_map_result;
+ u16 mcs_user, mcs_resp, nss, tmp;
+
+ /* Fill VHT capability info. */
+ nxpwifi_fill_vht_cap_info(priv, vht_cap, bands);
+
+ /* RX MCS set: min(user, AP). */
+ mcs_map_user = GET_DEVRXMCSMAP(adapter->usr_dot_11ac_mcs_support);
+ mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.rx_mcs_map);
+ mcs_map_result = 0;
+
+ for (nss = 1; nss <= 8; nss++) {
+ mcs_user = GET_VHTNSSMCS(mcs_map_user, nss);
+ mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss);
+
+ if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED ||
+ mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED)
+ SET_VHTNSSMCS(mcs_map_result, nss,
+ IEEE80211_VHT_MCS_NOT_SUPPORTED);
+ else
+ SET_VHTNSSMCS(mcs_map_result, nss,
+ min(mcs_user, mcs_resp));
+ }
+
+ vht_cap->supp_mcs.rx_mcs_map = cpu_to_le16(mcs_map_result);
+
+ tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result);
+ vht_cap->supp_mcs.rx_highest = cpu_to_le16(tmp);
+
+ /* TX MCS set: min(user, AP). */
+ mcs_map_user = GET_DEVTXMCSMAP(adapter->usr_dot_11ac_mcs_support);
+ mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map);
+ mcs_map_result = 0;
+
+ for (nss = 1; nss <= 8; nss++) {
+ mcs_user = GET_VHTNSSMCS(mcs_map_user, nss);
+ mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss);
+ if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED ||
+ mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED)
+ SET_VHTNSSMCS(mcs_map_result, nss,
+ IEEE80211_VHT_MCS_NOT_SUPPORTED);
+ else
+ SET_VHTNSSMCS(mcs_map_result, nss,
+ min(mcs_user, mcs_resp));
+ }
+
+ vht_cap->supp_mcs.tx_mcs_map = cpu_to_le16(mcs_map_result);
+
+ tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result);
+ vht_cap->supp_mcs.tx_highest = cpu_to_le16(tmp);
+}
+
+int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc,
+ u8 **buffer)
+{
+ struct nxpwifi_ie_types_vhtcap *vht_cap;
+ struct nxpwifi_ie_types_oper_mode_ntf *oper_ntf;
+ struct ieee_types_oper_mode_ntf *ieee_oper_ntf;
+ struct nxpwifi_ie_types_vht_oper *vht_op;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ u8 supp_chwd_set;
+ u32 usr_vht_cap_info;
+ int ret_len = 0;
+
+ if (bss_desc->bss_band & BAND_A)
+ usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a;
+ else
+ usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg;
+
+ /* VHT Capabilities element. */
+ if (bss_desc->bcn_vht_cap) {
+ vht_cap = (struct nxpwifi_ie_types_vhtcap *)*buffer;
+ memset(vht_cap, 0, sizeof(*vht_cap));
+ vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY);
+ vht_cap->header.len =
+ cpu_to_le16(sizeof(struct ieee80211_vht_cap));
+ memcpy((u8 *)vht_cap + sizeof(struct nxpwifi_ie_types_header),
+ (u8 *)bss_desc->bcn_vht_cap,
+ le16_to_cpu(vht_cap->header.len));
+
+ nxpwifi_fill_vht_cap_tlv(priv, &vht_cap->vht_cap,
+ bss_desc->bss_band);
+ *buffer += sizeof(*vht_cap);
+ ret_len += sizeof(*vht_cap);
+ }
+
+ /* VHT Operation element. */
+ if (bss_desc->bcn_vht_oper) {
+ if (priv->bss_mode == NL80211_IFTYPE_STATION) {
+ vht_op = (struct nxpwifi_ie_types_vht_oper *)*buffer;
+ memset(vht_op, 0, sizeof(*vht_op));
+ vht_op->header.type =
+ cpu_to_le16(WLAN_EID_VHT_OPERATION);
+ vht_op->header.len = cpu_to_le16(sizeof(*vht_op) -
+ sizeof(struct nxpwifi_ie_types_header));
+ memcpy((u8 *)vht_op +
+ sizeof(struct nxpwifi_ie_types_header),
+ (u8 *)bss_desc->bcn_vht_oper,
+ le16_to_cpu(vht_op->header.len));
+
+ /* Negotiate channel width; keep peer's center freq. */
+ supp_chwd_set = u32_get_bits(usr_vht_cap_info,
+ IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK);
+
+ switch (supp_chwd_set) {
+ case 0:
+ vht_op->chan_width =
+ min_t(u8, IEEE80211_VHT_CHANWIDTH_80MHZ,
+ bss_desc->bcn_vht_oper->chan_width);
+ break;
+ case 1:
+ vht_op->chan_width =
+ min_t(u8, IEEE80211_VHT_CHANWIDTH_160MHZ,
+ bss_desc->bcn_vht_oper->chan_width);
+ break;
+ case 2:
+ vht_op->chan_width =
+ min_t(u8, IEEE80211_VHT_CHANWIDTH_80P80MHZ,
+ bss_desc->bcn_vht_oper->chan_width);
+ break;
+ default:
+ vht_op->chan_width =
+ IEEE80211_VHT_CHANWIDTH_USE_HT;
+ break;
+ }
+
+ *buffer += sizeof(*vht_op);
+ ret_len += sizeof(*vht_op);
+ }
+ }
+
+ /* Operating Mode Notification element. */
+ if (bss_desc->oper_mode) {
+ ieee_oper_ntf = bss_desc->oper_mode;
+ oper_ntf = (void *)*buffer;
+ memset(oper_ntf, 0, sizeof(*oper_ntf));
+ oper_ntf->header.type = cpu_to_le16(WLAN_EID_OPMODE_NOTIF);
+ oper_ntf->header.len = cpu_to_le16(sizeof(u8));
+ oper_ntf->oper_mode = ieee_oper_ntf->oper_mode;
+ *buffer += sizeof(*oper_ntf);
+ ret_len += sizeof(*oper_ntf);
+ }
+
+ return ret_len;
+}
+
+int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_11ac_vht_cfg *cfg)
+{
+ struct host_cmd_11ac_vht_cfg *vhtcfg = &cmd->params.vht_cfg;
+
+ cmd->command = cpu_to_le16(HOST_CMD_11AC_CFG);
+ cmd->size = cpu_to_le16(sizeof(struct host_cmd_11ac_vht_cfg) +
+ S_DS_GEN);
+ vhtcfg->action = cpu_to_le16(cmd_action);
+ vhtcfg->band_config = cfg->band_config;
+ vhtcfg->misc_config = cfg->misc_config;
+ vhtcfg->cap_info = cpu_to_le32(cfg->cap_info);
+ vhtcfg->mcs_tx_set = cpu_to_le32(cfg->mcs_tx_set);
+ vhtcfg->mcs_rx_set = cpu_to_le32(cfg->mcs_rx_set);
+
+ return 0;
+}
+
+/* Initialize BlockAck parameters for 11ac. */
+void nxpwifi_set_11ac_ba_params(struct nxpwifi_private *priv)
+{
+ priv->add_ba_param.timeout = NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT;
+
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) {
+ priv->add_ba_param.tx_win_size =
+ NXPWIFI_11AC_UAP_AMPDU_DEF_TXWINSIZE;
+ priv->add_ba_param.rx_win_size =
+ NXPWIFI_11AC_UAP_AMPDU_DEF_RXWINSIZE;
+ } else {
+ priv->add_ba_param.tx_win_size =
+ NXPWIFI_11AC_STA_AMPDU_DEF_TXWINSIZE;
+ priv->add_ba_param.rx_win_size =
+ NXPWIFI_11AC_STA_AMPDU_DEF_RXWINSIZE;
+ }
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/11ac.h b/drivers/net/wireless/nxp/nxpwifi/11ac.h
new file mode 100644
index 000000000000..edc01b35d5b8
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11ac.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * nxpwifi: 802.11ac (VHT) definitions
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#ifndef _NXPWIFI_11AC_H_
+#define _NXPWIFI_11AC_H_
+
+#define VHT_CFG_2GHZ BIT(0)
+#define VHT_CFG_5GHZ BIT(1)
+
+enum vht_cfg_misc_config {
+ VHT_CAP_TX_OPERATION = 1,
+ VHT_CAP_ASSOCIATION,
+ VHT_CAP_UAP_ONLY
+};
+
+#define DEFAULT_VHT_MCS_SET 0xfffe
+#define DISABLE_VHT_MCS_SET 0xffff
+
+#define VHT_BW_80_160_80P80 BIT(2)
+
+int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc,
+ u8 **buffer);
+int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_11ac_vht_cfg *cfg);
+void nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv,
+ struct ieee80211_vht_cap *vht_cap, u16 bands);
+#endif /* _NXPWIFI_11AC_H_ */
--
2.34.1
^ permalink raw reply related
* [PATCH v10 01/21] wifi: nxpwifi: add 802.11n support for STA and AP modes
From: Jeff Chen @ 2026-03-05 14:39 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, johannes, francesco, wyatt.hsu, s.hauer, Jeff Chen
In-Reply-To: <20260305143939.3724868-1-jeff.chen_1@nxp.com>
Add 802.11n functionality to nxpwifi, enabling HT capability negotiation
and configuration for both STA and AP roles.
Convert HT elements from cfg80211 into firmware TLVs and append them to
HOST_CMD_802_11_ASSOCIATE when operating as a station. Generate the
corresponding HT elements for HOST_CMD_UAP_SYS_CONFIG when operating as
an AP.
Implement AMSDU aggregation and blockack reorder buffer handling in the
driver due to firmware resource limitations.
Add the required logic and files to enable 802.11n functionality across
both roles.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/11n.c | 837 ++++++++++++++++++
drivers/net/wireless/nxp/nxpwifi/11n.h | 158 ++++
drivers/net/wireless/nxp/nxpwifi/11n_aggr.c | 251 ++++++
drivers/net/wireless/nxp/nxpwifi/11n_aggr.h | 21 +
.../net/wireless/nxp/nxpwifi/11n_rxreorder.c | 826 +++++++++++++++++
.../net/wireless/nxp/nxpwifi/11n_rxreorder.h | 71 ++
6 files changed, 2164 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h
diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c
new file mode 100644
index 000000000000..e46c5053d509
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11n.c
@@ -0,0 +1,837 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nxpwifi 802.11n helpers
+ * Copyright 2011-2024 NXP
+ */
+
+#include "cfg.h"
+#include "util.h"
+#include "fw.h"
+#include "main.h"
+#include "cmdevt.h"
+#include "wmm.h"
+#include "11n.h"
+#include "11ax.h"
+
+/*
+ * Fills HT capability information field, AMPDU Parameters field, HT extended
+ * capability field, and supported MCS set fields.
+ *
+ * HT capability information field, AMPDU Parameters field, supported MCS set
+ * fields are retrieved from cfg80211 stack
+ *
+ * RD responder bit to set to clear in the extended capability header.
+ */
+int nxpwifi_fill_cap_info(struct nxpwifi_private *priv, u8 radio_type,
+ struct ieee80211_ht_cap *ht_cap)
+{
+ u16 ht_cap_info;
+ u16 bcn_ht_cap = le16_to_cpu(ht_cap->cap_info);
+ u16 ht_ext_cap = le16_to_cpu(ht_cap->extended_ht_cap_info);
+ struct ieee80211_supported_band *sband =
+ priv->wdev.wiphy->bands[radio_type];
+
+ if (WARN_ON_ONCE(!sband)) {
+ nxpwifi_dbg(priv->adapter, ERROR, "Invalid radio type!\n");
+ return -EINVAL;
+ }
+
+ ht_cap->ampdu_params_info =
+ (AMPDU_FACTOR_64K & IEEE80211_HT_AMPDU_PARM_FACTOR) |
+ ((priv->adapter->hw_mpdu_density <<
+ IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT) &
+ IEEE80211_HT_AMPDU_PARM_DENSITY);
+
+ memcpy((u8 *)&ht_cap->mcs, &sband->ht_cap.mcs,
+ sizeof(sband->ht_cap.mcs));
+
+ if (priv->bss_mode == NL80211_IFTYPE_STATION ||
+ (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 &&
+ priv->adapter->sec_chan_offset != IEEE80211_HT_PARAM_CHA_SEC_NONE))
+ /* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */
+ SETHT_MCS32(ht_cap->mcs.rx_mask);
+
+ /* Clear RD responder bit */
+ ht_ext_cap &= ~IEEE80211_HT_EXT_CAP_RD_RESPONDER;
+
+ ht_cap_info = sband->ht_cap.cap;
+ if (bcn_ht_cap) {
+ if (!(bcn_ht_cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40))
+ ht_cap_info &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+ if (!(bcn_ht_cap & IEEE80211_HT_CAP_SGI_40))
+ ht_cap_info &= ~IEEE80211_HT_CAP_SGI_40;
+ if (!(bcn_ht_cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT))
+ ht_cap_info &= ~IEEE80211_HT_CAP_40MHZ_INTOLERANT;
+ }
+ ht_cap->cap_info = cpu_to_le16(ht_cap_info);
+ ht_cap->extended_ht_cap_info = cpu_to_le16(ht_ext_cap);
+
+ if (ISSUPP_BEAMFORMING(priv->adapter->hw_dot_11n_dev_cap))
+ ht_cap->tx_BF_cap_info = cpu_to_le32(NXPWIFI_DEF_11N_TX_BF_CAP);
+
+ return 0;
+}
+
+/* Return BA stream entry that matches the requested status. */
+static struct nxpwifi_tx_ba_stream_tbl *
+nxpwifi_get_ba_status(struct nxpwifi_private *priv, int tid,
+ enum nxpwifi_ba_status ba_status)
+{
+ struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl, *found = NULL;
+
+ guard(rcu)();
+ list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) {
+ if (tx_ba_tsr_tbl->ba_status == ba_status) {
+ found = tx_ba_tsr_tbl;
+ break;
+ }
+ }
+ return found;
+}
+
+/* Handle DELBA command response (recreate or continue ADDBA as needed). */
+int nxpwifi_ret_11n_delba(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp)
+{
+ int tid;
+ struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl;
+ struct host_cmd_ds_11n_delba *del_ba = &resp->params.del_ba;
+ u16 del_ba_param_set = le16_to_cpu(del_ba->del_ba_param_set);
+
+ tid = del_ba_param_set >> DELBA_TID_POS;
+ if (del_ba->del_result == BA_RESULT_SUCCESS) {
+ nxpwifi_del_ba_tbl(priv, tid, del_ba->peer_mac_addr,
+ TYPE_DELBA_SENT,
+ INITIATOR_BIT(del_ba_param_set));
+
+ tx_ba_tbl = nxpwifi_get_ba_status(priv, tid, BA_SETUP_INPROGRESS);
+ if (tx_ba_tbl)
+ nxpwifi_send_addba(priv, tx_ba_tbl->tid,
+ tx_ba_tbl->ra);
+ } else {
+ /*
+ * In case of failure, recreate the deleted stream in case
+ * we initiated the DELBA
+ */
+ if (!INITIATOR_BIT(del_ba_param_set))
+ return 0;
+
+ nxpwifi_create_ba_tbl(priv, del_ba->peer_mac_addr, tid,
+ BA_SETUP_INPROGRESS);
+
+ tx_ba_tbl = nxpwifi_get_ba_status(priv, tid, BA_SETUP_INPROGRESS);
+
+ if (tx_ba_tbl)
+ nxpwifi_del_ba_tbl(priv, tx_ba_tbl->tid, tx_ba_tbl->ra,
+ TYPE_DELBA_SENT, true);
+ }
+
+ return 0;
+}
+
+/* Handle ADDBA response; delete BA stream on failure. */
+int nxpwifi_ret_11n_addba_req(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp)
+{
+ int tid, tid_down;
+ struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp;
+ struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl;
+ struct nxpwifi_ra_list_tbl *ra_list;
+ u16 block_ack_param_set = le16_to_cpu(add_ba_rsp->block_ack_param_set);
+
+ add_ba_rsp->ssn = cpu_to_le16((le16_to_cpu(add_ba_rsp->ssn))
+ & SSN_MASK);
+
+ tid = u16_get_bits(block_ack_param_set, IEEE80211_ADDBA_PARAM_TID_MASK);
+
+ tid_down = nxpwifi_wmm_downgrade_tid(priv, tid);
+ ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down,
+ add_ba_rsp->peer_mac_addr);
+ if (le16_to_cpu(add_ba_rsp->status_code) != BA_RESULT_SUCCESS) {
+ if (ra_list) {
+ ra_list->ba_status = BA_SETUP_NONE;
+ ra_list->amsdu_in_ampdu = false;
+ }
+ nxpwifi_del_ba_tbl(priv, tid, add_ba_rsp->peer_mac_addr,
+ TYPE_DELBA_SENT, true);
+ if (add_ba_rsp->add_rsp_result != BA_RESULT_TIMEOUT)
+ priv->aggr_prio_tbl[tid].ampdu_ap =
+ BA_STREAM_NOT_ALLOWED;
+ return 0;
+ }
+
+ guard(rcu)();
+ tx_ba_tbl = nxpwifi_get_ba_tbl(priv, tid, add_ba_rsp->peer_mac_addr);
+ if (tx_ba_tbl) {
+ nxpwifi_dbg(priv->adapter, EVENT, "info: BA stream complete\n");
+ tx_ba_tbl->ba_status = BA_SETUP_COMPLETE;
+ if ((block_ack_param_set & IEEE80211_ADDBA_PARAM_AMSDU_MASK) &&
+ priv->add_ba_param.tx_amsdu &&
+ priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED)
+ tx_ba_tbl->amsdu = true;
+ else
+ tx_ba_tbl->amsdu = false;
+ if (ra_list) {
+ ra_list->amsdu_in_ampdu = tx_ba_tbl->amsdu;
+ ra_list->ba_status = BA_SETUP_COMPLETE;
+ }
+ } else {
+ nxpwifi_dbg(priv->adapter, ERROR, "BA stream not created\n");
+ }
+
+ return 0;
+}
+
+/*
+ * Reconfigure Tx buffer command.
+ * Set command ID/action/size; set Tx buffer size on SET; ensure little-endian.
+ */
+int nxpwifi_cmd_recfg_tx_buf(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, int cmd_action,
+ u16 *buf_size)
+{
+ struct host_cmd_ds_txbuf_cfg *tx_buf = &cmd->params.tx_buf;
+ u16 action = (u16)cmd_action;
+
+ cmd->command = cpu_to_le16(HOST_CMD_RECONFIGURE_TX_BUFF);
+ cmd->size =
+ cpu_to_le16(sizeof(struct host_cmd_ds_txbuf_cfg) + S_DS_GEN);
+ tx_buf->action = cpu_to_le16(action);
+ switch (action) {
+ case HOST_ACT_GEN_SET:
+ nxpwifi_dbg(priv->adapter, CMD,
+ "cmd: set tx_buf=%d\n", *buf_size);
+ tx_buf->buff_size = cpu_to_le16(*buf_size);
+ break;
+ case HOST_ACT_GEN_GET:
+ default:
+ tx_buf->buff_size = 0;
+ break;
+ }
+ return 0;
+}
+
+/*
+ * AMSDU aggregation control command.
+ * Set ID/action/size; set AMSDU params on SET; ensure little-endian.
+ */
+int nxpwifi_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd,
+ int cmd_action,
+ struct nxpwifi_ds_11n_amsdu_aggr_ctrl *aa_ctrl)
+{
+ struct host_cmd_ds_amsdu_aggr_ctrl *amsdu_ctrl =
+ &cmd->params.amsdu_aggr_ctrl;
+ u16 action = (u16)cmd_action;
+
+ cmd->command = cpu_to_le16(HOST_CMD_AMSDU_AGGR_CTRL);
+ cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_amsdu_aggr_ctrl)
+ + S_DS_GEN);
+ amsdu_ctrl->action = cpu_to_le16(action);
+ switch (action) {
+ case HOST_ACT_GEN_SET:
+ amsdu_ctrl->enable = cpu_to_le16(aa_ctrl->enable);
+ amsdu_ctrl->curr_buf_size = 0;
+ break;
+ case HOST_ACT_GEN_GET:
+ default:
+ amsdu_ctrl->curr_buf_size = 0;
+ break;
+ }
+ return 0;
+}
+
+/*
+ * 11n configuration command.
+ * Set action, HT Tx capability/info, and misc config when 11ac HW is present.
+ */
+int nxpwifi_cmd_11n_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_ds_11n_tx_cfg *txcfg)
+{
+ struct host_cmd_ds_11n_cfg *htcfg = &cmd->params.htcfg;
+
+ cmd->command = cpu_to_le16(HOST_CMD_11N_CFG);
+ cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_11n_cfg) + S_DS_GEN);
+ htcfg->action = cpu_to_le16(cmd_action);
+ htcfg->ht_tx_cap = cpu_to_le16(txcfg->tx_htcap);
+ htcfg->ht_tx_info = cpu_to_le16(txcfg->tx_htinfo);
+
+ if (priv->adapter->is_hw_11ac_capable)
+ htcfg->misc_config = cpu_to_le16(txcfg->misc_config);
+
+ return 0;
+}
+
+/*
+ * Append 11n TLVs to the caller-owned buffer.
+ * Caller allocates space; no size checks here.
+ * May add: HT Cap, HT Operation + channel list, 20/40 BSS Coexistence,
+ * and Extended Capabilities (HS2/TWT bits when applicable).
+ */
+int
+nxpwifi_cmd_append_11n_tlv(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc,
+ u8 **buffer)
+{
+ struct nxpwifi_ie_types_htcap *ht_cap;
+ struct nxpwifi_ie_types_chan_list_param_set *chan_list;
+ struct nxpwifi_chan_scan_param_set *chan_param;
+ struct nxpwifi_ie_types_2040bssco *bss_co_2040;
+ struct nxpwifi_ie_types_extcap *ext_cap;
+ int ret_len = 0;
+ struct ieee80211_supported_band *sband;
+ struct element *hdr;
+ u8 radio_type;
+
+ if (!buffer || !*buffer)
+ return ret_len;
+
+ radio_type = nxpwifi_band_to_radio_type((u8)bss_desc->bss_band);
+ sband = priv->wdev.wiphy->bands[radio_type];
+
+ if (bss_desc->bcn_ht_cap) {
+ ht_cap = (struct nxpwifi_ie_types_htcap *)*buffer;
+ memset(ht_cap, 0, sizeof(struct nxpwifi_ie_types_htcap));
+ ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY);
+ ht_cap->header.len =
+ cpu_to_le16(sizeof(struct ieee80211_ht_cap));
+ memcpy((u8 *)ht_cap + sizeof(struct nxpwifi_ie_types_header),
+ (u8 *)bss_desc->bcn_ht_cap,
+ le16_to_cpu(ht_cap->header.len));
+
+ nxpwifi_fill_cap_info(priv, radio_type, &ht_cap->ht_cap);
+ /* Update HT40 capability from current channel. */
+ if (bss_desc->bcn_ht_oper) {
+ u8 ht_param = bss_desc->bcn_ht_oper->ht_param;
+ u8 radio =
+ nxpwifi_band_to_radio_type(bss_desc->bss_band);
+ int freq =
+ ieee80211_channel_to_frequency(bss_desc->channel,
+ radio);
+ struct ieee80211_channel *chan =
+ ieee80211_get_channel(priv->adapter->wiphy, freq);
+
+ switch (ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) {
+ case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
+ if (chan->flags & IEEE80211_CHAN_NO_HT40PLUS) {
+ ht_cap->ht_cap.cap_info &=
+ cpu_to_le16
+ (~IEEE80211_HT_CAP_SUP_WIDTH_20_40);
+ ht_cap->ht_cap.cap_info &=
+ cpu_to_le16(~IEEE80211_HT_CAP_SGI_40);
+ }
+ break;
+ case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
+ if (chan->flags & IEEE80211_CHAN_NO_HT40MINUS) {
+ ht_cap->ht_cap.cap_info &=
+ cpu_to_le16
+ (~IEEE80211_HT_CAP_SUP_WIDTH_20_40);
+ ht_cap->ht_cap.cap_info &=
+ cpu_to_le16(~IEEE80211_HT_CAP_SGI_40);
+ }
+ break;
+ }
+ }
+
+ *buffer += sizeof(struct nxpwifi_ie_types_htcap);
+ ret_len += sizeof(struct nxpwifi_ie_types_htcap);
+ }
+
+ if (bss_desc->bcn_ht_oper) {
+ chan_list =
+ (struct nxpwifi_ie_types_chan_list_param_set *)*buffer;
+ chan_param = chan_list->chan_scan_param;
+ memset(chan_list, 0, struct_size(chan_list, chan_scan_param, 1));
+ chan_list->header.type = cpu_to_le16(TLV_TYPE_CHANLIST);
+ chan_list->header.len = cpu_to_le16(sizeof(*chan_param));
+ chan_param->chan_number = bss_desc->bcn_ht_oper->primary_chan;
+ chan_param->band_cfg =
+ nxpwifi_band_to_radio_type((u8)bss_desc->bss_band);
+
+ if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) &&
+ bss_desc->bcn_vht_oper &&
+ bss_desc->bcn_vht_oper->chan_width ==
+ IEEE80211_VHT_CHANWIDTH_80MHZ) {
+ SET_SECONDARYCHAN(chan_param->band_cfg,
+ (bss_desc->bcn_ht_oper->ht_param &
+ IEEE80211_HT_PARAM_CHA_SEC_OFFSET));
+ chan_param->band_cfg |=
+ ((CHAN_BW_80MHZ <<
+ BAND_CFG_CHAN_WIDTH_SHIFT_BIT) &
+ BAND_CFG_CHAN_WIDTH_MASK);
+ } else if (sband->ht_cap.cap &
+ IEEE80211_HT_CAP_SUP_WIDTH_20_40 &&
+ bss_desc->bcn_ht_oper->ht_param &
+ IEEE80211_HT_PARAM_CHAN_WIDTH_ANY) {
+ SET_SECONDARYCHAN(chan_param->band_cfg,
+ (bss_desc->bcn_ht_oper->ht_param &
+ IEEE80211_HT_PARAM_CHA_SEC_OFFSET));
+ chan_param->band_cfg |=
+ ((CHAN_BW_40MHZ <<
+ BAND_CFG_CHAN_WIDTH_SHIFT_BIT) &
+ BAND_CFG_CHAN_WIDTH_MASK);
+ }
+
+ *buffer += struct_size(chan_list, chan_scan_param, 1);
+ ret_len += struct_size(chan_list, chan_scan_param, 1);
+ }
+
+ if (bss_desc->bcn_bss_co_2040) {
+ bss_co_2040 = (struct nxpwifi_ie_types_2040bssco *)*buffer;
+ memset(bss_co_2040, 0,
+ sizeof(struct nxpwifi_ie_types_2040bssco));
+ bss_co_2040->header.type = cpu_to_le16(WLAN_EID_BSS_COEX_2040);
+ bss_co_2040->header.len =
+ cpu_to_le16(sizeof(bss_co_2040->bss_co_2040));
+
+ memcpy((u8 *)bss_co_2040 +
+ sizeof(struct nxpwifi_ie_types_header),
+ bss_desc->bcn_bss_co_2040 +
+ sizeof(struct element),
+ le16_to_cpu(bss_co_2040->header.len));
+
+ *buffer += sizeof(struct nxpwifi_ie_types_2040bssco);
+ ret_len += sizeof(struct nxpwifi_ie_types_2040bssco);
+ }
+
+ if (bss_desc->bcn_ext_cap) {
+ u8 *ext_capab;
+
+ hdr = (void *)bss_desc->bcn_ext_cap;
+
+ ext_capab = (u8 *)cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, priv->gen_ie_buf,
+ priv->gen_ie_buf_len);
+ if (ext_capab) {
+ ext_capab += 2;
+ } else {
+ ext_cap = (struct nxpwifi_ie_types_extcap *)*buffer;
+ memset(ext_cap, 0, sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen);
+ ext_cap->header.type = cpu_to_le16(WLAN_EID_EXT_CAPABILITY);
+ ext_cap->header.len = cpu_to_le16(hdr->datalen);
+ ext_capab = ext_cap->ext_capab;
+ *buffer += sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen;
+ ret_len += sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen;
+ }
+
+ if (hdr->datalen > 3 &&
+ ext_capab[3] & WLAN_EXT_CAPA4_INTERWORKING_ENABLED)
+ priv->hs2_enabled = true;
+ else
+ priv->hs2_enabled = false;
+
+ if (nxpwifi_is_11ax_twt_supported(priv, bss_desc))
+ ext_capab[9] |=
+ WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT;
+ }
+ return ret_len;
+}
+
+/* Check if pointer is a valid Tx BA stream entry. */
+static bool
+nxpwifi_is_tx_ba_stream_ptr_valid(struct nxpwifi_private *priv,
+ struct nxpwifi_tx_ba_stream_tbl *tx_tbl_ptr)
+{
+ struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl;
+ bool ret = false;
+ int tid;
+
+ tid = tx_tbl_ptr->tid;
+ guard(rcu)();
+ list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) {
+ if (tx_ba_tsr_tbl == tx_tbl_ptr) {
+ ret = true;
+ break;
+ }
+ }
+ return ret;
+}
+
+/* Delete a Tx BA stream entry (after validating pointer). */
+void
+nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv,
+ struct nxpwifi_tx_ba_stream_tbl *tbl)
+{
+ if (!tbl && nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl))
+ return;
+
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: tx_ba_tsr_tbl %p\n", tbl);
+
+ list_del_rcu(&tbl->list);
+ kfree_rcu(tbl, rcu);
+}
+
+/* Delete all entries in Tx BA stream table. */
+void nxpwifi_11n_delete_all_tx_ba_stream_tbl(struct nxpwifi_private *priv)
+{
+ int i;
+ struct nxpwifi_tx_ba_stream_tbl *del_tbl_ptr, *tmp_node;
+
+ for (i = 0; i < MAX_NUM_TID; i++) {
+ spin_lock_bh(&priv->tx_ba_stream_tbl_lock[i]);
+ list_for_each_entry_safe(del_tbl_ptr, tmp_node,
+ &priv->tx_ba_stream_tbl_ptr[i], list)
+ nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, del_tbl_ptr);
+ spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[i]);
+
+ INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr[i]);
+
+ priv->aggr_prio_tbl[i].ampdu_ap =
+ priv->aggr_prio_tbl[i].ampdu_user;
+ }
+}
+
+/* Return BA stream entry for given RA/TID. */
+struct nxpwifi_tx_ba_stream_tbl *
+nxpwifi_get_ba_tbl(struct nxpwifi_private *priv, int tid, u8 *ra)
+{
+ struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl = NULL;
+
+ list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) {
+ if (ether_addr_equal_unaligned(tx_ba_tsr_tbl->ra, ra) &&
+ tx_ba_tsr_tbl->tid == tid)
+ return tx_ba_tsr_tbl;
+ }
+ return NULL;
+}
+
+/* Create Tx BA stream entry for given RA/TID. */
+void nxpwifi_create_ba_tbl(struct nxpwifi_private *priv, u8 *ra, int tid,
+ enum nxpwifi_ba_status ba_status)
+{
+ struct nxpwifi_tx_ba_stream_tbl *new_node;
+ struct nxpwifi_ra_list_tbl *ra_list;
+ int tid_down;
+ struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl;
+
+ guard(rcu)();
+ tx_ba_tbl = nxpwifi_get_ba_tbl(priv, tid, ra);
+
+ if (!tx_ba_tbl) {
+ new_node = kzalloc_obj(*new_node, GFP_ATOMIC);
+ if (!new_node)
+ return;
+
+ tid_down = nxpwifi_wmm_downgrade_tid(priv, tid);
+ ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, ra);
+ if (ra_list) {
+ ra_list->ba_status = ba_status;
+ ra_list->amsdu_in_ampdu = false;
+ }
+ INIT_LIST_HEAD(&new_node->list);
+
+ new_node->tid = tid;
+ new_node->ba_status = ba_status;
+ memcpy(new_node->ra, ra, ETH_ALEN);
+
+ spin_lock_bh(&priv->tx_ba_stream_tbl_lock[tid]);
+ list_add_tail_rcu(&new_node->list, &priv->tx_ba_stream_tbl_ptr[tid]);
+ spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[tid]);
+ }
+}
+
+/* Send ADDBA request to the given TID/RA. */
+int nxpwifi_send_addba(struct nxpwifi_private *priv, int tid, u8 *peer_mac)
+{
+ struct host_cmd_ds_11n_addba_req add_ba_req;
+ u32 tx_win_size = priv->add_ba_param.tx_win_size;
+ static u8 dialog_tok;
+ u16 block_ack_param_set;
+
+ nxpwifi_dbg(priv->adapter, CMD, "cmd: %s: tid %d\n", __func__, tid);
+
+ memset(&add_ba_req, 0, sizeof(add_ba_req));
+
+ block_ack_param_set = (u16)((tid << BLOCKACKPARAM_TID_POS) |
+ tx_win_size << BLOCKACKPARAM_WINSIZE_POS |
+ IMMEDIATE_BLOCK_ACK);
+
+ /* enable AMSDU inside AMPDU */
+ if (priv->add_ba_param.tx_amsdu &&
+ priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED)
+ block_ack_param_set |= IEEE80211_ADDBA_PARAM_AMSDU_MASK;
+
+ add_ba_req.block_ack_param_set = cpu_to_le16(block_ack_param_set);
+ add_ba_req.block_ack_tmo = cpu_to_le16((u16)priv->add_ba_param.timeout);
+
+ ++dialog_tok;
+
+ if (dialog_tok == 0)
+ dialog_tok = 1;
+
+ add_ba_req.dialog_token = dialog_tok;
+ memcpy(&add_ba_req.peer_mac_addr, peer_mac, ETH_ALEN);
+
+ /* We don't wait for the response of this command */
+ return nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_REQ,
+ 0, 0, &add_ba_req, false);
+}
+
+/* Send DELBA request to the given TID/RA. */
+int nxpwifi_send_delba(struct nxpwifi_private *priv, int tid, u8 *peer_mac,
+ int initiator)
+{
+ struct host_cmd_ds_11n_delba delba;
+ u16 del_ba_param_set;
+
+ memset(&delba, 0, sizeof(delba));
+
+ del_ba_param_set = tid << DELBA_TID_POS;
+
+ if (initiator)
+ del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK;
+ else
+ del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK;
+
+ delba.del_ba_param_set = cpu_to_le16(del_ba_param_set);
+ memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN);
+
+ /* We don't wait for the response of this command */
+ return nxpwifi_send_cmd(priv, HOST_CMD_11N_DELBA,
+ HOST_ACT_GEN_SET, 0, &delba, false);
+}
+
+/* Send DELBA to specific TID. */
+void nxpwifi_11n_delba(struct nxpwifi_private *priv, int tid)
+{
+ struct nxpwifi_rx_reorder_tbl *rx_reor_tbl_ptr;
+ u8 ta[ETH_ALEN];
+ bool found = false;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(rx_reor_tbl_ptr, &priv->rx_reorder_tbl_ptr[tid], list) {
+ if (rx_reor_tbl_ptr->tid == tid) {
+ memcpy(ta, rx_reor_tbl_ptr->ta, ETH_ALEN);
+ found = true;
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ if (found) {
+ nxpwifi_dbg(priv->adapter, INFO,
+ "Send delba to tid=%d, %pM\n", tid, ta);
+ nxpwifi_send_delba(priv, tid, ta, 0);
+ }
+}
+
+/* Handle DELBA event; remove BA stream. */
+void nxpwifi_11n_delete_ba_stream(struct nxpwifi_private *priv, u8 *del_ba)
+{
+ struct host_cmd_ds_11n_delba *cmd_del_ba =
+ (struct host_cmd_ds_11n_delba *)del_ba;
+ u16 del_ba_param_set = le16_to_cpu(cmd_del_ba->del_ba_param_set);
+ int tid;
+
+ tid = del_ba_param_set >> DELBA_TID_POS;
+
+ nxpwifi_del_ba_tbl(priv, tid, cmd_del_ba->peer_mac_addr,
+ TYPE_DELBA_RECEIVE, INITIATOR_BIT(del_ba_param_set));
+}
+
+/* Retrieve Rx reordering table. */
+int nxpwifi_get_rx_reorder_tbl(struct nxpwifi_private *priv,
+ struct nxpwifi_ds_rx_reorder_tbl *buf)
+{
+ int i, j;
+ struct nxpwifi_ds_rx_reorder_tbl *rx_reo_tbl = buf;
+ struct nxpwifi_rx_reorder_tbl *rx_reorder_tbl_ptr;
+ int count = 0;
+
+ guard(rcu)();
+ for (j = 0; j < MAX_NUM_TID; j++) {
+ list_for_each_entry_rcu(rx_reorder_tbl_ptr,
+ &priv->rx_reorder_tbl_ptr[j],
+ list) {
+ rx_reo_tbl->tid = (u16)rx_reorder_tbl_ptr->tid;
+ memcpy(rx_reo_tbl->ta, rx_reorder_tbl_ptr->ta, ETH_ALEN);
+ rx_reo_tbl->start_win = rx_reorder_tbl_ptr->start_win;
+ rx_reo_tbl->win_size = rx_reorder_tbl_ptr->win_size;
+ for (i = 0; i < rx_reorder_tbl_ptr->win_size; ++i) {
+ if (rx_reorder_tbl_ptr->rx_reorder_ptr[i])
+ rx_reo_tbl->buffer[i] = true;
+ else
+ rx_reo_tbl->buffer[i] = false;
+ }
+ rx_reo_tbl++;
+ count++;
+
+ if (count >= NXPWIFI_MAX_RX_BASTREAM_SUPPORTED)
+ return count;
+ }
+ }
+
+ return count;
+}
+
+/* Retrieve Tx BA stream table. */
+int nxpwifi_get_tx_ba_stream_tbl(struct nxpwifi_private *priv,
+ struct nxpwifi_ds_tx_ba_stream_tbl *buf)
+{
+ struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl;
+ struct nxpwifi_ds_tx_ba_stream_tbl *rx_reo_tbl = buf;
+ int count = 0;
+ int i;
+
+ guard(rcu)();
+ for (i = 0; i < MAX_NUM_TID; i++) {
+ list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[i], list) {
+ rx_reo_tbl->tid = (u16)tx_ba_tsr_tbl->tid;
+ nxpwifi_dbg(priv->adapter, DATA, "data: %s tid=%d\n",
+ __func__, rx_reo_tbl->tid);
+ memcpy(rx_reo_tbl->ra, tx_ba_tsr_tbl->ra, ETH_ALEN);
+ rx_reo_tbl->amsdu = tx_ba_tsr_tbl->amsdu;
+ rx_reo_tbl++;
+ count++;
+ if (count >= NXPWIFI_MAX_TX_BASTREAM_SUPPORTED)
+ return count;
+ }
+ }
+
+ return count;
+}
+
+/* Delete Tx BA stream entry by RA. */
+void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra)
+{
+ struct nxpwifi_tx_ba_stream_tbl *tbl;
+ int i;
+
+ if (!ra)
+ return;
+
+ for (i = 0; i < MAX_NUM_TID; i++) {
+ spin_lock_bh(&priv->tx_ba_stream_tbl_lock[i]);
+ list_for_each_entry_rcu(tbl, &priv->tx_ba_stream_tbl_ptr[i], list)
+ if (!memcmp(tbl->ra, ra, ETH_ALEN))
+ nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, tbl);
+
+ spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[i]);
+ }
+}
+
+/* Initialize BlockAck parameters. */
+void nxpwifi_set_ba_params(struct nxpwifi_private *priv)
+{
+ priv->add_ba_param.timeout = NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT;
+
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) {
+ priv->add_ba_param.tx_win_size =
+ NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE;
+ priv->add_ba_param.rx_win_size =
+ NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE;
+ } else {
+ priv->add_ba_param.tx_win_size =
+ NXPWIFI_STA_AMPDU_DEF_TXWINSIZE;
+ priv->add_ba_param.rx_win_size =
+ NXPWIFI_STA_AMPDU_DEF_RXWINSIZE;
+ }
+
+ priv->add_ba_param.tx_amsdu = true;
+ priv->add_ba_param.rx_amsdu = true;
+}
+
+u8 nxpwifi_get_sec_chan_offset(int chan)
+{
+ u8 sec_offset;
+
+ switch (chan) {
+ case 36:
+ case 44:
+ case 52:
+ case 60:
+ case 100:
+ case 108:
+ case 116:
+ case 124:
+ case 132:
+ case 140:
+ case 149:
+ case 157:
+ case 173:
+ sec_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
+ break;
+ case 40:
+ case 48:
+ case 56:
+ case 64:
+ case 104:
+ case 112:
+ case 120:
+ case 128:
+ case 136:
+ case 144:
+ case 153:
+ case 161:
+ case 169:
+ case 177:
+ sec_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
+ break;
+ case 165:
+ default:
+ sec_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE;
+ break;
+ }
+
+ return sec_offset;
+}
+
+/* Send DELBA to entries in the Tx BA stream table. */
+static void
+nxpwifi_send_delba_txbastream_tbl(struct nxpwifi_private *priv, u8 tid)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct nxpwifi_tx_ba_stream_tbl *tx_ba_stream_tbl_ptr;
+
+ guard(rcu)();
+ list_for_each_entry_rcu(tx_ba_stream_tbl_ptr,
+ &priv->tx_ba_stream_tbl_ptr[tid], list) {
+ if (tx_ba_stream_tbl_ptr->ba_status == BA_SETUP_COMPLETE) {
+ if (tid == tx_ba_stream_tbl_ptr->tid) {
+ nxpwifi_dbg(adapter, INFO,
+ "Tx:Send delba to tid=%d, %pM\n", tid,
+ tx_ba_stream_tbl_ptr->ra);
+ nxpwifi_send_delba(priv,
+ tx_ba_stream_tbl_ptr->tid,
+ tx_ba_stream_tbl_ptr->ra, 1);
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Update tx_win_size for all interfaces and send DELBA when it changes.
+ */
+void nxpwifi_update_ampdu_txwinsize(struct nxpwifi_adapter *adapter)
+{
+ u8 i, j;
+ u32 tx_win_size;
+ struct nxpwifi_private *priv;
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+ tx_win_size = priv->add_ba_param.tx_win_size;
+
+ if (priv->bss_type == NXPWIFI_BSS_TYPE_STA)
+ priv->add_ba_param.tx_win_size =
+ NXPWIFI_STA_AMPDU_DEF_TXWINSIZE;
+
+ if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP)
+ priv->add_ba_param.tx_win_size =
+ NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE;
+
+ if (adapter->coex_win_size) {
+ if (adapter->coex_tx_win_size)
+ priv->add_ba_param.tx_win_size =
+ adapter->coex_tx_win_size;
+ }
+
+ if (tx_win_size != priv->add_ba_param.tx_win_size) {
+ if (!priv->media_connected)
+ continue;
+ for (j = 0; j < MAX_NUM_TID; j++)
+ nxpwifi_send_delba_txbastream_tbl(priv, j);
+ }
+ }
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.h b/drivers/net/wireless/nxp/nxpwifi/11n.h
new file mode 100644
index 000000000000..039c45993f07
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11n.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * nxpwifi: 802.11n support
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#ifndef _NXPWIFI_11N_H_
+#define _NXPWIFI_11N_H_
+
+#include "11n_aggr.h"
+#include "11n_rxreorder.h"
+#include "wmm.h"
+
+int nxpwifi_ret_11n_delba(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp);
+int nxpwifi_ret_11n_addba_req(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp);
+int nxpwifi_cmd_11n_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd, u16 cmd_action,
+ struct nxpwifi_ds_11n_tx_cfg *txcfg);
+int nxpwifi_cmd_append_11n_tlv(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc,
+ u8 **buffer);
+int nxpwifi_fill_cap_info(struct nxpwifi_private *priv, u8 radio_type,
+ struct ieee80211_ht_cap *ht_cap);
+int nxpwifi_set_get_11n_htcap_cfg(struct nxpwifi_private *priv,
+ u16 action, int *htcap_cfg);
+void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv,
+ struct nxpwifi_tx_ba_stream_tbl
+ *tx_tbl);
+void nxpwifi_11n_delete_all_tx_ba_stream_tbl(struct nxpwifi_private *priv);
+struct nxpwifi_tx_ba_stream_tbl *nxpwifi_get_ba_tbl(struct nxpwifi_private
+ *priv, int tid, u8 *ra);
+void nxpwifi_create_ba_tbl(struct nxpwifi_private *priv, u8 *ra, int tid,
+ enum nxpwifi_ba_status ba_status);
+int nxpwifi_send_addba(struct nxpwifi_private *priv, int tid, u8 *peer_mac);
+int nxpwifi_send_delba(struct nxpwifi_private *priv, int tid, u8 *peer_mac,
+ int initiator);
+void nxpwifi_11n_delete_ba_stream(struct nxpwifi_private *priv, u8 *del_ba);
+int nxpwifi_get_rx_reorder_tbl(struct nxpwifi_private *priv,
+ struct nxpwifi_ds_rx_reorder_tbl *buf);
+int nxpwifi_get_tx_ba_stream_tbl(struct nxpwifi_private *priv,
+ struct nxpwifi_ds_tx_ba_stream_tbl *buf);
+int nxpwifi_cmd_recfg_tx_buf(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ int cmd_action, u16 *buf_size);
+int nxpwifi_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd,
+ int cmd_action,
+ struct nxpwifi_ds_11n_amsdu_aggr_ctrl *aa_ctrl);
+void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra);
+u8 nxpwifi_get_sec_chan_offset(int chan);
+
+static inline bool
+nxpwifi_is_station_ampdu_allowed(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ptr, int tid)
+{
+ struct nxpwifi_sta_node *node;
+
+ guard(rcu)();
+ node = nxpwifi_get_sta_entry(priv, ptr->ra);
+ if (unlikely(!node))
+ return false;
+
+ if (node->ampdu_sta[tid] == BA_STREAM_NOT_ALLOWED)
+ return false;
+
+ return true;
+}
+
+/* Check if AMPDU is allowed for the given TID. */
+static inline bool
+nxpwifi_is_ampdu_allowed(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ptr, int tid)
+{
+ if (is_broadcast_ether_addr(ptr->ra))
+ return false;
+
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP)
+ return nxpwifi_is_station_ampdu_allowed(priv, ptr, tid);
+
+ return priv->aggr_prio_tbl[tid].ampdu_ap != BA_STREAM_NOT_ALLOWED;
+}
+
+/* Check if AMSDU is allowed for the given TID. */
+static inline bool
+nxpwifi_is_amsdu_allowed(struct nxpwifi_private *priv, int tid)
+{
+ bool amsdu_enabled = priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED;
+ bool rate_ok = priv->is_data_rate_auto || !(priv->bitmap_rates[2] & 0x03);
+
+ return amsdu_enabled && rate_ok;
+}
+
+/* Check if there is available space for a new BA stream. */
+static inline bool
+nxpwifi_space_avail_for_new_ba_stream(struct nxpwifi_adapter *adapter)
+{
+ struct nxpwifi_private *priv;
+ u8 i, j;
+ size_t ba_stream_num = 0;
+ size_t ba_stream_max = NXPWIFI_MAX_TX_BASTREAM_SUPPORTED;
+
+ if (adapter->fw_api_ver == NXPWIFI_FW_V15) {
+ ba_stream_max = GETSUPP_TXBASTREAMS(adapter->hw_dot_11n_dev_cap);
+ if (!ba_stream_max)
+ ba_stream_max = NXPWIFI_MAX_TX_BASTREAM_SUPPORTED;
+ }
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+ for (j = 0; j < MAX_NUM_TID; j++)
+ ba_stream_num += list_count_nodes(&priv->tx_ba_stream_tbl_ptr[j]);
+ }
+
+ return ba_stream_num < ba_stream_max;
+}
+
+/* Find the Tx BA stream to delete and return its TID and RA. */
+static inline bool
+nxpwifi_find_stream_to_delete(struct nxpwifi_private *priv, int ptr_tid,
+ int *ptid, u8 *ra)
+{
+ int search_tid = priv->aggr_prio_tbl[ptr_tid].ampdu_user;
+ bool found = false;
+ struct nxpwifi_tx_ba_stream_tbl *tx_tbl;
+ int candidate_tid;
+
+ spin_lock_bh(&priv->tx_ba_stream_tbl_lock[ptr_tid]);
+
+ list_for_each_entry(tx_tbl, &priv->tx_ba_stream_tbl_ptr[ptr_tid], list) {
+ candidate_tid = priv->aggr_prio_tbl[tx_tbl->tid].ampdu_user;
+
+ if (search_tid > candidate_tid) {
+ search_tid = candidate_tid;
+ *ptid = tx_tbl->tid;
+ memcpy(ra, tx_tbl->ra, ETH_ALEN);
+ found = true;
+ }
+ }
+
+ spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[ptr_tid]);
+
+ return found;
+}
+
+/* Check whether the associated station is 11n enabled. */
+static inline int nxpwifi_is_sta_11n_enabled(struct nxpwifi_private *priv,
+ struct nxpwifi_sta_node *node)
+{
+ if (!node || (priv->bss_role == NXPWIFI_BSS_ROLE_UAP &&
+ !priv->ap_11n_enabled))
+ return 0;
+
+ return node->is_11n_enabled;
+}
+
+#endif /* !_NXPWIFI_11N_H_ */
diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c
new file mode 100644
index 000000000000..be7080f2a6ce
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nxpwifi: 802.11n Aggregation
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#include "cfg.h"
+#include "util.h"
+#include "fw.h"
+#include "main.h"
+#include "wmm.h"
+#include "11n.h"
+#include "11n_aggr.h"
+
+/*
+ * Build an AMSDU subframe for aggregation, with fields
+ * (DA | SA | Length | SNAP header | MSDU), and compute padding
+ * to align the subframe to a 4-byte boundary.
+ */
+static int nxpwifi_11n_form_amsdu_pkt(struct sk_buff *skb_aggr,
+ struct sk_buff *skb_src, int *pad)
+
+{
+ int dt_offset;
+ struct rfc_1042_hdr snap = {
+ 0xaa, /* LLC DSAP */
+ 0xaa, /* LLC SSAP */
+ 0x03, /* LLC CTRL */
+ {0x00, 0x00, 0x00}, /* SNAP OUI */
+ 0x0000 /* SNAP type */
+ /* This field will be overwritten later with ethertype */
+ };
+ struct tx_packet_hdr *tx_header;
+
+ tx_header = skb_put(skb_aggr, sizeof(*tx_header));
+
+ /* Copy DA and SA */
+ dt_offset = 2 * ETH_ALEN;
+ memcpy(&tx_header->eth803_hdr, skb_src->data, dt_offset);
+
+ /* Copy SNAP header */
+ snap.snap_type = ((struct ethhdr *)skb_src->data)->h_proto;
+
+ dt_offset += sizeof(__be16);
+
+ memcpy(&tx_header->rfc1042_hdr, &snap, sizeof(struct rfc_1042_hdr));
+
+ skb_pull(skb_src, dt_offset);
+
+ /* Update Length field */
+ tx_header->eth803_hdr.h_proto = htons(skb_src->len + LLC_SNAP_LEN);
+
+ /* Add payload */
+ skb_put_data(skb_aggr, skb_src->data, skb_src->len);
+
+ /* Add padding for new MSDU to start from 4 byte boundary */
+ *pad = (4 - ((unsigned long)skb_aggr->tail & 0x3)) % 4;
+
+ return skb_aggr->len + *pad;
+}
+
+/*
+ * Adds TxPD to AMSDU header. Each AMSDU packet will contain one TxPD at the
+ * beginning, followed by multiple AMSDU subframes
+ */
+static void
+nxpwifi_11n_form_amsdu_txpd(struct nxpwifi_private *priv,
+ struct sk_buff *skb)
+{
+ struct txpd *local_tx_pd;
+
+ skb_push(skb, sizeof(*local_tx_pd));
+
+ local_tx_pd = (struct txpd *)skb->data;
+ memset(local_tx_pd, 0, sizeof(struct txpd));
+
+ /* Original priority has been overwritten */
+ local_tx_pd->priority = (u8)skb->priority;
+ local_tx_pd->pkt_delay_2ms =
+ nxpwifi_wmm_compute_drv_pkt_delay(priv, skb);
+ local_tx_pd->bss_num = priv->bss_num;
+ local_tx_pd->bss_type = priv->bss_type;
+ /* Always zero as the data is followed by struct txpd */
+ local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd));
+ local_tx_pd->tx_pkt_type = cpu_to_le16(PKT_TYPE_AMSDU);
+ local_tx_pd->tx_pkt_length = cpu_to_le16(skb->len -
+ sizeof(*local_tx_pd));
+
+ if (local_tx_pd->tx_control == 0)
+ /* TxCtrl set by user or default */
+ local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl);
+
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA &&
+ priv->adapter->pps_uapsd_mode) {
+ if (nxpwifi_check_last_packet_indication(priv)) {
+ priv->adapter->tx_lock_flag = true;
+ local_tx_pd->flags =
+ NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET;
+ }
+ }
+}
+
+/*
+ * Build an aggregated MSDU packet by encapsulating buffers from the RA
+ * list as AMSDU subframes and concatenating them. A TxPD is prepended
+ * before transmission to form the final AMSDU packet.
+ */
+int
+nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *pra_list,
+ int ptrindex)
+ __releases(&priv->wmm.ra_list_spinlock)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct sk_buff *skb_aggr, *skb_src;
+ struct nxpwifi_txinfo *tx_info_aggr, *tx_info_src;
+ int pad = 0, aggr_num = 0, ret;
+ struct nxpwifi_tx_param tx_param;
+ struct txpd *ptx_pd = NULL;
+ int headroom = adapter->intf_hdr_len;
+
+ skb_src = skb_peek(&pra_list->skb_head);
+ if (!skb_src) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ return 0;
+ }
+
+ tx_info_src = NXPWIFI_SKB_TXCB(skb_src);
+ skb_aggr = nxpwifi_alloc_dma_align_buf(adapter->tx_buf_size,
+ GFP_ATOMIC);
+ if (!skb_aggr) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ return -ENOMEM;
+ }
+
+ /*
+ * skb_aggr->data already 64 byte align, just reserve bus interface
+ * header and txpd.
+ */
+ skb_reserve(skb_aggr, headroom + sizeof(struct txpd));
+ tx_info_aggr = NXPWIFI_SKB_TXCB(skb_aggr);
+
+ memset(tx_info_aggr, 0, sizeof(*tx_info_aggr));
+ tx_info_aggr->bss_type = tx_info_src->bss_type;
+ tx_info_aggr->bss_num = tx_info_src->bss_num;
+
+ tx_info_aggr->flags |= NXPWIFI_BUF_FLAG_AGGR_PKT;
+ skb_aggr->priority = skb_src->priority;
+ skb_aggr->tstamp = skb_src->tstamp;
+
+ do {
+ /* Check if AMSDU can accommodate this MSDU */
+ if ((skb_aggr->len + skb_src->len + LLC_SNAP_LEN) >
+ adapter->tx_buf_size)
+ break;
+
+ skb_src = skb_dequeue(&pra_list->skb_head);
+ pra_list->total_pkt_count--;
+ atomic_dec(&priv->wmm.tx_pkts_queued);
+ aggr_num++;
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ nxpwifi_11n_form_amsdu_pkt(skb_aggr, skb_src, &pad);
+
+ nxpwifi_write_data_complete(adapter, skb_src, 0, 0);
+
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+
+ if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ return -ENOENT;
+ }
+
+ if (skb_tailroom(skb_aggr) < pad) {
+ pad = 0;
+ break;
+ }
+ skb_put(skb_aggr, pad);
+
+ skb_src = skb_peek(&pra_list->skb_head);
+
+ } while (skb_src);
+
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+
+ /* Last AMSDU packet does not need padding */
+ skb_trim(skb_aggr, skb_aggr->len - pad);
+
+ /* Form AMSDU */
+ nxpwifi_11n_form_amsdu_txpd(priv, skb_aggr);
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA)
+ ptx_pd = (struct txpd *)skb_aggr->data;
+
+ skb_push(skb_aggr, headroom);
+ tx_info_aggr->aggr_num = aggr_num * 2;
+ if (adapter->data_sent || adapter->tx_lock_flag) {
+ atomic_add(aggr_num * 2, &adapter->tx_queued);
+ skb_queue_tail(&adapter->tx_data_q, skb_aggr);
+ return 0;
+ }
+
+ if (skb_src)
+ tx_param.next_pkt_len = skb_src->len + sizeof(struct txpd);
+ else
+ tx_param.next_pkt_len = 0;
+
+ ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA,
+ skb_aggr, &tx_param);
+
+ switch (ret) {
+ case -EBUSY:
+ spin_lock_bh(&priv->wmm.ra_list_spinlock);
+ if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) {
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ nxpwifi_write_data_complete(adapter, skb_aggr, 1, -1);
+ return -EINVAL;
+ }
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA &&
+ adapter->pps_uapsd_mode && adapter->tx_lock_flag) {
+ priv->adapter->tx_lock_flag = false;
+ if (ptx_pd)
+ ptx_pd->flags = 0;
+ }
+
+ skb_queue_tail(&pra_list->skb_head, skb_aggr);
+
+ pra_list->total_pkt_count++;
+
+ atomic_inc(&priv->wmm.tx_pkts_queued);
+
+ tx_info_aggr->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT;
+ spin_unlock_bh(&priv->wmm.ra_list_spinlock);
+ nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n");
+ break;
+ case -EINPROGRESS:
+ break;
+ case 0:
+ nxpwifi_write_data_complete(adapter, skb_aggr, 1, ret);
+ break;
+ default:
+ nxpwifi_dbg(adapter, ERROR, "%s: host_to_card failed: %#x\n",
+ __func__, ret);
+ adapter->dbg.num_tx_host_to_card_failure++;
+ nxpwifi_write_data_complete(adapter, skb_aggr, 1, ret);
+ break;
+ }
+ if (ret != -EBUSY)
+ nxpwifi_rotate_priolists(priv, pra_list, ptrindex);
+
+ return 0;
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h
new file mode 100644
index 000000000000..be9f0f8f4e48
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * NXP Wireless LAN device driver: 802.11n Aggregation
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#ifndef _NXPWIFI_11N_AGGR_H_
+#define _NXPWIFI_11N_AGGR_H_
+
+#define PKT_TYPE_AMSDU 0xE6
+#define MIN_NUM_AMSDU 2
+
+int nxpwifi_11n_deaggregate_pkt(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+int nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv,
+ struct nxpwifi_ra_list_tbl *ptr,
+ int ptr_index)
+ __releases(&priv->wmm.ra_list_spinlock);
+
+#endif /* !_NXPWIFI_11N_AGGR_H_ */
diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c
new file mode 100644
index 000000000000..c5819f89b08c
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c
@@ -0,0 +1,826 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nxpwifi: 802.11n RX Re-ordering
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#include "cfg.h"
+#include "util.h"
+#include "fw.h"
+#include "main.h"
+#include "cmdevt.h"
+#include "wmm.h"
+#include "11n.h"
+#include "11n_rxreorder.h"
+/* Dispatch A-MSDU to stack. */
+static int nxpwifi_11n_dispatch_amsdu_pkt(struct nxpwifi_private *priv,
+ struct sk_buff *skb)
+{
+ struct rxpd *local_rx_pd = (struct rxpd *)(skb->data);
+ int ret;
+
+ if (le16_to_cpu(local_rx_pd->rx_pkt_type) == PKT_TYPE_AMSDU) {
+ struct sk_buff_head list;
+ struct sk_buff *rx_skb;
+
+ __skb_queue_head_init(&list);
+
+ skb_pull(skb, le16_to_cpu(local_rx_pd->rx_pkt_offset));
+ skb_trim(skb, le16_to_cpu(local_rx_pd->rx_pkt_length));
+
+ ieee80211_amsdu_to_8023s(skb, &list, priv->curr_addr,
+ priv->wdev.iftype, 0, NULL, NULL, false);
+
+ while (!skb_queue_empty(&list)) {
+ rx_skb = __skb_dequeue(&list);
+
+ if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP)
+ ret = nxpwifi_uap_recv_packet(priv, rx_skb);
+ else
+ ret = nxpwifi_recv_packet(priv, rx_skb);
+ if (ret)
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Rx of A-MSDU failed");
+ }
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* Process RX packet and forward to stack. */
+static int nxpwifi_11n_dispatch_pkt(struct nxpwifi_private *priv,
+ struct sk_buff *payload)
+{
+ int ret;
+
+ if (!payload) {
+ nxpwifi_dbg(priv->adapter, INFO, "info: fw drop data\n");
+ return 0;
+ }
+
+ ret = nxpwifi_11n_dispatch_amsdu_pkt(priv, payload);
+ if (!ret)
+ return 0;
+
+ if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP)
+ return nxpwifi_handle_uap_rx_forward(priv, payload);
+
+ return nxpwifi_process_rx_packet(priv, payload);
+}
+
+/* Dispatch packets up to start_win. */
+static void
+nxpwifi_11n_dispatch_pkt_until_start_win(struct nxpwifi_private *priv,
+ struct nxpwifi_rx_reorder_tbl *tbl,
+ int start_win)
+{
+ struct sk_buff_head list;
+ struct sk_buff *skb;
+ int pkt_to_send, i, tid;
+
+ tid = tbl->tid;
+ __skb_queue_head_init(&list);
+ spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]);
+
+ pkt_to_send = (start_win > tbl->start_win) ?
+ min((start_win - tbl->start_win), tbl->win_size) :
+ tbl->win_size;
+
+ for (i = 0; i < pkt_to_send; ++i) {
+ if (tbl->rx_reorder_ptr[i]) {
+ skb = tbl->rx_reorder_ptr[i];
+ __skb_queue_tail(&list, skb);
+ tbl->rx_reorder_ptr[i] = NULL;
+ }
+ }
+
+ /* Simulate circular buffer via rotation. */
+ for (i = 0; i < tbl->win_size - pkt_to_send; ++i) {
+ tbl->rx_reorder_ptr[i] = tbl->rx_reorder_ptr[pkt_to_send + i];
+ tbl->rx_reorder_ptr[pkt_to_send + i] = NULL;
+ }
+
+ tbl->start_win = start_win;
+ spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]);
+
+ while ((skb = __skb_dequeue(&list)))
+ nxpwifi_11n_dispatch_pkt(priv, skb);
+}
+
+/* Dispatch packets until a hole is found. */
+static void
+nxpwifi_11n_scan_and_dispatch(struct nxpwifi_private *priv,
+ struct nxpwifi_rx_reorder_tbl *tbl)
+{
+ struct sk_buff_head list;
+ struct sk_buff *skb;
+ int i, j, xchg, tid;
+
+ tid = tbl->tid;
+ __skb_queue_head_init(&list);
+ spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]);
+
+ for (i = 0; i < tbl->win_size; ++i) {
+ if (!tbl->rx_reorder_ptr[i])
+ break;
+ skb = tbl->rx_reorder_ptr[i];
+ __skb_queue_tail(&list, skb);
+ tbl->rx_reorder_ptr[i] = NULL;
+ }
+
+ /* Simulate circular buffer via rotation. */
+ if (i > 0) {
+ xchg = tbl->win_size - i;
+ for (j = 0; j < xchg; ++j) {
+ tbl->rx_reorder_ptr[j] = tbl->rx_reorder_ptr[i + j];
+ tbl->rx_reorder_ptr[i + j] = NULL;
+ }
+ }
+ tbl->start_win = (tbl->start_win + i) & (MAX_TID_VALUE - 1);
+
+ spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]);
+
+ while ((skb = __skb_dequeue(&list)))
+ nxpwifi_11n_dispatch_pkt(priv, skb);
+}
+
+/* Delete RX reorder entry and flush pending packets. */
+static void
+nxpwifi_del_rx_reorder_entry(struct nxpwifi_private *priv,
+ struct nxpwifi_rx_reorder_tbl *tbl)
+{
+ int start_win, tid;
+
+ if (!tbl)
+ return;
+
+ tid = tbl->tid;
+
+ atomic_set(&priv->adapter->rx_ba_teardown_pending, 1);
+ flush_workqueue(priv->adapter->rx_workqueue);
+
+ start_win = (tbl->start_win + tbl->win_size) & (MAX_TID_VALUE - 1);
+ nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, start_win);
+
+ timer_delete_sync(&tbl->timer_context.timer);
+ tbl->timer_context.timer_is_set = false;
+
+ spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]);
+ list_del_rcu(&tbl->list);
+ spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]);
+
+ kfree(tbl->rx_reorder_ptr);
+ kfree_rcu(tbl, rcu);
+
+ atomic_set(&priv->adapter->rx_ba_teardown_pending, 0);
+}
+
+/* Lookup RX reorder entry by TID/TA. */
+struct nxpwifi_rx_reorder_tbl *
+nxpwifi_11n_get_rx_reorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta)
+{
+ struct nxpwifi_rx_reorder_tbl *tbl, *found = NULL;
+
+ guard(rcu)();
+
+ list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[tid], list) {
+ if (!memcmp(tbl->ta, ta, ETH_ALEN) && tbl->tid == tid) {
+ found = tbl;
+ break;
+ }
+ }
+
+ return found;
+}
+
+/* Delete RX reorder entries by TA. */
+void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta)
+{
+ struct nxpwifi_rx_reorder_tbl *tbl, *tmp;
+ LIST_HEAD(to_delete);
+ int i;
+
+ if (!ta)
+ return;
+
+ for (i = 0; i < MAX_NUM_TID; i++) {
+ guard(rcu)();
+ list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) {
+ if (!memcmp(tbl->ta, ta, ETH_ALEN)) {
+ INIT_LIST_HEAD(&tbl->tmp_list);
+ list_add_tail(&tbl->tmp_list, &to_delete);
+ }
+ }
+
+ list_for_each_entry_safe(tbl, tmp, &to_delete, tmp_list)
+ nxpwifi_del_rx_reorder_entry(priv, tbl);
+
+ INIT_LIST_HEAD(&to_delete);
+ }
+}
+
+/* Find last buffered sequence index. */
+static int
+nxpwifi_11n_find_last_seq_num(struct reorder_tmr_cnxt *ctx)
+{
+ struct nxpwifi_rx_reorder_tbl *rx_reorder_tbl_ptr = ctx->ptr;
+ int i;
+
+ guard(rcu)();
+ for (i = rx_reorder_tbl_ptr->win_size - 1; i >= 0; --i) {
+ if (rx_reorder_tbl_ptr->rx_reorder_ptr[i])
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+/* Flush and dispatch buffered packets on timer. */
+static void
+nxpwifi_flush_data(struct timer_list *t)
+{
+ struct reorder_tmr_cnxt *ctx =
+ timer_container_of(ctx, t, timer);
+ int start_win, seq_num;
+
+ ctx->timer_is_set = false;
+ seq_num = nxpwifi_11n_find_last_seq_num(ctx);
+
+ if (seq_num < 0)
+ return;
+
+ nxpwifi_dbg(ctx->priv->adapter, INFO, "info: flush data %d\n", seq_num);
+ start_win = (ctx->ptr->start_win + seq_num + 1) & (MAX_TID_VALUE - 1);
+ nxpwifi_11n_dispatch_pkt_until_start_win(ctx->priv, ctx->ptr,
+ start_win);
+}
+
+/* Create RX reorder entry (TID/TA, SSN, winsize, timer). */
+static void
+nxpwifi_11n_create_rx_reorder_tbl(struct nxpwifi_private *priv, u8 *ta,
+ int tid, int win_size, int seq_num)
+{
+ int i;
+ struct nxpwifi_rx_reorder_tbl *tbl, *new_node;
+ u16 last_seq = 0;
+ struct nxpwifi_sta_node *node;
+
+ /* Existing TID/TA: flush and move window to SSN. */
+ tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, ta);
+ if (tbl) {
+ nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, seq_num);
+ return;
+ }
+ /* if !tbl then create one */
+ new_node = kzalloc_obj(*new_node, GFP_KERNEL);
+ if (!new_node)
+ return;
+
+ INIT_LIST_HEAD(&new_node->list);
+ new_node->tid = tid;
+ memcpy(new_node->ta, ta, ETH_ALEN);
+ new_node->start_win = seq_num;
+ new_node->init_win = seq_num;
+ new_node->flags = 0;
+
+ if (nxpwifi_queuing_ra_based(priv)) {
+ if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) {
+ guard(rcu)();
+ node = nxpwifi_get_sta_entry(priv, ta);
+ if (node)
+ last_seq = node->rx_seq[tid];
+ }
+ } else {
+ guard(rcu)();
+ node = nxpwifi_get_sta_entry(priv, ta);
+ if (node)
+ last_seq = node->rx_seq[tid];
+ else
+ last_seq = priv->rx_seq[tid];
+ }
+
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: last_seq=%d start_win=%d\n",
+ last_seq, new_node->start_win);
+
+ if (last_seq != NXPWIFI_DEF_11N_RX_SEQ_NUM &&
+ last_seq >= new_node->start_win) {
+ new_node->start_win = last_seq + 1;
+ new_node->flags |= RXREOR_INIT_WINDOW_SHIFT;
+ }
+
+ new_node->win_size = win_size;
+
+ new_node->rx_reorder_ptr = kcalloc(win_size, sizeof(void *),
+ GFP_KERNEL);
+ if (!new_node->rx_reorder_ptr) {
+ kfree(new_node);
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "%s: failed to alloc reorder_ptr\n", __func__);
+ return;
+ }
+
+ new_node->timer_context.ptr = new_node;
+ new_node->timer_context.priv = priv;
+ new_node->timer_context.timer_is_set = false;
+
+ timer_setup(&new_node->timer_context.timer, nxpwifi_flush_data, 0);
+
+ for (i = 0; i < win_size; ++i)
+ new_node->rx_reorder_ptr[i] = NULL;
+
+ spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]);
+ list_add_tail_rcu(&new_node->list, &priv->rx_reorder_tbl_ptr[tid]);
+ spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]);
+}
+
+static void
+nxpwifi_11n_rxreorder_timer_restart(struct nxpwifi_rx_reorder_tbl *tbl)
+{
+ u32 min_flush_time;
+
+ if (tbl->win_size >= NXPWIFI_BA_WIN_SIZE_32)
+ min_flush_time = MIN_FLUSH_TIMER_15_MS;
+ else
+ min_flush_time = MIN_FLUSH_TIMER_MS;
+
+ mod_timer(&tbl->timer_context.timer,
+ jiffies + msecs_to_jiffies(min_flush_time * tbl->win_size));
+
+ tbl->timer_context.timer_is_set = true;
+}
+
+/* Prepare ADDBA request. */
+int nxpwifi_cmd_11n_addba_req(struct host_cmd_ds_command *cmd, void *data_buf)
+{
+ struct host_cmd_ds_11n_addba_req *add_ba_req = &cmd->params.add_ba_req;
+
+ cmd->command = cpu_to_le16(HOST_CMD_11N_ADDBA_REQ);
+ cmd->size = cpu_to_le16(sizeof(*add_ba_req) + S_DS_GEN);
+ memcpy(add_ba_req, data_buf, sizeof(*add_ba_req));
+
+ return 0;
+}
+
+/* Prepare ADDBA response and create RX reorder table. */
+int nxpwifi_cmd_11n_addba_rsp_gen(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ struct host_cmd_ds_11n_addba_req
+ *cmd_addba_req)
+{
+ struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &cmd->params.add_ba_rsp;
+ u32 rx_win_size = priv->add_ba_param.rx_win_size;
+ u8 tid;
+ int win_size;
+ u16 block_ack_param_set;
+
+ cmd->command = cpu_to_le16(HOST_CMD_11N_ADDBA_RSP);
+ cmd->size = cpu_to_le16(sizeof(*add_ba_rsp) + S_DS_GEN);
+
+ memcpy(add_ba_rsp->peer_mac_addr, cmd_addba_req->peer_mac_addr,
+ ETH_ALEN);
+ add_ba_rsp->dialog_token = cmd_addba_req->dialog_token;
+ add_ba_rsp->block_ack_tmo = cmd_addba_req->block_ack_tmo;
+ add_ba_rsp->ssn = cmd_addba_req->ssn;
+
+ block_ack_param_set = le16_to_cpu(cmd_addba_req->block_ack_param_set);
+ tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK)
+ >> BLOCKACKPARAM_TID_POS;
+ add_ba_rsp->status_code = cpu_to_le16(ADDBA_RSP_STATUS_ACCEPT);
+ block_ack_param_set &= ~IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK;
+
+ /* If we don't support AMSDU inside AMPDU, reset the bit */
+ if (!priv->add_ba_param.rx_amsdu ||
+ priv->aggr_prio_tbl[tid].amsdu == BA_STREAM_NOT_ALLOWED)
+ block_ack_param_set &= ~IEEE80211_ADDBA_PARAM_AMSDU_MASK;
+ block_ack_param_set |= rx_win_size << BLOCKACKPARAM_WINSIZE_POS;
+ add_ba_rsp->block_ack_param_set = cpu_to_le16(block_ack_param_set);
+ win_size = (le16_to_cpu(add_ba_rsp->block_ack_param_set)
+ & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK)
+ >> BLOCKACKPARAM_WINSIZE_POS;
+ cmd_addba_req->block_ack_param_set = cpu_to_le16(block_ack_param_set);
+
+ nxpwifi_11n_create_rx_reorder_tbl(priv, cmd_addba_req->peer_mac_addr,
+ tid, win_size,
+ le16_to_cpu(cmd_addba_req->ssn));
+ return 0;
+}
+
+/* Prepare DELBA command. */
+int nxpwifi_cmd_11n_delba(struct host_cmd_ds_command *cmd, void *data_buf)
+{
+ struct host_cmd_ds_11n_delba *del_ba = &cmd->params.del_ba;
+
+ cmd->command = cpu_to_le16(HOST_CMD_11N_DELBA);
+ cmd->size = cpu_to_le16(sizeof(*del_ba) + S_DS_GEN);
+ memcpy(del_ba, data_buf, sizeof(*del_ba));
+
+ return 0;
+}
+
+/* Decide and perform RX reordering for a packet. */
+int nxpwifi_11n_rx_reorder_pkt(struct nxpwifi_private *priv,
+ u16 seq_num, u16 tid,
+ u8 *ta, u8 pkt_type, void *payload)
+{
+ struct nxpwifi_rx_reorder_tbl *tbl;
+ int prev_start_win, start_win, end_win, win_size;
+ u16 pkt_index;
+ bool init_window_shift = false;
+ int ret = 0;
+
+ tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, ta);
+ if (!tbl) {
+ if (pkt_type != PKT_TYPE_BAR)
+ nxpwifi_11n_dispatch_pkt(priv, payload);
+ return ret;
+ }
+
+ if (pkt_type == PKT_TYPE_AMSDU && !tbl->amsdu) {
+ nxpwifi_11n_dispatch_pkt(priv, payload);
+ return ret;
+ }
+
+ start_win = tbl->start_win;
+ prev_start_win = start_win;
+ win_size = tbl->win_size;
+ end_win = ((start_win + win_size) - 1) & (MAX_TID_VALUE - 1);
+ if (tbl->flags & RXREOR_INIT_WINDOW_SHIFT) {
+ init_window_shift = true;
+ tbl->flags &= ~RXREOR_INIT_WINDOW_SHIFT;
+ }
+
+ if (tbl->flags & RXREOR_FORCE_NO_DROP) {
+ nxpwifi_dbg(priv->adapter, INFO,
+ "RXREOR_FORCE_NO_DROP when HS is activated\n");
+ tbl->flags &= ~RXREOR_FORCE_NO_DROP;
+ } else if (init_window_shift && seq_num < start_win &&
+ seq_num >= tbl->init_win) {
+ nxpwifi_dbg(priv->adapter, INFO,
+ "Sender TID sequence number reset %d->%d for SSN %d\n",
+ start_win, seq_num, tbl->init_win);
+ start_win = seq_num;
+ tbl->start_win = start_win;
+ end_win = ((start_win + win_size) - 1) & (MAX_TID_VALUE - 1);
+ } else {
+ /* Drop packet if seq_num < start_win. */
+ if ((start_win + TWOPOW11) > (MAX_TID_VALUE - 1)) {
+ if (seq_num >= ((start_win + TWOPOW11) &
+ (MAX_TID_VALUE - 1)) &&
+ seq_num < start_win) {
+ ret = -EINVAL;
+ goto done;
+ }
+ } else if ((seq_num < start_win) ||
+ (seq_num >= (start_win + TWOPOW11))) {
+ ret = -EINVAL;
+ goto done;
+ }
+ }
+
+ /* Adjust seq_num for BAR (WinStart = seq_num). */
+ if (pkt_type == PKT_TYPE_BAR)
+ seq_num = ((seq_num + win_size) - 1) & (MAX_TID_VALUE - 1);
+
+ if ((end_win < start_win &&
+ seq_num < start_win && seq_num > end_win) ||
+ (end_win > start_win && (seq_num > end_win ||
+ seq_num < start_win))) {
+ end_win = seq_num;
+ if (((end_win - win_size) + 1) >= 0)
+ start_win = (end_win - win_size) + 1;
+ else
+ start_win = (MAX_TID_VALUE - (win_size - end_win)) + 1;
+ nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, start_win);
+ }
+
+ if (pkt_type != PKT_TYPE_BAR) {
+ if (seq_num >= start_win)
+ pkt_index = seq_num - start_win;
+ else
+ pkt_index = (seq_num + MAX_TID_VALUE) - start_win;
+
+ if (tbl->rx_reorder_ptr[pkt_index]) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ tbl->rx_reorder_ptr[pkt_index] = payload;
+ }
+
+ /* Dispatch sequentially until a hole; update start_win. */
+ nxpwifi_11n_scan_and_dispatch(priv, tbl);
+
+done:
+ if (!tbl->timer_context.timer_is_set ||
+ prev_start_win != tbl->start_win)
+ nxpwifi_11n_rxreorder_timer_restart(tbl);
+ return ret;
+}
+
+/* Delete BA entry for TID/TA. */
+void
+nxpwifi_del_ba_tbl(struct nxpwifi_private *priv, int tid, u8 *peer_mac,
+ u8 type, int initiator)
+{
+ struct nxpwifi_rx_reorder_tbl *tbl;
+ struct nxpwifi_tx_ba_stream_tbl *ptx_tbl;
+ struct nxpwifi_ra_list_tbl *ra_list;
+ u8 cleanup_rx_reorder_tbl;
+ int tid_down;
+
+ if (type == TYPE_DELBA_RECEIVE)
+ cleanup_rx_reorder_tbl = (initiator) ? true : false;
+ else
+ cleanup_rx_reorder_tbl = (initiator) ? false : true;
+
+ nxpwifi_dbg(priv->adapter, EVENT, "event: DELBA: %pM tid=%d initiator=%d\n",
+ peer_mac, tid, initiator);
+
+ if (cleanup_rx_reorder_tbl) {
+ tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, peer_mac);
+ if (!tbl) {
+ nxpwifi_dbg(priv->adapter, EVENT,
+ "event: TID, TA not found in table\n");
+ return;
+ }
+ nxpwifi_del_rx_reorder_entry(priv, tbl);
+ } else {
+ guard(rcu)();
+ ptx_tbl = nxpwifi_get_ba_tbl(priv, tid, peer_mac);
+
+ if (!ptx_tbl) {
+ nxpwifi_dbg(priv->adapter, EVENT,
+ "event: TID, RA not found in table\n");
+ return;
+ }
+
+ tid_down = nxpwifi_wmm_downgrade_tid(priv, tid);
+ ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, peer_mac);
+ if (ra_list) {
+ ra_list->amsdu_in_ampdu = false;
+ ra_list->ba_status = BA_SETUP_NONE;
+ }
+ spin_lock_bh(&priv->tx_ba_stream_tbl_lock[tid]);
+ nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, ptx_tbl);
+ spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[tid]);
+ }
+}
+
+/* Handle ADDBA response. */
+int nxpwifi_ret_11n_addba_resp(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp)
+{
+ struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp;
+ int tid, win_size;
+ struct nxpwifi_rx_reorder_tbl *tbl;
+ u16 block_ack_param_set;
+
+ block_ack_param_set = le16_to_cpu(add_ba_rsp->block_ack_param_set);
+
+ tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK)
+ >> BLOCKACKPARAM_TID_POS;
+ /* Check if we had rejected the ADDBA, if yes then do not create the stream */
+ if (le16_to_cpu(add_ba_rsp->status_code) != BA_RESULT_SUCCESS) {
+ nxpwifi_dbg(priv->adapter, ERROR, "ADDBA RSP: failed %pM tid=%d)\n",
+ add_ba_rsp->peer_mac_addr, tid);
+
+ tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid,
+ add_ba_rsp->peer_mac_addr);
+ if (tbl)
+ nxpwifi_del_rx_reorder_entry(priv, tbl);
+
+ return 0;
+ }
+
+ win_size = (block_ack_param_set & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK)
+ >> BLOCKACKPARAM_WINSIZE_POS;
+
+ tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid,
+ add_ba_rsp->peer_mac_addr);
+ if (tbl) {
+ if ((block_ack_param_set & IEEE80211_ADDBA_PARAM_AMSDU_MASK) &&
+ priv->add_ba_param.rx_amsdu &&
+ priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED)
+ tbl->amsdu = true;
+ else
+ tbl->amsdu = false;
+ }
+
+ nxpwifi_dbg(priv->adapter, CMD,
+ "cmd: ADDBA RSP: %pM tid=%d ssn=%d win_size=%d\n",
+ add_ba_rsp->peer_mac_addr, tid, add_ba_rsp->ssn, win_size);
+
+ return 0;
+}
+
+/* Handle BA stream timeout: send DELBA. */
+void nxpwifi_11n_ba_stream_timeout(struct nxpwifi_private *priv,
+ struct host_cmd_ds_11n_batimeout *event)
+{
+ struct host_cmd_ds_11n_delba delba;
+
+ memset(&delba, 0, sizeof(struct host_cmd_ds_11n_delba));
+ memcpy(delba.peer_mac_addr, event->peer_mac_addr, ETH_ALEN);
+
+ delba.del_ba_param_set |=
+ cpu_to_le16((u16)event->tid << DELBA_TID_POS);
+ delba.del_ba_param_set |=
+ cpu_to_le16((u16)event->origninator << DELBA_INITIATOR_POS);
+ delba.reason_code = cpu_to_le16(WLAN_REASON_QSTA_TIMEOUT);
+ nxpwifi_send_cmd(priv, HOST_CMD_11N_DELBA, 0, 0, &delba, false);
+}
+
+/* Cleanup all RX reorder entries. */
+void nxpwifi_11n_cleanup_reorder_tbl(struct nxpwifi_private *priv)
+{
+ struct nxpwifi_rx_reorder_tbl *del_tbl_ptr, *tmp_node;
+ LIST_HEAD(to_delete_list);
+ int i;
+
+ for (i = 0; i < MAX_NUM_TID; i++) {
+ spin_lock_bh(&priv->rx_reorder_tbl_lock[i]);
+ list_splice_init(&priv->rx_reorder_tbl_ptr[i], &to_delete_list);
+ spin_unlock_bh(&priv->rx_reorder_tbl_lock[i]);
+
+ list_for_each_entry_safe(del_tbl_ptr, tmp_node, &to_delete_list, list)
+ nxpwifi_del_rx_reorder_entry(priv, del_tbl_ptr);
+
+ INIT_LIST_HEAD(&to_delete_list);
+ }
+
+ nxpwifi_reset_11n_rx_seq_num(priv);
+}
+
+/* Update flags for all RX reorder tables. */
+void nxpwifi_update_rxreor_flags(struct nxpwifi_adapter *adapter, u8 flags)
+{
+ struct nxpwifi_private *priv;
+ struct nxpwifi_rx_reorder_tbl *tbl;
+ int i, j;
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+
+ for (j = 0; j < MAX_NUM_TID; j++) {
+ spin_lock_bh(&priv->rx_reorder_tbl_lock[j]);
+ list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[j], list)
+ tbl->flags = flags;
+ spin_unlock_bh(&priv->rx_reorder_tbl_lock[j]);
+ }
+ }
+}
+
+/* Update RX window size based on coex flag. */
+static void nxpwifi_update_ampdu_rxwinsize(struct nxpwifi_adapter *adapter,
+ bool coex_flag)
+{
+ u8 i, j;
+ u32 rx_win_size;
+ struct nxpwifi_private *priv;
+
+ nxpwifi_dbg(adapter, INFO, "Update rxwinsize %d\n", coex_flag);
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+ rx_win_size = priv->add_ba_param.rx_win_size;
+ if (coex_flag) {
+ if (priv->bss_type == NXPWIFI_BSS_TYPE_STA)
+ priv->add_ba_param.rx_win_size =
+ NXPWIFI_STA_COEX_AMPDU_DEF_RXWINSIZE;
+ if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP)
+ priv->add_ba_param.rx_win_size =
+ NXPWIFI_UAP_COEX_AMPDU_DEF_RXWINSIZE;
+ } else {
+ if (priv->bss_type == NXPWIFI_BSS_TYPE_STA)
+ priv->add_ba_param.rx_win_size =
+ NXPWIFI_STA_AMPDU_DEF_RXWINSIZE;
+ if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP)
+ priv->add_ba_param.rx_win_size =
+ NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE;
+ }
+
+ if (adapter->coex_win_size && adapter->coex_rx_win_size)
+ priv->add_ba_param.rx_win_size =
+ adapter->coex_rx_win_size;
+
+ if (rx_win_size != priv->add_ba_param.rx_win_size) {
+ if (!priv->media_connected)
+ continue;
+ for (j = 0; j < MAX_NUM_TID; j++)
+ nxpwifi_11n_delba(priv, j);
+ }
+ }
+}
+
+/* Check coex for RX BA. */
+void nxpwifi_coex_ampdu_rxwinsize(struct nxpwifi_adapter *adapter)
+{
+ u8 i;
+ struct nxpwifi_private *priv;
+ u8 count = 0;
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) {
+ if (priv->media_connected)
+ count++;
+ }
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) {
+ if (priv->bss_started)
+ count++;
+ }
+ if (count >= NXPWIFI_BSS_COEX_COUNT)
+ break;
+ }
+ if (count >= NXPWIFI_BSS_COEX_COUNT)
+ nxpwifi_update_ampdu_rxwinsize(adapter, true);
+ else
+ nxpwifi_update_ampdu_rxwinsize(adapter, false);
+}
+
+/* Handle RXBA sync event. */
+void nxpwifi_11n_rxba_sync_event(struct nxpwifi_private *priv,
+ u8 *event_buf, u16 len)
+{
+ struct nxpwifi_ie_types_rxba_sync *tlv_rxba = (void *)event_buf;
+ u16 tlv_type, tlv_len;
+ struct nxpwifi_rx_reorder_tbl *rx_reor_tbl_ptr;
+ u8 i, j;
+ u16 seq_num, tlv_seq_num, tlv_bitmap_len;
+ int tlv_buf_left = len;
+ int ret;
+ u8 *tmp;
+
+ nxpwifi_dbg_dump(priv->adapter, EVT_D, "RXBA_SYNC event:",
+ event_buf, len);
+ while (tlv_buf_left > sizeof(*tlv_rxba)) {
+ tlv_type = le16_to_cpu(tlv_rxba->header.type);
+ tlv_len = le16_to_cpu(tlv_rxba->header.len);
+ if (size_add(sizeof(tlv_rxba->header), tlv_len) > tlv_buf_left) {
+ nxpwifi_dbg(priv->adapter, WARN,
+ "TLV size (%zu) overflows event_buf buf_left=%d\n",
+ size_add(sizeof(tlv_rxba->header), tlv_len),
+ tlv_buf_left);
+ return;
+ }
+
+ if (tlv_type != TLV_TYPE_RXBA_SYNC) {
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Wrong TLV id=0x%x\n", tlv_type);
+ return;
+ }
+
+ tlv_seq_num = le16_to_cpu(tlv_rxba->seq_num);
+ tlv_bitmap_len = le16_to_cpu(tlv_rxba->bitmap_len);
+ if (size_add(sizeof(*tlv_rxba), tlv_bitmap_len) > tlv_buf_left) {
+ nxpwifi_dbg(priv->adapter, WARN,
+ "TLV size (%zu) overflows event_buf buf_left=%d\n",
+ size_add(sizeof(*tlv_rxba), tlv_bitmap_len),
+ tlv_buf_left);
+ return;
+ }
+
+ nxpwifi_dbg(priv->adapter, INFO,
+ "%pM tid=%d seq_num=%d bitmap_len=%d\n",
+ tlv_rxba->mac, tlv_rxba->tid, tlv_seq_num,
+ tlv_bitmap_len);
+
+ rx_reor_tbl_ptr =
+ nxpwifi_11n_get_rx_reorder_tbl(priv, tlv_rxba->tid,
+ tlv_rxba->mac);
+ if (!rx_reor_tbl_ptr) {
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Can not find rx_reorder_tbl!");
+ return;
+ }
+
+ for (i = 0; i < tlv_bitmap_len; i++) {
+ for (j = 0 ; j < 8; j++) {
+ if (tlv_rxba->bitmap[i] & (1 << j)) {
+ seq_num = (MAX_TID_VALUE - 1) &
+ (tlv_seq_num + i * 8 + j);
+
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "drop packet,seq=%d\n",
+ seq_num);
+
+ ret = nxpwifi_11n_rx_reorder_pkt
+ (priv, seq_num, tlv_rxba->tid,
+ tlv_rxba->mac, 0, NULL);
+
+ if (ret)
+ nxpwifi_dbg(priv->adapter,
+ ERROR,
+ "Fail to drop packet");
+ }
+ }
+ }
+
+ tlv_buf_left -= (sizeof(tlv_rxba->header) + tlv_len);
+ tmp = (u8 *)tlv_rxba + sizeof(tlv_rxba->header) + tlv_len;
+ tlv_rxba = (struct nxpwifi_ie_types_rxba_sync *)tmp;
+ }
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h
new file mode 100644
index 000000000000..db95d9db5d1f
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * NXP Wireless LAN device driver: 802.11n RX Re-ordering
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#ifndef _NXPWIFI_11N_RXREORDER_H_
+#define _NXPWIFI_11N_RXREORDER_H_
+
+#define MIN_FLUSH_TIMER_MS 50
+#define MIN_FLUSH_TIMER_15_MS 15
+#define NXPWIFI_BA_WIN_SIZE_32 32
+
+#define PKT_TYPE_BAR 0xE7
+#define MAX_TID_VALUE (2 << 11)
+#define TWOPOW11 (2 << 10)
+
+#define BLOCKACKPARAM_TID_POS 2
+#define BLOCKACKPARAM_WINSIZE_POS 6
+#define DELBA_TID_POS 12
+#define DELBA_INITIATOR_POS 11
+#define TYPE_DELBA_SENT 1
+#define TYPE_DELBA_RECEIVE 2
+#define IMMEDIATE_BLOCK_ACK 0x2
+
+#define ADDBA_RSP_STATUS_ACCEPT 0
+
+#define NXPWIFI_DEF_11N_RX_SEQ_NUM 0xffff
+#define BA_SETUP_MAX_PACKET_THRESHOLD 16
+#define BA_SETUP_PACKET_OFFSET 16
+
+enum nxpwifi_rxreor_flags {
+ RXREOR_FORCE_NO_DROP = 1 << 0,
+ RXREOR_INIT_WINDOW_SHIFT = 1 << 1,
+};
+
+static inline void nxpwifi_reset_11n_rx_seq_num(struct nxpwifi_private *priv)
+{
+ memset(priv->rx_seq, 0xff, sizeof(priv->rx_seq));
+}
+
+int nxpwifi_11n_rx_reorder_pkt(struct nxpwifi_private *priv,
+ u16 seq_num,
+ u16 tid, u8 *ta,
+ u8 pkttype, void *payload);
+void nxpwifi_del_ba_tbl(struct nxpwifi_private *priv, int tid,
+ u8 *peer_mac, u8 type, int initiator);
+void nxpwifi_11n_ba_stream_timeout(struct nxpwifi_private *priv,
+ struct host_cmd_ds_11n_batimeout *event);
+int nxpwifi_ret_11n_addba_resp(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command
+ *resp);
+int nxpwifi_cmd_11n_delba(struct host_cmd_ds_command *cmd,
+ void *data_buf);
+int nxpwifi_cmd_11n_addba_rsp_gen(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ struct host_cmd_ds_11n_addba_req
+ *cmd_addba_req);
+int nxpwifi_cmd_11n_addba_req(struct host_cmd_ds_command *cmd,
+ void *data_buf);
+void nxpwifi_11n_cleanup_reorder_tbl(struct nxpwifi_private *priv);
+struct nxpwifi_rx_reorder_tbl *
+nxpwifi_11n_get_rxreorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta);
+struct nxpwifi_rx_reorder_tbl *
+nxpwifi_11n_get_rx_reorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta);
+void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta);
+void nxpwifi_update_rxreor_flags(struct nxpwifi_adapter *adapter, u8 flags);
+void nxpwifi_11n_rxba_sync_event(struct nxpwifi_private *priv,
+ u8 *event_buf, u16 len);
+#endif /* _NXPWIFI_11N_RXREORDER_H_ */
--
2.34.1
^ permalink raw reply related
* [PATCH v10 00/21] wifi: nxpwifi: create nxpwifi to support
From: Jeff Chen @ 2026-03-05 14:39 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, johannes, francesco, wyatt.hsu, s.hauer, Jeff Chen
This series adds a new full-MAC Wi-Fi driver `nxpwifi` to support NXP
IW611/IW612 chip family. These chips are tri-radio single-chip solutions
with Wi-Fi 6(1x1, 2.4/5 GHz), Bluetooth 5.4, and IEEE 802.15.4.
Communication with the external host is via SDIO interface. The driver is
tested on i.MX8M Mini EVK in both STA and AP mode.
This driver is not based on mac80211. It derives from mwifiex, but due to
significant differences in firmware architecture, host command interface,
and supported features, it was not feasible to extend mwifiex without risk
of regressions. Thus, a new driver is introduced. Future NXP Wi-Fi chipsets
will also be supported under `nxpwifi`.
The driver passes checkpatch. WPA2/WPA3 personal/enterprise is currently
handled in host via wpa_supplicant/hostapd.
v4 adds support for TWT, monitor mode, WPA3 Enterprise Suite-B, and various
fixes and improvements. See below for full changelog.
Data sheet is available at:
https://www.nxp.com/docs/en/data-sheet/IW612.pdf
Firmware binaries can be obtained from:
https://github.com/nxp-imx/imx-firmware/tree/lf-6.12.34_2.1.0/nxp/FwImage_IW612_SD/
Change history:
v10:
- Removed all remaining OF code, since DT bindings were dropped and
OF usage is not allowed without a corresponding schema.
- Updated all commit subjects to imperative mood and fixed non-imperative
wording in several commit messages.
v9:
SDIO updates (addressing v8 review feedback)
--------------------------------------------
- Dropped mandatory Device Tree matching in SDIO probe. DT parsing is
now optional and no longer affects probe success. All unused OFi
match logic was removed.
- Removed pr_debug() and all probe()/remove() entry/exit prints. SDIO
now stays silent on success, consistent with kernel expectations.
- Removed legacy assignment of MMC_QUIRK_BLKSZ_FOR_BYTE_MODE.
- Dropped deprecated `.owner = THIS_MODULE` from struct sdio_driver.
- Reworked SDIO comments to follow current kernel style guidelines and
removed verbose “This function …” comment blocks.
- Modernized firmware-ready polling using read_poll_timeout().
- Rate-limited intermittent ISR error messages.
- Replaced sprintf/mdelay/memmove with scnprintf/msleep/memcpy.
- Added missing return value checks to sdio_enable_func() and
sdio_set_block_size().
- Removed the dependency on include/linux/mmc/sdio_ids.h.
This ID table belongs to MMC/SDIO subsystem and is handled separately;
nxpwifi no longer requires this header and the update will be
submitted as an MMC patch.
Devicetree bindings note
------------------------
The previous version included a devicetree binding document for
`nxp,iw61x.yaml`. Since Device Tree support for this device is optional
and not required for current SDIO-based bring-up, the binding has been
dropped from this series. A proper schema will be submitted separately
once DT usage becomes relevant, so that binding review can be handled
in the correct subsystem and without blocking this driver introduction.
Initialization path cleanups (aligned with upstream mwifiex)
------------------------------------------------------------
This series ports three upstream mwifiex cleanups which remove obsolete
asynchronous initialization scaffolding:
- Based-on: c2095eb63319 ("wifi: mwifiex: remove mwifiex_sta_init_cmd() last
argument")
Dropped the unused “init” argument from the STA init command helper.
- Based-on: f996f434aa78 ("wifi: mwifiex: drop asynchronous init waiting code")
Removed all asynchronous-init waiting logic (init_wait_q, last_init_cmd,
INIT_DONE transitional state). Initialization now completes strictly when
the last synchronous command returns.
- Based-on: 659d609bdda5 ("wifi: mwifiex: remove unnecessary queue empty check")
Removed the redundant “command queue empty” check after init; retained
only a WARN_ON() for future regression detection.
General cleanups
----------------
- Numerous comment style fixes across SDIO, HE, VHT/HT, and main files.
- Reduced verbosity across the driver to align with upstream expectations.
- Small structural cleanups and dead-code removals.
v8:
- Standardized multi-line comment style across nxpwifi driver files to Linux kernel
convention.
/*
* ...
*/
No functional changes.
- Fixed workqueue cleanup and error path ordering in nxpwifi_add_card(): ensure
proper termination before resource free to avoid race conditions.
- Corrected spelling mistakes in source code and comments for better readability
(e.g., Dimentions -> Dimensions, interfacces -> interfaces).
- Added Device Tree binding schema for NXP IW61x SDIO devices (nxp,iw61x.yaml),
including properties for compatible, reg, interrupts, wakeup pin, and optional
calibration data.
v7:
- Addressed review feedback from v6
- Removed unused variable `ext_rate_info` in `nxpwifi_rxpdinfo_to_radiotapinfo()
- Radiotap handling fixes:
- Converted fields to `__le16`/`__le64`
- Applied `cpu_to_le16()`/`cpu_to_le64()` for endian correctness
- Replaced `jiffies` with `ktime_get_ns()/1000` for timestamp
- Validated channel frequency conversion and removed redundant code
- General cleanup of endian conversions and Sparse warnings
- Improved HE capability setup via `_ieee80211_set_sband_iftype_data()`
- TWT configuration fixes:
- Updated structs to `__le16`/`__le32`
- Corrected debugfs write helpers for endian conversions
- Removed redundant conversions in `nxpwifi_cmd_twt_cfg()`
- Scan handling fixes:
- Changed OUI variables to `__be32`
- Used `le16_to_cpu()` for `ext_cap->header.len`
- Moved element pointer declaration outside `switch`
- HW spec parsing fix:
- Added `le32_to_cpu()` before `GET_MPDU_DENSITY()`
- Cfg80211 handling fix:
- Moved `legacy_rates[]` definition outside `switch` block
v6:
- Removed custom locking (main_locked, main_proc_lock, more_task_flag)
- Refactored main process to rely solely on workqueue
- SDIO interrupt now only queues main_work; avoids direct call to nxpwifi_main_process()
- Introduced atomic iface_changing flag to block main process during interface transitions
- Split monolithic main process logic into helper functions for better readability
- Improved exit logic to avoid lost-kick scenarios and ensure all pending tasks are processed
- Replaced rcu_read_lock()/unlock() pairs with guard(rcu)() for modern kernel style
- Code cleanups based on review feedback
v5:
- Fixed build errors introduced in v4.
v4:
- Added support for TWT (STA mode)
- Added support for Monitor mode
- Added support for WPA3 Enterprise Suite-B
- Bug fix: In BGN/AN HT40 mode, throughput was 50% lower than expected
- Bug fix: In STA 2.4G HE40 mode, throughput was 80% lower than expected
- Use wiphy work instead of general workqueue for cfg80211 ops
- Introduced RCU protection for ba, rx_reorder, and sta_lists
- Used per-TID spinlock for tx_ba / rx_reorder lists to improve
concurrency
- Replaced mutex_lock with wiphy_lock
- Reverted "use tasklet for Rx" (back to workqueue)
- Refactored HE capability handling for better clarity and
maintainability
- Used standard kernel helpers for MAC address handling
- Replaced proprietary nxpwifi_ieee80211 with standard ieee80211_mgmt
structure
- Used u32_get_bits() for VHT capability field access
- Replaced LOW_PRIO_TID with TC_PRIO_BESTEFFORT for skb priority
- Removed static WPA/RSN OUI table; now derived from cipher suite ID
- Removed redundant helper functions: has_vendor_hdr and has_ieee_hdr
v3:
- Enable 802.11ax (Wi-Fi 6) for both AP and STA mode.
- Extend driver version string with hotfix number.
- Remove Rx mlme work.
- Remove all useless check of netif_carrier_ok().
- Merge decl.h to cfg.h.
- Remove unnecessary check for wiphy parameters setting.
- Synchronize following commits from Mwifiex:
wifi: mwifiex: replace open-coded module_sdio_driver()
wifi: mwifiex: Fix interface type change
wifi: mwifiex: Do not return unused priv in mwifiex_get_priv_by_id()
wifi: mwifiex: increase max_num_akm_suites
wifi: mwifiex: duplicate static structs used in driver instances
wifi: mwifiex: keep mwifiex_cfg80211_ops constant
wifi: mwifiex: Fix uninitialized variable in
mwifiex_cfg80211_authenticate()
wifi: mwifiex: remove unnecessary checks for valid priv
wifi: mwifiex: Fix memcpy() field-spanning write warning in
mwifiex_cmd_802_11_scan_ext()
wifi: mwifiex: Use IRQF_NO_AUTOEN flag in request_irq()
v2:
- Rename ioctl.h and sta_ioctl.c to cfg.h and sta_cfg.c.
- Remove useless header file semaphore.h.
- Use static variable for cookie.
- Modify nxpwifi_register to use ERR_PTR, IS_ERR and PTR_ERR.
- Use error number for error code.
- Remove unnecessary private ie definitions.
- Remove mutex async_mutex and related code.
- Remove unnecessary work queue.
- Add the support for PSK SHA256.
- Use tasklet for Rx.
- Remove unused functions.
- Remove compile warning.
Jeff Chen (21):
wifi: nxpwifi: add 802.11n support for STA and AP modes
wifi: nxpwifi: add initial 802.11ac support for STA and AP modes
wifi: nxpwifi: add initial 802.11ax support for STA and AP modes
wifi: nxpwifi: add 802.11h DFS/TPC support for 5 GHz operation
wifi: nxpwifi: add WMM support for QoS-based traffic handling
wifi: nxpwifi: add scan support
wifi: nxpwifi: add join and association support
wifi: nxpwifi: add channel/frequency/power support
wifi: nxpwifi: add configuration support
wifi: nxpwifi: implement cfg80211 ops for STA and AP
wifi: nxpwifi: add firmware command and TLV definitions
wifi: nxpwifi: add command/event handling support
wifi: nxpwifi: add data path support for STA and AP modes
wifi: nxpwifi: add debugfs support
wifi: nxpwifi: add ethtool support for Wake-on-LAN
wifi: nxpwifi: add utility and IE handling support
wifi: nxpwifi: add init and shutdown support
wifi: nxpwifi: add core driver implementation
wifi: nxpwifi: add initial SDIO bus driver support
wifi: nxpwifi: add Kconfig and Makefile entries
wifi: nxpwifi: add MAINTAINERS entry for nxpwifi driver
MAINTAINERS | 7 +
drivers/net/wireless/Kconfig | 1 +
drivers/net/wireless/Makefile | 1 +
drivers/net/wireless/nxp/Kconfig | 17 +
drivers/net/wireless/nxp/Makefile | 3 +
drivers/net/wireless/nxp/nxpwifi/11ac.c | 280 ++
drivers/net/wireless/nxp/nxpwifi/11ac.h | 33 +
drivers/net/wireless/nxp/nxpwifi/11ax.c | 594 +++
drivers/net/wireless/nxp/nxpwifi/11ax.h | 73 +
drivers/net/wireless/nxp/nxpwifi/11h.c | 339 ++
drivers/net/wireless/nxp/nxpwifi/11n.c | 837 ++++
drivers/net/wireless/nxp/nxpwifi/11n.h | 158 +
drivers/net/wireless/nxp/nxpwifi/11n_aggr.c | 251 ++
drivers/net/wireless/nxp/nxpwifi/11n_aggr.h | 21 +
.../net/wireless/nxp/nxpwifi/11n_rxreorder.c | 826 ++++
.../net/wireless/nxp/nxpwifi/11n_rxreorder.h | 71 +
drivers/net/wireless/nxp/nxpwifi/Kconfig | 22 +
drivers/net/wireless/nxp/nxpwifi/Makefile | 39 +
drivers/net/wireless/nxp/nxpwifi/cfg.h | 993 +++++
drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 3962 +++++++++++++++++
drivers/net/wireless/nxp/nxpwifi/cfg80211.h | 19 +
drivers/net/wireless/nxp/nxpwifi/cfp.c | 458 ++
drivers/net/wireless/nxp/nxpwifi/cmdevt.c | 1149 +++++
drivers/net/wireless/nxp/nxpwifi/cmdevt.h | 96 +
drivers/net/wireless/nxp/nxpwifi/debugfs.c | 1094 +++++
drivers/net/wireless/nxp/nxpwifi/ethtool.c | 58 +
drivers/net/wireless/nxp/nxpwifi/fw.h | 2373 ++++++++++
drivers/net/wireless/nxp/nxpwifi/ie.c | 480 ++
drivers/net/wireless/nxp/nxpwifi/init.c | 607 +++
drivers/net/wireless/nxp/nxpwifi/join.c | 788 ++++
drivers/net/wireless/nxp/nxpwifi/main.c | 1673 +++++++
drivers/net/wireless/nxp/nxpwifi/main.h | 1773 ++++++++
drivers/net/wireless/nxp/nxpwifi/scan.c | 2763 ++++++++++++
drivers/net/wireless/nxp/nxpwifi/sdio.c | 2327 ++++++++++
drivers/net/wireless/nxp/nxpwifi/sdio.h | 340 ++
drivers/net/wireless/nxp/nxpwifi/sta_cfg.c | 1165 +++++
drivers/net/wireless/nxp/nxpwifi/sta_cmd.c | 3387 ++++++++++++++
drivers/net/wireless/nxp/nxpwifi/sta_event.c | 862 ++++
drivers/net/wireless/nxp/nxpwifi/sta_rx.c | 242 +
drivers/net/wireless/nxp/nxpwifi/sta_tx.c | 190 +
drivers/net/wireless/nxp/nxpwifi/txrx.c | 352 ++
drivers/net/wireless/nxp/nxpwifi/uap_cmd.c | 1197 +++++
drivers/net/wireless/nxp/nxpwifi/uap_event.c | 488 ++
drivers/net/wireless/nxp/nxpwifi/uap_txrx.c | 478 ++
drivers/net/wireless/nxp/nxpwifi/util.c | 1523 +++++++
drivers/net/wireless/nxp/nxpwifi/util.h | 128 +
drivers/net/wireless/nxp/nxpwifi/wmm.c | 1308 ++++++
drivers/net/wireless/nxp/nxpwifi/wmm.h | 77 +
48 files changed, 35923 insertions(+)
create mode 100644 drivers/net/wireless/nxp/Kconfig
create mode 100644 drivers/net/wireless/nxp/Makefile
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11h.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/Kconfig
create mode 100644 drivers/net/wireless/nxp/nxpwifi/Makefile
create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg80211.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg80211.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfp.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/cmdevt.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/cmdevt.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/debugfs.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/ethtool.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/fw.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/ie.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/init.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/join.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/scan.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_cfg.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_cmd.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_event.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_rx.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_tx.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/txrx.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_cmd.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_event.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_txrx.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/util.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/util.h
create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.h
--
2.34.1
^ permalink raw reply
* Re: [PATCH 10/13] wifi: rt2x00: drop redundant device reference
From: Johan Hovold @ 2026-03-05 14:15 UTC (permalink / raw)
To: Stanislaw Gruszka
Cc: linux-wireless, Brian Norris, Francesco Dolcini, Felix Fietkau,
Lorenzo Bianconi, Ryder Lee, Shayne Chen, Sean Wang,
Jakub Kicinski, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel
In-Reply-To: <20260305133755.GA75655@wp.pl>
On Thu, Mar 05, 2026 at 02:37:55PM +0100, Stanislaw Gruszka wrote:
> On Thu, Mar 05, 2026 at 12:07:10PM +0100, Johan Hovold wrote:
> > Driver core holds a reference to the USB interface and its parent USB
> > device while the interface is bound to a driver and there is no need to
> > take additional references unless the structures are needed after
> > disconnect.
> >
> > Drop the redundant device reference to reduce cargo culting, make it
>
> Getting the reference in probe() and drop it in disconnect() was not a cargo cult.
> That was requirement from usb_get_dev() comment, that later it was changed
> by below commit:
>
> commit f6a9a2d64dd168b7d71076c0e6b2be7db7cb7399
> Author: Alan Stern <stern@rowland.harvard.edu>
> Date: Fri Feb 25 09:38:25 2022 -0500
>
> USB: core: Update kerneldoc for usb_get_dev() and usb_get_intf()
>
> The kerneldoc for usb_get_dev() and usb_get_intf() says that drivers
> should always refcount the references they hold for the usb_device or
> usb_interface structure, respectively. But this is an overstatement:
> In many cases drivers do not access these references after they have
> been unbound, and in such cases refcounting is unnecessary.
>
> This patch updates the kerneldoc for the two routines, explaining when
> a driver does not need to increment and decrement the refcount. This
> should help dispel misconceptions which might otherwise afflict
> programmers new to the USB subsystem.
Yeah, the documentation indeed was misleading for a very long time. That
reference comment even predates the driver model.
Many authors base their work on existing drivers so we still get new
ones that take such references even after the docs were amended.
Johan
^ permalink raw reply
* Re: [PATCH 10/13] wifi: rt2x00: drop redundant device reference
From: Stanislaw Gruszka @ 2026-03-05 13:37 UTC (permalink / raw)
To: Johan Hovold
Cc: linux-wireless, Brian Norris, Francesco Dolcini, Felix Fietkau,
Lorenzo Bianconi, Ryder Lee, Shayne Chen, Sean Wang,
Jakub Kicinski, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel
In-Reply-To: <20260305110713.17725-11-johan@kernel.org>
On Thu, Mar 05, 2026 at 12:07:10PM +0100, Johan Hovold wrote:
> Driver core holds a reference to the USB interface and its parent USB
> device while the interface is bound to a driver and there is no need to
> take additional references unless the structures are needed after
> disconnect.
>
> Drop the redundant device reference to reduce cargo culting, make it
Getting the reference in probe() and drop it in disconnect() was not a cargo cult.
That was requirement from usb_get_dev() comment, that later it was changed
by below commit:
commit f6a9a2d64dd168b7d71076c0e6b2be7db7cb7399
Author: Alan Stern <stern@rowland.harvard.edu>
Date: Fri Feb 25 09:38:25 2022 -0500
USB: core: Update kerneldoc for usb_get_dev() and usb_get_intf()
The kerneldoc for usb_get_dev() and usb_get_intf() says that drivers
should always refcount the references they hold for the usb_device or
usb_interface structure, respectively. But this is an overstatement:
In many cases drivers do not access these references after they have
been unbound, and in such cases refcounting is unnecessary.
This patch updates the kerneldoc for the two routines, explaining when
a driver does not need to increment and decrement the refcount. This
should help dispel misconceptions which might otherwise afflict
programmers new to the USB subsystem.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/Yhjp4Rp9Alipmwtq@rowland.harvard.edu
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Regards
Stanislaw
> easier to spot drivers where an extra reference is needed, and reduce
> the risk of memory leaks when drivers fail to release it.
>
> Signed-off-by: Johan Hovold <johan@kernel.org>
> ---
> drivers/net/wireless/ralink/rt2x00/rt2x00usb.c | 12 +-----------
> 1 file changed, 1 insertion(+), 11 deletions(-)
>
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> index 54599cad78f9..83d00b6baf64 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> @@ -802,14 +802,12 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
> struct rt2x00_dev *rt2x00dev;
> int retval;
>
> - usb_dev = usb_get_dev(usb_dev);
> usb_reset_device(usb_dev);
>
> hw = ieee80211_alloc_hw(sizeof(struct rt2x00_dev), ops->hw);
> if (!hw) {
> rt2x00_probe_err("Failed to allocate hardware\n");
> - retval = -ENOMEM;
> - goto exit_put_device;
> + return -ENOMEM;
> }
>
> usb_set_intfdata(usb_intf, hw);
> @@ -851,10 +849,6 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
>
> exit_free_device:
> ieee80211_free_hw(hw);
> -
> -exit_put_device:
> - usb_put_dev(usb_dev);
> -
> usb_set_intfdata(usb_intf, NULL);
>
> return retval;
> @@ -873,11 +867,7 @@ void rt2x00usb_disconnect(struct usb_interface *usb_intf)
> rt2x00usb_free_reg(rt2x00dev);
> ieee80211_free_hw(hw);
>
> - /*
> - * Free the USB device data.
> - */
> usb_set_intfdata(usb_intf, NULL);
> - usb_put_dev(interface_to_usbdev(usb_intf));
> }
> EXPORT_SYMBOL_GPL(rt2x00usb_disconnect);
>
> --
> 2.52.0
>
^ permalink raw reply
* Re: [PATCH] wifi: ath11k: Add quirk entry for Thinkpad T14s Gen3 AMD
From: Ilya K @ 2026-03-05 12:46 UTC (permalink / raw)
To: Baochen Qiang, tiwai
Cc: ath11k, jeff.johnson, linux-wireless, mpearson-lenovo
In-Reply-To: <c63b4cc0-3157-432c-a83b-c638e8c8d665@oss.qualcomm.com>
On 2026-01-12 05:37, Baochen Qiang wrote:
>
>
> On 1/12/2026 10:35 AM, Baochen Qiang wrote:
>>
>>
>> On 1/11/2026 6:47 PM, Ilya K wrote:
>>> Hey folks,
>>>
>>> Sorry for bumping an old-ish thread, but I've had the same issue on a T13 Gen3 machine as well (model 21CG), and there seems to be no better solution in sight.
>>>
>>> Also, here's another instance of what I'm pretty sure is the same issue: https://lore.kernel.org/ath11k/6268b094-5a40-40d8-8461-9c9b0f9e1ae3@oss.qualcomm.com/T/#t
>>>
>>> Can this get landed at least as a temporary workaround? Also, should I submit the 21CG quirk as a separate patch?
>>
>> I had an off list discussion with Takashi, and based on the logs collected I don't think
>> this is not a suspend/resume issue, or even this is not a STA side issue.
>
> my bad:
>
> s/this is not/this is/
>
>>
>> Before we conclude whether your issue is the same or not, can you please describe your
>> issue in detail? specifically is it an unexpected wakeup or is it reconnection failure
>> after a normal wakeup?
>>
>
I've had a bit of an off list conversation with Baochen about this a while ago, but I just saw https://lore.kernel.org/ath11k/20260214212452.782265-38-sashal@kernel.org/T/#u landed, and I still haven't had time to get more data on the affected machine, but it has been rock solid with the quirk added locally, across updated and different APs. Maybe it makes sense to add the quirk at least for the time being? I'll try to get some debug time in the coming weeks, but free time is hard to come by lately :(
^ permalink raw reply
* [wireless-next:main] BUILD SUCCESS 44d93cf1abb6a85d65c3b4b027c82d44263de6a5
From: kernel test robot @ 2026-03-05 11:37 UTC (permalink / raw)
To: Johannes Berg; +Cc: linux-wireless
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
branch HEAD: 44d93cf1abb6a85d65c3b4b027c82d44263de6a5 wifi: UHR: define DPS/DBE/P-EDCA elements and fix size parsing
elapsed time: 1453m
configs tested: 166
configs skipped: 2
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc allyesconfig gcc-15.2.0
arc randconfig-001-20260305 clang-19
arc randconfig-002-20260305 clang-19
arm allnoconfig clang-23
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm randconfig-001-20260305 clang-19
arm randconfig-002-20260305 clang-19
arm randconfig-003-20260305 clang-19
arm randconfig-004-20260305 clang-19
arm64 allmodconfig clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 randconfig-001-20260305 gcc-8.5.0
arm64 randconfig-002-20260305 gcc-8.5.0
arm64 randconfig-003-20260305 gcc-8.5.0
arm64 randconfig-004-20260305 gcc-8.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky randconfig-001-20260305 gcc-8.5.0
csky randconfig-002-20260305 gcc-8.5.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig clang-23
hexagon allnoconfig gcc-15.2.0
i386 allmodconfig clang-20
i386 allnoconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001-20260305 clang-20
i386 buildonly-randconfig-002-20260305 clang-20
i386 buildonly-randconfig-003-20260305 clang-20
i386 buildonly-randconfig-004-20260305 clang-20
i386 buildonly-randconfig-005-20260305 clang-20
i386 buildonly-randconfig-006-20260305 clang-20
i386 randconfig-001-20260305 gcc-14
i386 randconfig-002-20260305 gcc-14
i386 randconfig-003-20260305 gcc-14
i386 randconfig-004-20260305 gcc-14
i386 randconfig-005-20260305 gcc-14
i386 randconfig-006-20260305 gcc-14
i386 randconfig-007-20260305 gcc-14
i386 randconfig-011-20260305 clang-20
i386 randconfig-012-20260305 clang-20
i386 randconfig-013-20260305 clang-20
i386 randconfig-014-20260305 clang-20
i386 randconfig-015-20260305 clang-20
i386 randconfig-016-20260305 clang-20
i386 randconfig-017-20260305 clang-20
loongarch allmodconfig clang-19
loongarch allmodconfig clang-23
loongarch allnoconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k defconfig clang-19
m68k mvme16x_defconfig gcc-15.2.0
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc allyesconfig gcc-15.2.0
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260305 gcc-9.5.0
parisc randconfig-002-20260305 gcc-9.5.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc randconfig-001-20260305 gcc-9.5.0
powerpc randconfig-002-20260305 gcc-9.5.0
powerpc64 randconfig-001-20260305 gcc-9.5.0
powerpc64 randconfig-002-20260305 gcc-9.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260305 clang-23
riscv randconfig-002-20260305 clang-23
s390 allmodconfig clang-18
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260305 clang-23
s390 randconfig-002-20260305 clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh allyesconfig gcc-15.2.0
sh defconfig gcc-14
sh randconfig-001-20260305 clang-23
sh randconfig-002-20260305 clang-23
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260305 clang-20
x86_64 buildonly-randconfig-002-20260305 clang-20
x86_64 buildonly-randconfig-003-20260305 clang-20
x86_64 buildonly-randconfig-004-20260305 clang-20
x86_64 buildonly-randconfig-005-20260305 clang-20
x86_64 buildonly-randconfig-006-20260305 clang-20
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260305 clang-20
x86_64 randconfig-002-20260305 clang-20
x86_64 randconfig-003-20260305 clang-20
x86_64 randconfig-004-20260305 clang-20
x86_64 randconfig-005-20260305 clang-20
x86_64 randconfig-006-20260305 clang-20
x86_64 randconfig-011-20260305 clang-20
x86_64 randconfig-012-20260305 clang-20
x86_64 randconfig-013-20260305 clang-20
x86_64 randconfig-014-20260305 clang-20
x86_64 randconfig-015-20260305 clang-20
x86_64 randconfig-016-20260305 clang-20
x86_64 randconfig-071-20260305 gcc-14
x86_64 randconfig-072-20260305 gcc-14
x86_64 randconfig-073-20260305 gcc-14
x86_64 randconfig-074-20260305 gcc-14
x86_64 randconfig-075-20260305 gcc-14
x86_64 randconfig-076-20260305 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH 11/13] wifi: rtl818x: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c | 4 ----
1 file changed, 4 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c
index f7e0f6573180..1d21c468a236 100644
--- a/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c
+++ b/drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c
@@ -1475,8 +1475,6 @@ static int rtl8187_probe(struct usb_interface *intf,
usb_set_intfdata(intf, dev);
priv->udev = udev;
- usb_get_dev(udev);
-
skb_queue_head_init(&priv->rx_queue);
BUILD_BUG_ON(sizeof(priv->channels) != sizeof(rtl818x_channels));
@@ -1663,7 +1661,6 @@ static int rtl8187_probe(struct usb_interface *intf,
err_free_dmabuf:
kfree(priv->io_dmabuf);
usb_set_intfdata(intf, NULL);
- usb_put_dev(udev);
err_free_dev:
ieee80211_free_hw(dev);
return err;
@@ -1685,7 +1682,6 @@ static void rtl8187_disconnect(struct usb_interface *intf)
priv = dev->priv;
usb_reset_device(priv->udev);
- usb_put_dev(interface_to_usbdev(intf));
kfree(priv->io_dmabuf);
ieee80211_free_hw(dev);
}
--
2.52.0
^ permalink raw reply related
* [PATCH 07/13] wifi: mt76x2u: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/mediatek/mt76/mt76x2/usb.c | 4 ----
1 file changed, 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c b/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c
index 01cb3b2830f3..8af360bca643 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c
@@ -57,7 +57,6 @@ static int mt76x2u_probe(struct usb_interface *intf,
dev = container_of(mdev, struct mt76x02_dev, mt76);
- udev = usb_get_dev(udev);
usb_reset_device(udev);
usb_set_intfdata(intf, dev);
@@ -84,14 +83,12 @@ static int mt76x2u_probe(struct usb_interface *intf,
mt76u_queues_deinit(&dev->mt76);
mt76_free_device(&dev->mt76);
usb_set_intfdata(intf, NULL);
- usb_put_dev(udev);
return err;
}
static void mt76x2u_disconnect(struct usb_interface *intf)
{
- struct usb_device *udev = interface_to_usbdev(intf);
struct mt76x02_dev *dev = usb_get_intfdata(intf);
struct ieee80211_hw *hw = mt76_hw(dev);
@@ -100,7 +97,6 @@ static void mt76x2u_disconnect(struct usb_interface *intf)
mt76x2u_cleanup(dev);
mt76_free_device(&dev->mt76);
usb_set_intfdata(intf, NULL);
- usb_put_dev(udev);
}
static int __maybe_unused mt76x2u_suspend(struct usb_interface *intf,
--
2.52.0
^ permalink raw reply related
* [PATCH 06/13] wifi: mt76x0u: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/mediatek/mt76/mt76x0/usb.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x0/usb.c b/drivers/net/wireless/mediatek/mt76/mt76x0/usb.c
index 90e5666c0857..2acce121fb14 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x0/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x0/usb.c
@@ -245,7 +245,6 @@ static int mt76x0u_probe(struct usb_interface *usb_intf,
if (id->driver_info)
dev->no_2ghz = true;
- usb_dev = usb_get_dev(usb_dev);
usb_reset_device(usb_dev);
usb_set_intfdata(usb_intf, dev);
@@ -284,7 +283,6 @@ static int mt76x0u_probe(struct usb_interface *usb_intf,
err:
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
mt76u_queues_deinit(&dev->mt76);
mt76_free_device(&dev->mt76);
@@ -303,7 +301,6 @@ static void mt76x0_disconnect(struct usb_interface *usb_intf)
mt76x0u_cleanup(dev);
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
mt76_free_device(&dev->mt76);
}
--
2.52.0
^ permalink raw reply related
* [PATCH 13/13] wifi: rtlwifi: usb: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/realtek/rtlwifi/usb.c | 4 ----
1 file changed, 4 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtlwifi/usb.c b/drivers/net/wireless/realtek/rtlwifi/usb.c
index d35ed56d6db9..9a64df9eed39 100644
--- a/drivers/net/wireless/realtek/rtlwifi/usb.c
+++ b/drivers/net/wireless/realtek/rtlwifi/usb.c
@@ -986,7 +986,6 @@ int rtl_usb_probe(struct usb_interface *intf,
init_completion(&rtlpriv->firmware_loading_complete);
SET_IEEE80211_DEV(hw, &intf->dev);
udev = interface_to_usbdev(intf);
- usb_get_dev(udev);
usb_priv = rtl_usbpriv(hw);
memset(usb_priv, 0, sizeof(*usb_priv));
usb_priv->dev.intf = intf;
@@ -1038,7 +1037,6 @@ int rtl_usb_probe(struct usb_interface *intf,
rtl_deinit_core(hw);
error_out2:
_rtl_usb_io_handler_release(hw);
- usb_put_dev(udev);
kfree(rtlpriv->usb_data);
ieee80211_free_hw(hw);
return -ENODEV;
@@ -1050,7 +1048,6 @@ void rtl_usb_disconnect(struct usb_interface *intf)
struct ieee80211_hw *hw = usb_get_intfdata(intf);
struct rtl_priv *rtlpriv = rtl_priv(hw);
struct rtl_mac *rtlmac = rtl_mac(rtl_priv(hw));
- struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw));
if (unlikely(!rtlpriv))
return;
@@ -1072,7 +1069,6 @@ void rtl_usb_disconnect(struct usb_interface *intf)
kfree(rtlpriv->usb_data);
rtlpriv->cfg->ops->deinit_sw_vars(hw);
_rtl_usb_io_handler_release(hw);
- usb_put_dev(rtlusb->udev);
usb_set_intfdata(intf, NULL);
ieee80211_free_hw(hw);
}
--
2.52.0
^ permalink raw reply related
* [PATCH 09/13] wifi: mt7601u: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/mediatek/mt7601u/usb.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt7601u/usb.c b/drivers/net/wireless/mediatek/mt7601u/usb.c
index c41ae251cb95..9306870cbc91 100644
--- a/drivers/net/wireless/mediatek/mt7601u/usb.c
+++ b/drivers/net/wireless/mediatek/mt7601u/usb.c
@@ -274,7 +274,6 @@ static int mt7601u_probe(struct usb_interface *usb_intf,
if (!dev)
return -ENOMEM;
- usb_dev = usb_get_dev(usb_dev);
usb_reset_device(usb_dev);
usb_set_intfdata(usb_intf, dev);
@@ -319,7 +318,6 @@ static int mt7601u_probe(struct usb_interface *usb_intf,
mt7601u_cleanup(dev);
err:
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
destroy_workqueue(dev->stat_wq);
ieee80211_free_hw(dev->hw);
@@ -334,7 +332,6 @@ static void mt7601u_disconnect(struct usb_interface *usb_intf)
mt7601u_cleanup(dev);
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
destroy_workqueue(dev->stat_wq);
ieee80211_free_hw(dev->hw);
--
2.52.0
^ permalink raw reply related
* [PATCH 10/13] wifi: rt2x00: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/ralink/rt2x00/rt2x00usb.c | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
index 54599cad78f9..83d00b6baf64 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
@@ -802,14 +802,12 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
struct rt2x00_dev *rt2x00dev;
int retval;
- usb_dev = usb_get_dev(usb_dev);
usb_reset_device(usb_dev);
hw = ieee80211_alloc_hw(sizeof(struct rt2x00_dev), ops->hw);
if (!hw) {
rt2x00_probe_err("Failed to allocate hardware\n");
- retval = -ENOMEM;
- goto exit_put_device;
+ return -ENOMEM;
}
usb_set_intfdata(usb_intf, hw);
@@ -851,10 +849,6 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
exit_free_device:
ieee80211_free_hw(hw);
-
-exit_put_device:
- usb_put_dev(usb_dev);
-
usb_set_intfdata(usb_intf, NULL);
return retval;
@@ -873,11 +867,7 @@ void rt2x00usb_disconnect(struct usb_interface *usb_intf)
rt2x00usb_free_reg(rt2x00dev);
ieee80211_free_hw(hw);
- /*
- * Free the USB device data.
- */
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
}
EXPORT_SYMBOL_GPL(rt2x00usb_disconnect);
--
2.52.0
^ permalink raw reply related
* [PATCH 12/13] wifi: rtl8xxxu: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/realtek/rtl8xxxu/core.c | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c
index 794187d28caa..f131ca09ae43 100644
--- a/drivers/net/wireless/realtek/rtl8xxxu/core.c
+++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c
@@ -7698,7 +7698,7 @@ static int rtl8xxxu_probe(struct usb_interface *interface,
int ret;
int untested = 1;
- udev = usb_get_dev(interface_to_usbdev(interface));
+ udev = interface_to_usbdev(interface);
switch (id->idVendor) {
case USB_VENDOR_ID_REALTEK:
@@ -7756,10 +7756,8 @@ static int rtl8xxxu_probe(struct usb_interface *interface,
}
hw = ieee80211_alloc_hw(sizeof(struct rtl8xxxu_priv), &rtl8xxxu_ops);
- if (!hw) {
- ret = -ENOMEM;
- goto err_put_dev;
- }
+ if (!hw)
+ return -ENOMEM;
priv = hw->priv;
priv->hw = hw;
@@ -7901,8 +7899,6 @@ static int rtl8xxxu_probe(struct usb_interface *interface,
mutex_destroy(&priv->h2c_mutex);
ieee80211_free_hw(hw);
-err_put_dev:
- usb_put_dev(udev);
return ret;
}
@@ -7935,7 +7931,6 @@ static void rtl8xxxu_disconnect(struct usb_interface *interface)
"Device still attached, trying to reset\n");
usb_reset_device(priv->udev);
}
- usb_put_dev(priv->udev);
ieee80211_free_hw(hw);
}
--
2.52.0
^ permalink raw reply related
* [PATCH 08/13] wifi: mt76: mt792xu: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/mediatek/mt76/mt7921/usb.c | 2 --
drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 2 --
drivers/net/wireless/mediatek/mt76/mt792x_usb.c | 1 -
3 files changed, 5 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
index 17057e68bf21..9bfc234f306f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
@@ -197,7 +197,6 @@ static int mt7921u_probe(struct usb_interface *usb_intf,
dev->fw_features = features;
dev->hif_ops = &hif_ops;
- udev = usb_get_dev(udev);
usb_reset_device(udev);
usb_set_intfdata(usb_intf, dev);
@@ -246,7 +245,6 @@ static int mt7921u_probe(struct usb_interface *usb_intf,
mt76u_queues_deinit(&dev->mt76);
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
mt76_free_device(&dev->mt76);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
index d9968f03856d..84bcebbf009a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
@@ -185,7 +185,6 @@ static int mt7925u_probe(struct usb_interface *usb_intf,
dev->fw_features = features;
dev->hif_ops = &hif_ops;
- udev = usb_get_dev(udev);
usb_reset_device(udev);
usb_set_intfdata(usb_intf, dev);
@@ -234,7 +233,6 @@ static int mt7925u_probe(struct usb_interface *usb_intf,
mt76u_queues_deinit(&dev->mt76);
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
mt76_free_device(&dev->mt76);
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
index 552808458138..f827e8a56a18 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
@@ -306,7 +306,6 @@ void mt792xu_disconnect(struct usb_interface *usb_intf)
mt792xu_cleanup(dev);
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
mt76_free_device(&dev->mt76);
}
--
2.52.0
^ permalink raw reply related
* [PATCH 05/13] wifi: mt76: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/mediatek/mt76/mt7615/usb.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/usb.c b/drivers/net/wireless/mediatek/mt76/mt7615/usb.c
index d91feffadda9..bab7b91f14be 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/usb.c
@@ -151,7 +151,6 @@ static int mt7663u_probe(struct usb_interface *usb_intf,
return -ENOMEM;
dev = container_of(mdev, struct mt7615_dev, mt76);
- udev = usb_get_dev(udev);
usb_reset_device(udev);
usb_set_intfdata(usb_intf, dev);
@@ -193,7 +192,6 @@ static int mt7663u_probe(struct usb_interface *usb_intf,
error:
mt76u_queues_deinit(&dev->mt76);
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
mt76_free_device(&dev->mt76);
@@ -211,7 +209,6 @@ static void mt7663u_disconnect(struct usb_interface *usb_intf)
mt7663u_cleanup(dev);
usb_set_intfdata(usb_intf, NULL);
- usb_put_dev(interface_to_usbdev(usb_intf));
mt76_free_device(&dev->mt76);
}
--
2.52.0
^ permalink raw reply related
* [PATCH 04/13] wifi: mwifiex: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/marvell/mwifiex/usb.c | 4 ----
1 file changed, 4 deletions(-)
diff --git a/drivers/net/wireless/marvell/mwifiex/usb.c b/drivers/net/wireless/marvell/mwifiex/usb.c
index 947ecb0a7b40..f4b94a1054f6 100644
--- a/drivers/net/wireless/marvell/mwifiex/usb.c
+++ b/drivers/net/wireless/marvell/mwifiex/usb.c
@@ -520,8 +520,6 @@ static int mwifiex_usb_probe(struct usb_interface *intf,
return ret;
}
- usb_get_dev(udev);
-
return 0;
}
@@ -666,8 +664,6 @@ static void mwifiex_usb_disconnect(struct usb_interface *intf)
mwifiex_dbg(adapter, FATAL,
"%s: removing card\n", __func__);
mwifiex_remove_card(adapter);
-
- usb_put_dev(interface_to_usbdev(intf));
}
static void mwifiex_usb_coredump(struct device *dev)
--
2.52.0
^ permalink raw reply related
* [PATCH 02/13] wifi: libertas: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/marvell/libertas/if_usb.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/net/wireless/marvell/libertas/if_usb.c b/drivers/net/wireless/marvell/libertas/if_usb.c
index 8a6bf1365cfa..05fcf9cc28fa 100644
--- a/drivers/net/wireless/marvell/libertas/if_usb.c
+++ b/drivers/net/wireless/marvell/libertas/if_usb.c
@@ -276,7 +276,6 @@ static int if_usb_probe(struct usb_interface *intf,
cardp->boot2_version = udev->descriptor.bcdDevice;
- usb_get_dev(udev);
usb_set_intfdata(intf, cardp);
r = lbs_get_firmware_async(priv, &udev->dev, cardp->model,
@@ -287,7 +286,6 @@ static int if_usb_probe(struct usb_interface *intf,
return 0;
err_get_fw:
- usb_put_dev(udev);
lbs_remove_card(priv);
err_add_card:
if_usb_reset_device(cardp);
@@ -321,7 +319,6 @@ static void if_usb_disconnect(struct usb_interface *intf)
kfree(cardp);
usb_set_intfdata(intf, NULL);
- usb_put_dev(interface_to_usbdev(intf));
}
/**
--
2.52.0
^ permalink raw reply related
* [PATCH 01/13] wifi: at76c50x: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/atmel/at76c50x-usb.c | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/atmel/at76c50x-usb.c b/drivers/net/wireless/atmel/at76c50x-usb.c
index 6445332801a4..44b04ea3cc8b 100644
--- a/drivers/net/wireless/atmel/at76c50x-usb.c
+++ b/drivers/net/wireless/atmel/at76c50x-usb.c
@@ -2440,13 +2440,11 @@ static int at76_probe(struct usb_interface *interface,
struct mib_fw_version *fwv;
int board_type = (int)id->driver_info;
- udev = usb_get_dev(interface_to_usbdev(interface));
+ udev = interface_to_usbdev(interface);
fwv = kmalloc_obj(*fwv);
- if (!fwv) {
- ret = -ENOMEM;
- goto exit;
- }
+ if (!fwv)
+ return -ENOMEM;
/* Load firmware into kernel memory */
fwe = at76_load_firmware(udev, board_type);
@@ -2534,8 +2532,7 @@ static int at76_probe(struct usb_interface *interface,
exit:
kfree(fwv);
- if (ret < 0)
- usb_put_dev(udev);
+
return ret;
}
@@ -2552,7 +2549,6 @@ static void at76_disconnect(struct usb_interface *interface)
wiphy_info(priv->hw->wiphy, "disconnecting\n");
at76_delete_device(priv);
- usb_put_dev(interface_to_usbdev(interface));
dev_info(&interface->dev, "disconnected\n");
}
--
2.52.0
^ permalink raw reply related
* [PATCH 00/13] wifi: drop redundant USB device references
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop redundant device references to reduce cargo culting, make it easier
to spot drivers where an extra reference is needed, and reduce the risk
of memory leaks when drivers fail to release them.
Note that I sent an ath9k patch separately here:
https://lore.kernel.org/all/20260305105803.17011-1-johan@kernel.org/
but I guess these can all be picked up by Johannes.
Johan
Johan Hovold (13):
wifi: at76c50x: drop redundant device reference
wifi: libertas: drop redundant device reference
wifi: libertas_tf: drop redundant device reference
wifi: mwifiex: drop redundant device reference
wifi: mt76: drop redundant device reference
wifi: mt76x0u: drop redundant device reference
wifi: mt76x2u: drop redundant device reference
wifi: mt76: mt792xu: drop redundant device reference
wifi: mt7601u: drop redundant device reference
wifi: rt2x00: drop redundant device reference
wifi: rtl818x: drop redundant device reference
wifi: rtl8xxxu: drop redundant device reference
wifi: rtlwifi: usb: drop redundant device reference
drivers/net/wireless/atmel/at76c50x-usb.c | 12 ++++--------
drivers/net/wireless/marvell/libertas/if_usb.c | 3 ---
drivers/net/wireless/marvell/libertas_tf/if_usb.c | 2 --
drivers/net/wireless/marvell/mwifiex/usb.c | 4 ----
drivers/net/wireless/mediatek/mt76/mt7615/usb.c | 3 ---
drivers/net/wireless/mediatek/mt76/mt76x0/usb.c | 3 ---
drivers/net/wireless/mediatek/mt76/mt76x2/usb.c | 4 ----
drivers/net/wireless/mediatek/mt76/mt7921/usb.c | 2 --
drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 2 --
drivers/net/wireless/mediatek/mt76/mt792x_usb.c | 1 -
drivers/net/wireless/mediatek/mt7601u/usb.c | 3 ---
drivers/net/wireless/ralink/rt2x00/rt2x00usb.c | 12 +-----------
drivers/net/wireless/realtek/rtl818x/rtl8187/dev.c | 4 ----
drivers/net/wireless/realtek/rtl8xxxu/core.c | 11 +++--------
drivers/net/wireless/realtek/rtlwifi/usb.c | 4 ----
15 files changed, 8 insertions(+), 62 deletions(-)
--
2.52.0
^ permalink raw reply
* [PATCH 03/13] wifi: libertas_tf: drop redundant device reference
From: Johan Hovold @ 2026-03-05 11:07 UTC (permalink / raw)
To: linux-wireless
Cc: Brian Norris, Francesco Dolcini, Felix Fietkau, Lorenzo Bianconi,
Ryder Lee, Shayne Chen, Sean Wang, Jakub Kicinski,
Stanislaw Gruszka, Hin-Tak Leung, Jes Sorensen, Ping-Ke Shih,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Matthias Brugger, AngeloGioacchino Del Regno, libertas-dev,
linux-kernel, Johan Hovold
In-Reply-To: <20260305110713.17725-1-johan@kernel.org>
Driver core holds a reference to the USB interface and its parent USB
device while the interface is bound to a driver and there is no need to
take additional references unless the structures are needed after
disconnect.
Drop the redundant device reference to reduce cargo culting, make it
easier to spot drivers where an extra reference is needed, and reduce
the risk of memory leaks when drivers fail to release it.
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/marvell/libertas_tf/if_usb.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/drivers/net/wireless/marvell/libertas_tf/if_usb.c b/drivers/net/wireless/marvell/libertas_tf/if_usb.c
index f49151c18b79..07b38f2b8f58 100644
--- a/drivers/net/wireless/marvell/libertas_tf/if_usb.c
+++ b/drivers/net/wireless/marvell/libertas_tf/if_usb.c
@@ -223,7 +223,6 @@ static int if_usb_probe(struct usb_interface *intf,
if (!priv)
goto dealloc;
- usb_get_dev(udev);
usb_set_intfdata(intf, cardp);
return 0;
@@ -258,7 +257,6 @@ static void if_usb_disconnect(struct usb_interface *intf)
kfree(cardp);
usb_set_intfdata(intf, NULL);
- usb_put_dev(interface_to_usbdev(intf));
lbtf_deb_leave(LBTF_DEB_MAIN);
}
--
2.52.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox