wifi: nxp: add nxpwifi driver for IW61x

Add support for the NXP IW61x wireless devices.

The nxpwifi driver implements a full-MAC design and integrates with
cfg80211 for configuration and control, supporting both station (STA)
and access point (AP) modes.

The driver provides a firmware-based command/event interface using TLV
messages, with the core handling command processing, event dispatching,
and device lifecycle management. A SDIO transport layer is implemented
to support IW61x devices.

Key features include:
- 802.11n/ac/ax (HT/VHT/HE) capability support
- Scan, association, and connection management
- Data path handling for TX/RX, including aggregation and reorder
- WMM QoS support and traffic prioritization
- 802.11h (DFS/TPC) support for regulatory compliance
- cfg80211 integration for STA and AP operations
- Debugfs and ethtool support
- Wake-on-LAN support

The driver translates cfg80211 configuration into firmware commands
and implements required data path processing in software where needed.

Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
This commit is contained in:
Jeff Chen
2026-06-05 10:33:35 +08:00
parent dc4d1a7615
commit 73b01e57ed
48 changed files with 35763 additions and 0 deletions

View File

@@ -19536,6 +19536,13 @@ S: Maintained
F: Documentation/devicetree/bindings/ptp/nxp,ptp-netc.yaml
F: drivers/ptp/ptp_netc.c
NXP NXPWIFI WIRELESS DRIVER
M: Jeff Chen <jeff.chen_1@nxp.com>
R: Francesco Dolcini <francesco@dolcini.it>
L: linux-wireless@vger.kernel.org
S: Maintained
F: drivers/net/wireless/nxp/nxpwifi
NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER
M: Woodrow Douglass <wdouglass@carnegierobotics.com>
S: Maintained

View File

@@ -27,6 +27,7 @@ source "drivers/net/wireless/intersil/Kconfig"
source "drivers/net/wireless/marvell/Kconfig"
source "drivers/net/wireless/mediatek/Kconfig"
source "drivers/net/wireless/microchip/Kconfig"
source "drivers/net/wireless/nxp/Kconfig"
source "drivers/net/wireless/purelifi/Kconfig"
source "drivers/net/wireless/ralink/Kconfig"
source "drivers/net/wireless/realtek/Kconfig"

View File

@@ -12,6 +12,7 @@ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/
obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/
obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/
obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/
obj-$(CONFIG_WLAN_VENDOR_NXP) += nxp/
obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/
obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/
obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/

View File

@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
config WLAN_VENDOR_NXP
bool "NXP devices"
default y
help
If you have a wireless card belonging to this class, say Y.
Note that the answer to this question doesn't directly affect the
kernel: saying N will just cause the configurator to skip all the
questions about these cards. If you say Y, you will be asked for
your specific card in the following questions.
if WLAN_VENDOR_NXP
source "drivers/net/wireless/nxp/nxpwifi/Kconfig"
endif # WLAN_VENDOR_NXP

View File

@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_NXPWIFI) += nxpwifi/

View File

@@ -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;
}
}

View File

@@ -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_ */

View File

@@ -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;
}

View File

@@ -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_ */

View File

@@ -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;
}
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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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_ */

View File

@@ -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;
}

View File

@@ -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_ */

View File

@@ -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;
}
}

View File

@@ -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_ */

View File

@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0-only
config NXPWIFI
tristate "NXP WiFi Driver"
depends on CFG80211
help
This adds support for wireless adapters based on NXP
802.11n/ac chipsets.
If you choose to build it as a module, it will be called
nxpwifi.
config NXPWIFI_SDIO
tristate "NXP WiFi Driver for IW61x"
depends on NXPWIFI && MMC
select FW_LOADER
select WANT_DEV_COREDUMP
help
This adds support for wireless adapters based on NXP
IW61x interface.
If you choose to build it as a module, it will be called
nxpwifi_sdio.

View File

@@ -0,0 +1,39 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright 2011-2020 NXP
#
nxpwifi-y += main.o
nxpwifi-y += init.o
nxpwifi-y += cfp.o
nxpwifi-y += cmdevt.o
nxpwifi-y += util.o
nxpwifi-y += txrx.o
nxpwifi-y += wmm.o
nxpwifi-y += 11n.o
nxpwifi-y += 11ac.o
nxpwifi-y += 11ax.o
nxpwifi-y += 11n_aggr.o
nxpwifi-y += 11n_rxreorder.o
nxpwifi-y += scan.o
nxpwifi-y += join.o
nxpwifi-y += sta_cfg.o
nxpwifi-y += sta_cmd.o
nxpwifi-y += uap_cmd.o
nxpwifi-y += ie.o
nxpwifi-y += sta_event.o
nxpwifi-y += uap_event.o
nxpwifi-y += sta_tx.o
nxpwifi-y += sta_rx.o
nxpwifi-y += uap_txrx.o
nxpwifi-y += cfg80211.o
nxpwifi-y += ethtool.o
nxpwifi-y += 11h.o
nxpwifi-$(CONFIG_DEBUG_FS) += debugfs.o
obj-$(CONFIG_NXPWIFI) += nxpwifi.o
nxpwifi_sdio-y += sdio.o
obj-$(CONFIG_NXPWIFI_SDIO) += nxpwifi_sdio.o
ccflags-y += -D__CHECK_ENDIAN

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* nxpwifi: cfg80211 support
*
* Copyright 2011-2024 NXP
*/
#ifndef __NXPWIFI_CFG80211__
#define __NXPWIFI_CFG80211__
#include "main.h"
int nxpwifi_register_cfg80211(struct nxpwifi_adapter *adapter);
int nxpwifi_cfg80211_change_beacon(struct wiphy *wiphy,
struct net_device *dev,
struct cfg80211_ap_update *params);
#endif

View File

@@ -0,0 +1,478 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* nxpwifi: Channel, Frequency and Power
*
* Copyright 2011-2024 NXP
*/
#include "cfg.h"
#include "util.h"
#include "fw.h"
#include "main.h"
#include "cfg80211.h"
/* 100mW */
#define NXPWIFI_TX_PWR_DEFAULT 20
/* 100mW */
#define NXPWIFI_TX_PWR_US_DEFAULT 20
/* 50mW */
#define NXPWIFI_TX_PWR_JP_DEFAULT 16
/* 100mW */
#define NXPWIFI_TX_PWR_FR_100MW 20
/* 10mW */
#define NXPWIFI_TX_PWR_FR_10MW 10
/* 100mW */
#define NXPWIFI_TX_PWR_EMEA_DEFAULT 20
static u8 supported_rates_a[A_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24,
0xb0, 0x48, 0x60, 0x6c, 0 };
static u16 nxpwifi_data_rates[NXPWIFI_SUPPORTED_RATES_EXT] = { 0x02, 0x04,
0x0B, 0x16, 0x00, 0x0C, 0x12, 0x18,
0x24, 0x30, 0x48, 0x60, 0x6C, 0x90,
0x0D, 0x1A, 0x27, 0x34, 0x4E, 0x68,
0x75, 0x82, 0x0C, 0x1B, 0x36, 0x51,
0x6C, 0xA2, 0xD8, 0xF3, 0x10E, 0x00 };
static u8 supported_rates_b[B_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x16, 0 };
static u8 supported_rates_g[G_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24,
0x30, 0x48, 0x60, 0x6c, 0 };
static u8 supported_rates_bg[BG_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x0c,
0x12, 0x16, 0x18, 0x24, 0x30, 0x48,
0x60, 0x6c, 0 };
/* mcs_rate: first 8 entries for 1x1; all 16 for 2x2. */
static const u16 mcs_rate[4][16] = {
/* LGI 40M */
{ 0x1b, 0x36, 0x51, 0x6c, 0xa2, 0xd8, 0xf3, 0x10e,
0x36, 0x6c, 0xa2, 0xd8, 0x144, 0x1b0, 0x1e6, 0x21c },
/* SGI 40M */
{ 0x1e, 0x3c, 0x5a, 0x78, 0xb4, 0xf0, 0x10e, 0x12c,
0x3c, 0x78, 0xb4, 0xf0, 0x168, 0x1e0, 0x21c, 0x258 },
/* LGI 20M */
{ 0x0d, 0x1a, 0x27, 0x34, 0x4e, 0x68, 0x75, 0x82,
0x1a, 0x34, 0x4e, 0x68, 0x9c, 0xd0, 0xea, 0x104 },
/* SGI 20M */
{ 0x0e, 0x1c, 0x2b, 0x39, 0x56, 0x73, 0x82, 0x90,
0x1c, 0x39, 0x56, 0x73, 0xad, 0xe7, 0x104, 0x120 }
};
/* AC rates */
static const u16 ac_mcs_rate_nss1[8][10] = {
/* LG 160M */
{ 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D,
0x492, 0x57C, 0x618 },
/* SG 160M */
{ 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492,
0x514, 0x618, 0x6C6 },
/* LG 80M */
{ 0x3B, 0x75, 0xB0, 0xEA, 0x15F, 0x1D4, 0x20F,
0x249, 0x2BE, 0x30C },
/* SG 80M */
{ 0x41, 0x82, 0xC3, 0x104, 0x186, 0x208, 0x249,
0x28A, 0x30C, 0x363 },
/* LG 40M */
{ 0x1B, 0x36, 0x51, 0x6C, 0xA2, 0xD8, 0xF3,
0x10E, 0x144, 0x168 },
/* SG 40M */
{ 0x1E, 0x3C, 0x5A, 0x78, 0xB4, 0xF0, 0x10E,
0x12C, 0x168, 0x190 },
/* LG 20M */
{ 0xD, 0x1A, 0x27, 0x34, 0x4E, 0x68, 0x75, 0x82, 0x9C, 0x00 },
/* SG 20M */
{ 0xF, 0x1D, 0x2C, 0x3A, 0x57, 0x74, 0x82, 0x91, 0xAE, 0x00 },
};
/* NSS2 note: the value in the table is 2 multiplier of the actual rate */
static const u16 ac_mcs_rate_nss2[8][10] = {
/* LG 160M */
{ 0xEA, 0x1D4, 0x2BE, 0x3A8, 0x57C, 0x750, 0x83A,
0x924, 0xAF8, 0xC30 },
/* SG 160M */
{ 0x104, 0x208, 0x30C, 0x410, 0x618, 0x820, 0x924,
0xA28, 0xC30, 0xD8B },
/* LG 80M */
{ 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D,
0x492, 0x57C, 0x618 },
/* SG 80M */
{ 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492,
0x514, 0x618, 0x6C6 },
/* LG 40M */
{ 0x36, 0x6C, 0xA2, 0xD8, 0x144, 0x1B0, 0x1E6,
0x21C, 0x288, 0x2D0 },
/* SG 40M */
{ 0x3C, 0x78, 0xB4, 0xF0, 0x168, 0x1E0, 0x21C,
0x258, 0x2D0, 0x320 },
/* LG 20M */
{ 0x1A, 0x34, 0x4A, 0x68, 0x9C, 0xD0, 0xEA, 0x104,
0x138, 0x00 },
/* SG 20M */
{ 0x1D, 0x3A, 0x57, 0x74, 0xAE, 0xE6, 0x104, 0x121,
0x15B, 0x00 },
};
struct region_code_mapping {
u8 code;
u8 region[IEEE80211_COUNTRY_STRING_LEN];
};
static struct region_code_mapping region_code_mapping_t[] = {
{ 0x10, "US " }, /* US FCC */
{ 0x20, "CA " }, /* IC Canada */
{ 0x30, "FR " }, /* France */
{ 0x31, "ES " }, /* Spain */
{ 0x32, "FR " }, /* France */
{ 0x40, "JP " }, /* Japan */
{ 0x41, "JP " }, /* Japan */
{ 0x50, "CN " }, /* China */
};
/* Convert 11d country code to region string. */
u8 *nxpwifi_11d_code_2_region(u8 code)
{
u8 i;
/* Look for code in mapping table */
for (i = 0; i < ARRAY_SIZE(region_code_mapping_t); i++)
if (region_code_mapping_t[i].code == code)
return region_code_mapping_t[i].region;
return NULL;
}
/* Map supported rate index to AC/VHT data rate. */
u32 nxpwifi_index_to_acs_data_rate(struct nxpwifi_private *priv,
u8 index, u8 ht_info)
{
u32 rate = 0;
u8 mcs_index = 0;
u8 bw = 0;
u8 gi = 0;
if ((ht_info & 0x3) == NXPWIFI_RATE_FORMAT_VHT) {
mcs_index = min(index & 0xF, 9);
/* 20M: bw=0, 40M: bw=1, 80M: bw=2, 160M: bw=3 */
bw = (ht_info & 0xC) >> 2;
/* LGI: gi =0, SGI: gi = 1 */
gi = (ht_info & 0x10) >> 4;
if ((index >> 4) == 1) /* NSS = 2 */
rate = ac_mcs_rate_nss2[2 * (3 - bw) + gi][mcs_index];
else /* NSS = 1 */
rate = ac_mcs_rate_nss1[2 * (3 - bw) + gi][mcs_index];
} else if ((ht_info & 0x3) == NXPWIFI_RATE_FORMAT_HT) {
/* 20M: bw=0, 40M: bw=1 */
bw = (ht_info & 0xC) >> 2;
/* LGI: gi =0, SGI: gi = 1 */
gi = (ht_info & 0x10) >> 4;
if (index == NXPWIFI_RATE_BITMAP_MCS0) {
if (gi == 1)
rate = 0x0D; /* MCS 32 SGI rate */
else
rate = 0x0C; /* MCS 32 LGI rate */
} else if (index < 16) {
if (bw == 1 || bw == 0)
rate = mcs_rate[2 * (1 - bw) + gi][index];
else
rate = nxpwifi_data_rates[0];
} else {
rate = nxpwifi_data_rates[0];
}
} else {
/* 11n non-HT rates */
if (index >= NXPWIFI_SUPPORTED_RATES_EXT)
index = 0;
rate = nxpwifi_data_rates[index];
}
return rate;
}
/* Map supported rate index to data rate. */
u32 nxpwifi_index_to_data_rate(struct nxpwifi_private *priv,
u8 index, u8 ht_info)
{
u32 mcs_num_supp =
(priv->adapter->user_dev_mcs_support == HT_STREAM_2X2) ? 16 : 8;
u32 rate;
if (priv->adapter->is_hw_11ac_capable)
return nxpwifi_index_to_acs_data_rate(priv, index, ht_info);
if (ht_info & BIT(0)) {
if (index == NXPWIFI_RATE_BITMAP_MCS0) {
if (ht_info & BIT(2))
rate = 0x0D; /* MCS 32 SGI rate */
else
rate = 0x0C; /* MCS 32 LGI rate */
} else if (index < mcs_num_supp) {
if (ht_info & BIT(1)) {
if (ht_info & BIT(2))
/* SGI, 40M */
rate = mcs_rate[1][index];
else
/* LGI, 40M */
rate = mcs_rate[0][index];
} else {
if (ht_info & BIT(2))
/* SGI, 20M */
rate = mcs_rate[3][index];
else
/* LGI, 20M */
rate = mcs_rate[2][index];
}
} else {
rate = nxpwifi_data_rates[0];
}
} else {
if (index >= NXPWIFI_SUPPORTED_RATES_EXT)
index = 0;
rate = nxpwifi_data_rates[index];
}
return rate;
}
/* Return current active data rates (depends on connection). */
u32 nxpwifi_get_active_data_rates(struct nxpwifi_private *priv, u8 *rates)
{
if (!priv->media_connected)
return nxpwifi_get_supported_rates(priv, rates);
else
return nxpwifi_copy_rates(rates, 0,
priv->curr_bss_params.data_rates,
priv->curr_bss_params.num_of_rates);
}
/* Find Channel/Frequency/Power by band and channel or frequency. */
struct nxpwifi_chan_freq_power *
nxpwifi_get_cfp(struct nxpwifi_private *priv, u8 band, u16 channel, u32 freq)
{
struct nxpwifi_chan_freq_power *cfp = NULL;
struct ieee80211_supported_band *sband;
struct ieee80211_channel *ch = NULL;
int i;
if (!channel && !freq)
return cfp;
if (nxpwifi_band_to_radio_type(band) == HOST_SCAN_RADIO_TYPE_BG)
sband = priv->wdev.wiphy->bands[NL80211_BAND_2GHZ];
else
sband = priv->wdev.wiphy->bands[NL80211_BAND_5GHZ];
if (!sband) {
nxpwifi_dbg(priv->adapter, ERROR,
"%s: cannot find cfp by band %d\n",
__func__, band);
return cfp;
}
for (i = 0; i < sband->n_channels; i++) {
ch = &sband->channels[i];
if (ch->flags & IEEE80211_CHAN_DISABLED)
continue;
if (freq) {
if (ch->center_freq == freq)
break;
} else {
/* Find by valid channel. */
if (ch->hw_value == channel ||
channel == FIRST_VALID_CHANNEL)
break;
}
}
if (i == sband->n_channels) {
nxpwifi_dbg(priv->adapter, WARN,
"%s: cannot find cfp by band %d\t"
"& channel=%d freq=%d\n",
__func__, band, channel, freq);
} else {
if (!ch)
return cfp;
priv->cfp.channel = ch->hw_value;
priv->cfp.freq = ch->center_freq;
priv->cfp.max_tx_power = ch->max_power;
cfp = &priv->cfp;
}
return cfp;
}
/* Return true if data rate is set to auto. */
u8
nxpwifi_is_rate_auto(struct nxpwifi_private *priv)
{
u32 i;
int rate_num = 0;
for (i = 0; i < ARRAY_SIZE(priv->bitmap_rates); i++)
if (priv->bitmap_rates[i])
rate_num++;
if (rate_num > 1)
return true;
else
return false;
}
/* Extract supported rates from cfg80211_scan_request bitmask. */
u32 nxpwifi_get_rates_from_cfg80211(struct nxpwifi_private *priv,
u8 *rates, u8 radio_type)
{
struct wiphy *wiphy = priv->adapter->wiphy;
struct cfg80211_scan_request *request = priv->scan_request;
u32 num_rates, rate_mask;
struct ieee80211_supported_band *sband;
int i;
if (radio_type) {
sband = wiphy->bands[NL80211_BAND_5GHZ];
if (WARN_ON_ONCE(!sband))
return 0;
rate_mask = request->rates[NL80211_BAND_5GHZ];
} else {
sband = wiphy->bands[NL80211_BAND_2GHZ];
if (WARN_ON_ONCE(!sband))
return 0;
rate_mask = request->rates[NL80211_BAND_2GHZ];
}
num_rates = 0;
for (i = 0; i < sband->n_bitrates; i++) {
if ((BIT(i) & rate_mask) == 0)
continue; /* skip rate */
rates[num_rates++] = (u8)(sband->bitrates[i].bitrate / 5);
}
return num_rates;
}
/* Convert config_bands to B/G/A band */
static u16 nxpwifi_convert_config_bands(u16 config_bands)
{
u16 bands = 0;
if (config_bands & BAND_B)
bands |= BAND_B;
if (config_bands & BAND_G || config_bands & BAND_GN ||
config_bands & BAND_GAC || config_bands & BAND_GAX)
bands |= BAND_G;
if (config_bands & BAND_A || config_bands & BAND_AN ||
config_bands & BAND_AAC || config_bands & BAND_AAX)
bands |= BAND_A;
return bands;
}
/* Get supported rates in infrastructure (STA/P2P client) mode. */
u32 nxpwifi_get_supported_rates(struct nxpwifi_private *priv, u8 *rates)
{
struct nxpwifi_adapter *adapter = priv->adapter;
u32 k = 0;
u16 bands = 0;
bands = nxpwifi_convert_config_bands(adapter->fw_bands);
if (priv->bss_mode == NL80211_IFTYPE_STATION) {
if (bands == BAND_B) {
/* B only */
nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t"
"supported_rates_b\n",
priv->config_bands);
k = nxpwifi_copy_rates(rates, k, supported_rates_b,
sizeof(supported_rates_b));
} else if (bands == BAND_G) {
/* G only */
nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t"
"supported_rates_g\n",
priv->config_bands);
k = nxpwifi_copy_rates(rates, k, supported_rates_g,
sizeof(supported_rates_g));
} else if (bands & (BAND_B | BAND_G)) {
/* BG only */
nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t"
"supported_rates_bg\n",
priv->config_bands);
k = nxpwifi_copy_rates(rates, k, supported_rates_bg,
sizeof(supported_rates_bg));
} else if (bands & BAND_A) {
/* support A */
nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t"
"supported_rates_a\n",
priv->config_bands);
k = nxpwifi_copy_rates(rates, k, supported_rates_a,
sizeof(supported_rates_a));
}
}
return k;
}
u8 nxpwifi_adjust_data_rate(struct nxpwifi_private *priv,
u8 rx_rate, u8 rate_info)
{
u8 rate_index = 0;
/* HT40 */
if ((rate_info & BIT(0)) && (rate_info & BIT(1)))
rate_index = NXPWIFI_RATE_INDEX_MCS0 +
NXPWIFI_BW20_MCS_NUM + rx_rate;
else if (rate_info & BIT(0)) /* HT20 */
rate_index = NXPWIFI_RATE_INDEX_MCS0 + rx_rate;
else
rate_index = (rx_rate > NXPWIFI_RATE_INDEX_OFDM0) ?
rx_rate - 1 : rx_rate;
if (rate_index >= NXPWIFI_MAX_AC_RX_RATES)
rate_index = NXPWIFI_MAX_AC_RX_RATES - 1;
return rate_index;
}
/* Check if the given region code is a valid NXP-defined region code.
* Valid codes are defined by the FW v18 region enum in IEEE_types.h:
* 0x00 (World), 0x10 (US/FCC), 0x20 (Canada/IC), 0x30 (ETSI),
* 0x31 (Spain), 0x32 (France), 0x40 (Japan), 0x41 (Japan1), 0x50 (China)
*/
bool nxpwifi_is_valid_region_code(enum nxpwifi_region_code code)
{
switch (code) {
case NXPWIFI_REGION_WORLD:
case NXPWIFI_REGION_FCC:
case NXPWIFI_REGION_IC:
case NXPWIFI_REGION_ETSI:
case NXPWIFI_REGION_SPAIN:
case NXPWIFI_REGION_FRANCE:
case NXPWIFI_REGION_JAPAN:
case NXPWIFI_REGION_JAPAN1:
case NXPWIFI_REGION_CHINA:
return true;
default:
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* nxpwifi: commands and events
*
* Copyright 2011-2024 NXP
*/
#ifndef _NXPWIFI_CMD_EVT_H_
#define _NXPWIFI_CMD_EVT_H_
struct nxpwifi_cmd_entry {
u16 cmd_no;
int (*prepare_cmd)(struct nxpwifi_private *priv,
struct host_cmd_ds_command *cmd,
u16 cmd_no, void *data_buf,
u16 cmd_action, u32 cmd_type);
int (*cmd_resp)(struct nxpwifi_private *priv,
struct host_cmd_ds_command *resp,
u16 cmdresp_no,
void *data_buf);
};
struct nxpwifi_evt_entry {
u32 event_cause;
int (*event_handler)(struct nxpwifi_private *priv);
};
static inline int
nxpwifi_cmd_fill_head_only(struct nxpwifi_private *priv,
struct host_cmd_ds_command *cmd,
u16 cmd_no, void *data_buf,
u16 cmd_action, u32 cmd_type)
{
cmd->command = cpu_to_le16(cmd_no);
cmd->size = cpu_to_le16(S_DS_GEN);
return 0;
}
int nxpwifi_send_cmd(struct nxpwifi_private *priv, u16 cmd_no,
u16 cmd_action, u32 cmd_oid, void *data_buf, bool sync);
int nxpwifi_sta_prepare_cmd(struct nxpwifi_private *priv,
struct cmd_ctrl_node *cmd_node,
u16 cmd_action, u32 cmd_oid);
int nxpwifi_sta_init_cmd(struct nxpwifi_private *priv, u8 first_sta, bool init);
int nxpwifi_uap_prepare_cmd(struct nxpwifi_private *priv,
struct cmd_ctrl_node *cmd_node,
u16 cmd_action, u32 type);
int nxpwifi_set_secure_params(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *bss_config,
struct cfg80211_ap_settings *params);
void nxpwifi_set_ht_params(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *bss_cfg,
struct cfg80211_ap_settings *params);
void nxpwifi_set_vht_params(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *bss_cfg,
struct cfg80211_ap_settings *params);
void nxpwifi_set_tpc_params(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *bss_cfg,
struct cfg80211_ap_settings *params);
void nxpwifi_set_uap_rates(struct nxpwifi_uap_bss_param *bss_cfg,
struct cfg80211_ap_settings *params);
void nxpwifi_set_vht_width(struct nxpwifi_private *priv,
enum nl80211_chan_width width,
bool ap_11ac_disable);
bool nxpwifi_check_11ax_capability(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *bss_cfg,
struct cfg80211_ap_settings *params);
int nxpwifi_set_11ax_status(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *bss_cfg,
struct cfg80211_ap_settings *params);
void nxpwifi_set_sys_config_invalid_data(struct nxpwifi_uap_bss_param *config);
void nxpwifi_set_wmm_params(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *bss_cfg,
struct cfg80211_ap_settings *params);
void nxpwifi_config_uap_11d(struct nxpwifi_private *priv,
struct cfg80211_beacon_data *beacon_data);
void nxpwifi_uap_set_channel(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *bss_cfg,
struct cfg80211_chan_def chandef);
int nxpwifi_config_start_uap(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *bss_cfg);
int nxpwifi_process_event(struct nxpwifi_adapter *adapter);
int nxpwifi_process_sta_event(struct nxpwifi_private *priv);
int nxpwifi_process_uap_event(struct nxpwifi_private *priv);
void nxpwifi_reset_connect_state(struct nxpwifi_private *priv, u16 reason,
bool from_ap);
void nxpwifi_process_multi_chan_event(struct nxpwifi_private *priv,
struct sk_buff *event_skb);
void nxpwifi_process_tx_pause_event(struct nxpwifi_private *priv,
struct sk_buff *event);
void nxpwifi_bt_coex_wlan_param_update_event(struct nxpwifi_private *priv,
struct sk_buff *event_skb);
int nxpwifi_mgmt_frame_reg(struct nxpwifi_private *priv, u32 mask);
int nxpwifi_set_uap_sys_cfg(struct nxpwifi_private *priv,
struct nxpwifi_uap_bss_param *cfg);
int nxpwifi_set_rts(struct nxpwifi_private *priv, u32 rts_thr);
int nxpwifi_set_frag(struct nxpwifi_private *priv, u32 frag_thr);
int nxpwifi_set_bss_mode(struct nxpwifi_private *priv);
int nxpwifi_config_monitor_mode(struct nxpwifi_private *priv,
struct nxpwifi_802_11_net_monitor *cfg);
int nxpwifi_apply_regdomain(struct nxpwifi_private *priv);
int nxpwifi_get_tx_pwr(struct nxpwifi_private *priv);
int nxpwifi_get_rssi_info(struct nxpwifi_private *priv);
int nxpwifi_get_802_11_snmp_mib(struct nxpwifi_private *priv, u16 oid, void *value);
int nxpwifi_set_rf_antenna(struct nxpwifi_private *priv, void *antcfg);
int nxpwifi_get_rf_antenna(struct nxpwifi_private *priv, u32 *tx_ant, u32 *rx_ant);
int nxpwifi_ap_stop_bss(struct nxpwifi_private *priv);
int nxpwifi_ap_sys_reset(struct nxpwifi_private *priv);
int nxpwifi_cfg80211_deinit_p2p(struct nxpwifi_private *priv);
int nxpwifi_ap_get_sta_list(struct nxpwifi_private *priv);
int nxpwifi_set_tx_rate(struct nxpwifi_private *priv, void *bitmap_rates);
int nxpwifi_802_11_subscribe_event(struct nxpwifi_private *priv,
struct nxpwifi_ds_misc_subsc_evt *subsc_evt);
int nxpwifi_uap_sta_deauth(struct nxpwifi_private *priv, u8 *mac);
int nxpwifi_bg_scan_config(struct nxpwifi_private *priv, void *bg_scan_cfg);
int nxpwifi_mef_cfg(struct nxpwifi_private *priv, void *mef_cfg);
int nxpwifi_coalesce_cfg(struct nxpwifi_private *priv, void *coalesce_cfg);
int nxpwifi_add_new_station(struct nxpwifi_private *priv, void *add_sta);
int nxpwifi_hostcmd(struct nxpwifi_private *priv, struct nxpwifi_ds_misc_cmd *hostcmd);
int nxpwifi_chan_report_request(struct nxpwifi_private *priv, void *radar_params);
#endif /* !_NXPWIFI_CMD_EVT_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* nxpwifi: ethtool
*
* Copyright 2011-2024 NXP
*/
#include "main.h"
static void nxpwifi_ethtool_get_wol(struct net_device *dev,
struct ethtool_wolinfo *wol)
{
struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev);
u32 conditions = le32_to_cpu(priv->adapter->hs_cfg.conditions);
wol->supported = WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_PHY;
if (conditions == HS_CFG_COND_DEF)
return;
if (conditions & HS_CFG_COND_UNICAST_DATA)
wol->wolopts |= WAKE_UCAST;
if (conditions & HS_CFG_COND_MULTICAST_DATA)
wol->wolopts |= WAKE_MCAST;
if (conditions & HS_CFG_COND_BROADCAST_DATA)
wol->wolopts |= WAKE_BCAST;
if (conditions & HS_CFG_COND_MAC_EVENT)
wol->wolopts |= WAKE_PHY;
}
static int nxpwifi_ethtool_set_wol(struct net_device *dev,
struct ethtool_wolinfo *wol)
{
struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev);
u32 conditions = 0;
if (wol->wolopts & ~(WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_PHY))
return -EOPNOTSUPP;
if (wol->wolopts & WAKE_UCAST)
conditions |= HS_CFG_COND_UNICAST_DATA;
if (wol->wolopts & WAKE_MCAST)
conditions |= HS_CFG_COND_MULTICAST_DATA;
if (wol->wolopts & WAKE_BCAST)
conditions |= HS_CFG_COND_BROADCAST_DATA;
if (wol->wolopts & WAKE_PHY)
conditions |= HS_CFG_COND_MAC_EVENT;
if (wol->wolopts == 0)
conditions |= HS_CFG_COND_DEF;
priv->adapter->hs_cfg.conditions = cpu_to_le32(conditions);
return 0;
}
const struct ethtool_ops nxpwifi_ethtool_ops = {
.get_wol = nxpwifi_ethtool_get_wol,
.set_wol = nxpwifi_ethtool_set_wol,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,480 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* nxpwifi: management element handling - set/delete elements.
*
* Copyright 2011-2024 NXP
*/
#include "main.h"
#include "cmdevt.h"
/* Return true if the IE index is used by another interface. */
static bool
nxpwifi_ie_index_used_by_other_intf(struct nxpwifi_private *priv, u16 idx)
{
int i;
struct nxpwifi_adapter *adapter = priv->adapter;
struct nxpwifi_ie *ie;
for (i = 0; i < adapter->priv_num; i++) {
if (adapter->priv[i] != priv) {
ie = &adapter->priv[i]->mgmt_ie[idx];
if (ie->mgmt_subtype_mask && ie->ie_length)
return true;
}
}
return false;
}
/* Pick an unused IE index for a new element. */
static int
nxpwifi_ie_get_autoidx(struct nxpwifi_private *priv, u16 subtype_mask,
struct nxpwifi_ie *ie, u16 *index)
{
u16 mask, len, i;
for (i = 0; i < priv->adapter->max_mgmt_ie_index; i++) {
mask = le16_to_cpu(priv->mgmt_ie[i].mgmt_subtype_mask);
len = le16_to_cpu(ie->ie_length);
if (mask == NXPWIFI_AUTO_IDX_MASK)
continue;
if (mask == subtype_mask) {
if (len > IEEE_MAX_IE_SIZE)
continue;
*index = i;
return 0;
}
if (!priv->mgmt_ie[i].ie_length) {
if (nxpwifi_ie_index_used_by_other_intf(priv, i))
continue;
*index = i;
return 0;
}
}
return -ENOENT;
}
/* Build IE list and resolve AUTO index before sending to FW. */
static int
nxpwifi_update_autoindex_ies(struct nxpwifi_private *priv,
struct nxpwifi_ie_list *ie_list)
{
u16 travel_len, index, mask;
s16 input_len, tlv_len;
struct nxpwifi_ie *ie;
u8 *tmp;
input_len = le16_to_cpu(ie_list->len);
travel_len = sizeof(struct nxpwifi_ie_types_header);
ie_list->len = 0;
while (input_len >= sizeof(struct nxpwifi_ie_types_header)) {
ie = (struct nxpwifi_ie *)(((u8 *)ie_list) + travel_len);
tlv_len = le16_to_cpu(ie->ie_length);
travel_len += tlv_len + NXPWIFI_IE_HDR_SIZE;
if (input_len < tlv_len + NXPWIFI_IE_HDR_SIZE)
return -EINVAL;
index = le16_to_cpu(ie->ie_index);
mask = le16_to_cpu(ie->mgmt_subtype_mask);
if (index == NXPWIFI_AUTO_IDX_MASK) {
/* automatic addition */
if (nxpwifi_ie_get_autoidx(priv, mask, ie, &index))
return -ENOENT;
if (index == NXPWIFI_AUTO_IDX_MASK)
return -EINVAL;
tmp = (u8 *)&priv->mgmt_ie[index].ie_buffer;
memcpy(tmp, &ie->ie_buffer, le16_to_cpu(ie->ie_length));
priv->mgmt_ie[index].ie_length = ie->ie_length;
priv->mgmt_ie[index].ie_index = cpu_to_le16(index);
priv->mgmt_ie[index].mgmt_subtype_mask =
cpu_to_le16(mask);
ie->ie_index = cpu_to_le16(index);
} else {
if (mask != NXPWIFI_DELETE_MASK)
return -EINVAL;
/*
* Check if this index is being used on any
* other interface.
*/
if (nxpwifi_ie_index_used_by_other_intf(priv, index))
return -EPERM;
ie->ie_length = 0;
memcpy(&priv->mgmt_ie[index], ie,
sizeof(struct nxpwifi_ie));
}
le16_unaligned_add_cpu
(&ie_list->len,
le16_to_cpu(priv->mgmt_ie[index].ie_length) +
NXPWIFI_IE_HDR_SIZE);
input_len -= tlv_len + NXPWIFI_IE_HDR_SIZE;
}
if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP)
return nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG,
HOST_ACT_GEN_SET,
UAP_CUSTOM_IE_I, ie_list, true);
return 0;
}
/* Pack beacon/probe/assoc IEs into one list and update auto-assigned indices. */
static int
nxpwifi_update_uap_custom_ie(struct nxpwifi_private *priv,
struct nxpwifi_ie *beacon_ie, u16 *beacon_idx,
struct nxpwifi_ie *pr_ie, u16 *probe_idx,
struct nxpwifi_ie *ar_ie, u16 *assoc_idx)
{
struct nxpwifi_ie_list *ap_custom_ie;
u8 *pos;
u16 len;
int ret;
ap_custom_ie = kzalloc_obj(*ap_custom_ie, GFP_KERNEL);
if (!ap_custom_ie)
return -ENOMEM;
ap_custom_ie->type = cpu_to_le16(TLV_TYPE_MGMT_IE);
pos = (u8 *)ap_custom_ie->ie_list;
if (beacon_ie) {
len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE +
le16_to_cpu(beacon_ie->ie_length);
memcpy(pos, beacon_ie, len);
pos += len;
le16_unaligned_add_cpu(&ap_custom_ie->len, len);
}
if (pr_ie) {
len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE +
le16_to_cpu(pr_ie->ie_length);
memcpy(pos, pr_ie, len);
pos += len;
le16_unaligned_add_cpu(&ap_custom_ie->len, len);
}
if (ar_ie) {
len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE +
le16_to_cpu(ar_ie->ie_length);
memcpy(pos, ar_ie, len);
pos += len;
le16_unaligned_add_cpu(&ap_custom_ie->len, len);
}
ret = nxpwifi_update_autoindex_ies(priv, ap_custom_ie);
pos = (u8 *)(&ap_custom_ie->ie_list[0].ie_index);
if (beacon_ie && *beacon_idx == NXPWIFI_AUTO_IDX_MASK) {
/* save beacon element index after auto-indexing */
*beacon_idx = le16_to_cpu(ap_custom_ie->ie_list[0].ie_index);
len = sizeof(*beacon_ie) - IEEE_MAX_IE_SIZE +
le16_to_cpu(beacon_ie->ie_length);
pos += len;
}
if (pr_ie && le16_to_cpu(pr_ie->ie_index) == NXPWIFI_AUTO_IDX_MASK) {
/* save probe resp element index after auto-indexing */
*probe_idx = *((u16 *)pos);
len = sizeof(*pr_ie) - IEEE_MAX_IE_SIZE +
le16_to_cpu(pr_ie->ie_length);
pos += len;
}
if (ar_ie && le16_to_cpu(ar_ie->ie_index) == NXPWIFI_AUTO_IDX_MASK)
/* save assoc resp element index after auto-indexing */
*assoc_idx = *((u16 *)pos);
kfree(ap_custom_ie);
return ret;
}
/* Append vendor IE (if present) into nxpwifi_ie, allocating as needed. */
static int nxpwifi_update_vs_ie(const u8 *ies, int ies_len,
struct nxpwifi_ie **ie_ptr, u16 mask,
unsigned int oui, u8 oui_type)
{
struct element *vs_ie;
struct nxpwifi_ie *ie = *ie_ptr;
const u8 *vendor_ie;
vendor_ie = cfg80211_find_vendor_ie(oui, oui_type, ies, ies_len);
if (vendor_ie) {
if (!*ie_ptr) {
*ie_ptr = kzalloc_obj(struct nxpwifi_ie, GFP_KERNEL);
if (!*ie_ptr)
return -ENOMEM;
ie = *ie_ptr;
}
vs_ie = (struct element *)vendor_ie;
if (le16_to_cpu(ie->ie_length) + vs_ie->datalen + 2 >
IEEE_MAX_IE_SIZE)
return -EINVAL;
memcpy(ie->ie_buffer + le16_to_cpu(ie->ie_length),
vs_ie, vs_ie->datalen + 2);
le16_unaligned_add_cpu(&ie->ie_length, vs_ie->datalen + 2);
ie->mgmt_subtype_mask = cpu_to_le16(mask);
ie->ie_index = cpu_to_le16(NXPWIFI_AUTO_IDX_MASK);
}
*ie_ptr = ie;
return 0;
}
/* Parse beacon/probe/assoc IEs from cfg80211 and push them to FW. */
static int nxpwifi_set_mgmt_beacon_data_ies(struct nxpwifi_private *priv,
struct cfg80211_beacon_data *data)
{
struct nxpwifi_ie *beacon_ie = NULL, *pr_ie = NULL, *ar_ie = NULL;
u16 beacon_idx = NXPWIFI_AUTO_IDX_MASK, pr_idx = NXPWIFI_AUTO_IDX_MASK;
u16 ar_idx = NXPWIFI_AUTO_IDX_MASK;
int ret = 0;
if (data->beacon_ies && data->beacon_ies_len) {
nxpwifi_update_vs_ie(data->beacon_ies, data->beacon_ies_len,
&beacon_ie, MGMT_MASK_BEACON,
WLAN_OUI_MICROSOFT,
WLAN_OUI_TYPE_MICROSOFT_WPS);
nxpwifi_update_vs_ie(data->beacon_ies, data->beacon_ies_len,
&beacon_ie, MGMT_MASK_BEACON,
WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P);
}
if (data->proberesp_ies && data->proberesp_ies_len) {
nxpwifi_update_vs_ie(data->proberesp_ies,
data->proberesp_ies_len, &pr_ie,
MGMT_MASK_PROBE_RESP, WLAN_OUI_MICROSOFT,
WLAN_OUI_TYPE_MICROSOFT_WPS);
nxpwifi_update_vs_ie(data->proberesp_ies,
data->proberesp_ies_len, &pr_ie,
MGMT_MASK_PROBE_RESP,
WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P);
}
if (data->assocresp_ies && data->assocresp_ies_len) {
nxpwifi_update_vs_ie(data->assocresp_ies,
data->assocresp_ies_len, &ar_ie,
MGMT_MASK_ASSOC_RESP |
MGMT_MASK_REASSOC_RESP,
WLAN_OUI_MICROSOFT,
WLAN_OUI_TYPE_MICROSOFT_WPS);
nxpwifi_update_vs_ie(data->assocresp_ies,
data->assocresp_ies_len, &ar_ie,
MGMT_MASK_ASSOC_RESP |
MGMT_MASK_REASSOC_RESP, WLAN_OUI_WFA,
WLAN_OUI_TYPE_WFA_P2P);
}
if (beacon_ie || pr_ie || ar_ie) {
ret = nxpwifi_update_uap_custom_ie(priv, beacon_ie,
&beacon_idx, pr_ie,
&pr_idx, ar_ie, &ar_idx);
if (ret)
goto done;
}
priv->beacon_idx = beacon_idx;
priv->proberesp_idx = pr_idx;
priv->assocresp_idx = ar_idx;
done:
kfree(beacon_ie);
kfree(pr_ie);
kfree(ar_ie);
return ret;
}
/* Parse head/tail IEs from cfg80211_beacon_data and send them to FW. */
static int nxpwifi_uap_parse_tail_ies(struct nxpwifi_private *priv,
struct cfg80211_beacon_data *info)
{
struct nxpwifi_ie *gen_ie;
struct element *hdr;
struct ieee80211_vendor_ie *vendorhdr;
u16 gen_idx = NXPWIFI_AUTO_IDX_MASK, ie_len = 0;
int left_len, parsed_len = 0;
unsigned int token_len;
int ret = 0;
if (!info->tail || !info->tail_len)
return 0;
gen_ie = kzalloc_obj(*gen_ie, GFP_KERNEL);
if (!gen_ie)
return -ENOMEM;
left_len = info->tail_len;
/* Skip IEs generated by FW from bss configuration to avoid duplicates. */
while (left_len > sizeof(struct element)) {
hdr = (void *)(info->tail + parsed_len);
token_len = hdr->datalen + sizeof(struct element);
if (token_len > left_len) {
ret = -EINVAL;
goto done;
}
switch (hdr->id) {
case WLAN_EID_SSID:
case WLAN_EID_SUPP_RATES:
case WLAN_EID_COUNTRY:
case WLAN_EID_PWR_CONSTRAINT:
case WLAN_EID_ERP_INFO:
case WLAN_EID_EXT_SUPP_RATES:
case WLAN_EID_HT_CAPABILITY:
case WLAN_EID_HT_OPERATION:
case WLAN_EID_VHT_CAPABILITY:
break;
case WLAN_EID_VENDOR_SPECIFIC:
/* Skip only Microsoft WMM element */
if (cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT,
WLAN_OUI_TYPE_MICROSOFT_WMM,
(const u8 *)hdr,
token_len))
break;
fallthrough;
default:
if (ie_len + token_len > IEEE_MAX_IE_SIZE) {
ret = -EINVAL;
goto done;
}
memcpy(gen_ie->ie_buffer + ie_len, hdr, token_len);
ie_len += token_len;
break;
}
left_len -= token_len;
parsed_len += token_len;
}
/*
* parse only WPA vendor element from tail, WMM element is configured by
* bss_config command
*/
vendorhdr = (void *)cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT,
WLAN_OUI_TYPE_MICROSOFT_WPA,
info->tail, info->tail_len);
if (vendorhdr) {
token_len = vendorhdr->len + sizeof(struct element);
if (ie_len + token_len > IEEE_MAX_IE_SIZE) {
ret = -EINVAL;
goto done;
}
memcpy(gen_ie->ie_buffer + ie_len, vendorhdr, token_len);
ie_len += token_len;
}
if (!ie_len)
goto done;
gen_ie->ie_index = cpu_to_le16(gen_idx);
gen_ie->mgmt_subtype_mask = cpu_to_le16(MGMT_MASK_BEACON |
MGMT_MASK_PROBE_RESP |
MGMT_MASK_ASSOC_RESP);
gen_ie->ie_length = cpu_to_le16(ie_len);
ret = nxpwifi_update_uap_custom_ie(priv, gen_ie, &gen_idx, NULL,
NULL, NULL, NULL);
if (ret)
goto done;
priv->gen_idx = gen_idx;
done:
kfree(gen_ie);
return ret;
}
/* Parse head/tail/beacon/probe/assoc IEs and program the FW. */
int nxpwifi_set_mgmt_ies(struct nxpwifi_private *priv,
struct cfg80211_beacon_data *info)
{
int ret;
ret = nxpwifi_uap_parse_tail_ies(priv, info);
if (ret)
return ret;
return nxpwifi_set_mgmt_beacon_data_ies(priv, info);
}
/* Remove previously set management IEs. */
int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv)
{
struct nxpwifi_ie *beacon_ie = NULL, *pr_ie = NULL;
struct nxpwifi_ie *ar_ie = NULL, *gen_ie = NULL;
int ret = 0;
if (priv->gen_idx != NXPWIFI_AUTO_IDX_MASK) {
gen_ie = kmalloc_obj(*gen_ie, GFP_KERNEL);
if (!gen_ie)
return -ENOMEM;
gen_ie->ie_index = cpu_to_le16(priv->gen_idx);
gen_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK);
gen_ie->ie_length = 0;
ret = nxpwifi_update_uap_custom_ie(priv, gen_ie, &priv->gen_idx,
NULL, &priv->proberesp_idx,
NULL, &priv->assocresp_idx);
if (ret)
goto done;
priv->gen_idx = NXPWIFI_AUTO_IDX_MASK;
}
if (priv->beacon_idx != NXPWIFI_AUTO_IDX_MASK) {
beacon_ie = kmalloc_obj(*beacon_ie, GFP_KERNEL);
if (!beacon_ie) {
ret = -ENOMEM;
goto done;
}
beacon_ie->ie_index = cpu_to_le16(priv->beacon_idx);
beacon_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK);
beacon_ie->ie_length = 0;
}
if (priv->proberesp_idx != NXPWIFI_AUTO_IDX_MASK) {
pr_ie = kmalloc_obj(*pr_ie, GFP_KERNEL);
if (!pr_ie) {
ret = -ENOMEM;
goto done;
}
pr_ie->ie_index = cpu_to_le16(priv->proberesp_idx);
pr_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK);
pr_ie->ie_length = 0;
}
if (priv->assocresp_idx != NXPWIFI_AUTO_IDX_MASK) {
ar_ie = kmalloc_obj(*ar_ie, GFP_KERNEL);
if (!ar_ie) {
ret = -ENOMEM;
goto done;
}
ar_ie->ie_index = cpu_to_le16(priv->assocresp_idx);
ar_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK);
ar_ie->ie_length = 0;
}
if (beacon_ie || pr_ie || ar_ie)
ret = nxpwifi_update_uap_custom_ie(priv,
beacon_ie, &priv->beacon_idx,
pr_ie, &priv->proberesp_idx,
ar_ie, &priv->assocresp_idx);
done:
kfree(gen_ie);
kfree(beacon_ie);
kfree(pr_ie);
kfree(ar_ie);
return ret;
}

View File

@@ -0,0 +1,607 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* nxpwifi: HW/FW initialization
*
* 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"
/* Add a BSS priority node to the adapter list. */
static int nxpwifi_add_bss_prio_tbl(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct nxpwifi_bss_prio_node *bss_prio;
struct nxpwifi_bss_prio_tbl *tbl = adapter->bss_prio_tbl;
bss_prio = kzalloc_obj(*bss_prio, GFP_KERNEL);
if (!bss_prio)
return -ENOMEM;
bss_prio->priv = priv;
INIT_LIST_HEAD(&bss_prio->list);
spin_lock_bh(&tbl[priv->bss_priority].bss_prio_lock);
list_add_tail(&bss_prio->list, &tbl[priv->bss_priority].bss_prio_head);
spin_unlock_bh(&tbl[priv->bss_priority].bss_prio_lock);
return 0;
}
static void wakeup_timer_fn(struct timer_list *t)
{
struct nxpwifi_adapter *adapter = timer_container_of(adapter, t, wakeup_timer);
nxpwifi_dbg(adapter, ERROR, "Firmware wakeup failed\n");
adapter->hw_status = NXPWIFI_HW_STATUS_RESET;
nxpwifi_cancel_all_pending_cmd(adapter);
if (adapter->if_ops.card_reset)
adapter->if_ops.card_reset(adapter);
}
/* Initialize priv defaults and lists. */
int nxpwifi_init_priv(struct nxpwifi_private *priv)
{
u32 i;
priv->media_connected = false;
eth_broadcast_addr(priv->curr_addr);
priv->port_open = false;
priv->usb_port = NXPWIFI_USB_EP_DATA;
priv->pkt_tx_ctrl = 0;
priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED;
priv->data_rate = 0; /* Initially indicate the rate as auto */
priv->is_data_rate_auto = true;
priv->bcn_avg_factor = DEFAULT_BCN_AVG_FACTOR;
priv->data_avg_factor = DEFAULT_DATA_AVG_FACTOR;
priv->auth_flag = 0;
priv->auth_alg = WLAN_AUTH_NONE;
priv->sec_info.wep_enabled = 0;
priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM;
priv->sec_info.encryption_mode = 0;
for (i = 0; i < ARRAY_SIZE(priv->wep_key); i++)
memset(&priv->wep_key[i], 0, sizeof(struct nxpwifi_wep_key));
priv->wep_key_curr_index = 0;
priv->curr_pkt_filter = HOST_ACT_MAC_DYNAMIC_BW_ENABLE |
HOST_ACT_MAC_RX_ON | HOST_ACT_MAC_TX_ON |
HOST_ACT_MAC_ETHERNETII_ENABLE;
priv->beacon_period = 100; /* beacon interval */
priv->attempted_bss_desc = NULL;
memset(&priv->curr_bss_params, 0, sizeof(priv->curr_bss_params));
priv->listen_interval = NXPWIFI_DEFAULT_LISTEN_INTERVAL;
memset(&priv->prev_ssid, 0, sizeof(priv->prev_ssid));
memset(&priv->prev_bssid, 0, sizeof(priv->prev_bssid));
memset(&priv->assoc_rsp_buf, 0, sizeof(priv->assoc_rsp_buf));
priv->assoc_rsp_size = 0;
priv->atim_window = 0;
priv->tx_power_level = 0;
priv->max_tx_power_level = 0;
priv->min_tx_power_level = 0;
priv->tx_ant = 0;
priv->rx_ant = 0;
priv->tx_rate = 0;
priv->rxpd_htinfo = 0;
priv->rxpd_rate = 0;
priv->rate_bitmap = 0;
priv->data_rssi_last = 0;
priv->data_rssi_avg = 0;
priv->data_nf_avg = 0;
priv->data_nf_last = 0;
priv->bcn_rssi_last = 0;
priv->bcn_rssi_avg = 0;
priv->bcn_nf_avg = 0;
priv->bcn_nf_last = 0;
memset(&priv->wpa_ie, 0, sizeof(priv->wpa_ie));
memset(&priv->aes_key, 0, sizeof(priv->aes_key));
priv->wpa_ie_len = 0;
priv->wpa_is_gtk_set = false;
memset(&priv->assoc_tlv_buf, 0, sizeof(priv->assoc_tlv_buf));
priv->assoc_tlv_buf_len = 0;
memset(&priv->wps, 0, sizeof(priv->wps));
memset(&priv->gen_ie_buf, 0, sizeof(priv->gen_ie_buf));
priv->gen_ie_buf_len = 0;
memset(priv->vs_ie, 0, sizeof(priv->vs_ie));
priv->wmm_required = true;
priv->wmm_enabled = false;
priv->wmm_qosinfo = 0;
priv->curr_bcn_buf = NULL;
priv->curr_bcn_size = 0;
priv->wps_ie = NULL;
priv->wps_ie_len = 0;
priv->ap_11n_enabled = 0;
memset(&priv->roc_cfg, 0, sizeof(priv->roc_cfg));
priv->scan_block = false;
priv->csa_chan = 0;
priv->csa_expire_time = 0;
priv->del_list_idx = 0;
priv->hs2_enabled = false;
nxpwifi_wmm_init_tos_to_tid_inv(priv);
nxpwifi_init_11h_params(priv);
return nxpwifi_add_bss_prio_tbl(priv);
}
/* Allocate command buffer and sleep-confirm skb. */
static int nxpwifi_allocate_adapter(struct nxpwifi_adapter *adapter)
{
int ret;
/* Allocate command buffer */
ret = nxpwifi_alloc_cmd_buffer(adapter);
if (ret) {
nxpwifi_dbg(adapter, ERROR,
"%s: failed to alloc cmd buffer\n",
__func__);
return ret;
}
adapter->sleep_cfm =
dev_alloc_skb(sizeof(struct nxpwifi_opt_sleep_confirm)
+ INTF_HEADER_LEN);
if (!adapter->sleep_cfm) {
nxpwifi_dbg(adapter, ERROR,
"%s: failed to alloc sleep cfm\t"
" cmd buffer\n", __func__);
return -ENOMEM;
}
skb_reserve(adapter->sleep_cfm, INTF_HEADER_LEN);
return 0;
}
/* Initialize adapter defaults and WMM parameters. */
static void nxpwifi_init_adapter(struct nxpwifi_adapter *adapter)
{
struct nxpwifi_opt_sleep_confirm *sleep_cfm_buf = NULL;
skb_put(adapter->sleep_cfm, sizeof(struct nxpwifi_opt_sleep_confirm));
adapter->cmd_sent = false;
adapter->data_sent = true;
adapter->intf_hdr_len = INTF_HEADER_LEN;
adapter->cmd_resp_received = false;
adapter->event_received = false;
adapter->data_received = false;
adapter->assoc_resp_received = false;
adapter->priv_link_lost = NULL;
adapter->host_mlme_link_lost = false;
clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags);
adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING;
adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM;
adapter->ps_state = PS_STATE_AWAKE;
adapter->need_to_wakeup = false;
adapter->scan_mode = HOST_BSS_MODE_ANY;
adapter->specific_scan_time = NXPWIFI_SPECIFIC_SCAN_CHAN_TIME;
adapter->active_scan_time = NXPWIFI_ACTIVE_SCAN_CHAN_TIME;
adapter->passive_scan_time = NXPWIFI_PASSIVE_SCAN_CHAN_TIME;
adapter->scan_chan_gap_time = NXPWIFI_DEF_SCAN_CHAN_GAP_TIME;
adapter->scan_probes = 1;
adapter->multiple_dtim = 1;
/* default value in firmware will be used */
adapter->local_listen_interval = 0;
adapter->is_deep_sleep = false;
adapter->delay_null_pkt = false;
adapter->delay_to_ps = 1000;
adapter->enhanced_ps_mode = PS_MODE_AUTO;
/* Disable NULL Pkg generation by default */
adapter->gen_null_pkt = false;
/* Disable pps/uapsd mode by default */
adapter->pps_uapsd_mode = false;
adapter->pm_wakeup_card_req = false;
adapter->pm_wakeup_fw_try = false;
adapter->curr_tx_buf_size = NXPWIFI_TX_DATA_BUF_SIZE_2K;
clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags);
adapter->hs_cfg.conditions = cpu_to_le32(HS_CFG_COND_DEF);
adapter->hs_cfg.gpio = HS_CFG_GPIO_DEF;
adapter->hs_cfg.gap = HS_CFG_GAP_DEF;
adapter->hs_activated = false;
memset(adapter->event_body, 0, sizeof(adapter->event_body));
adapter->hw_dot_11n_dev_cap = 0;
adapter->hw_dev_mcs_support = 0;
adapter->sec_chan_offset = 0;
nxpwifi_wmm_init(adapter);
atomic_set(&adapter->tx_hw_pending, 0);
sleep_cfm_buf = (struct nxpwifi_opt_sleep_confirm *)
adapter->sleep_cfm->data;
memset(sleep_cfm_buf, 0, adapter->sleep_cfm->len);
sleep_cfm_buf->command = cpu_to_le16(HOST_CMD_802_11_PS_MODE_ENH);
sleep_cfm_buf->size = cpu_to_le16(adapter->sleep_cfm->len);
sleep_cfm_buf->result = 0;
sleep_cfm_buf->action = cpu_to_le16(SLEEP_CONFIRM);
sleep_cfm_buf->resp_ctrl = cpu_to_le16(RESP_NEEDED);
memset(&adapter->sleep_period, 0, sizeof(adapter->sleep_period));
adapter->tx_lock_flag = false;
adapter->null_pkt_interval = 0;
adapter->fw_bands = 0;
adapter->fw_release_number = 0;
adapter->fw_cap_info = 0;
memset(&adapter->upld_buf, 0, sizeof(adapter->upld_buf));
adapter->event_cause = 0;
adapter->region_code = 0;
adapter->bcn_miss_time_out = DEFAULT_BCN_MISS_TIMEOUT;
memset(&adapter->arp_filter, 0, sizeof(adapter->arp_filter));
adapter->arp_filter_size = 0;
adapter->max_mgmt_ie_index = MAX_MGMT_IE_INDEX;
adapter->key_api_major_ver = 0;
adapter->key_api_minor_ver = 0;
eth_broadcast_addr(adapter->perm_addr);
adapter->iface_limit.sta_intf = NXPWIFI_MAX_STA_NUM;
adapter->iface_limit.uap_intf = NXPWIFI_MAX_UAP_NUM;
adapter->active_scan_triggered = false;
timer_setup(&adapter->wakeup_timer, wakeup_timer_fn, 0);
adapter->devdump_len = 0;
memset(&adapter->vdll_ctrl, 0, sizeof(adapter->vdll_ctrl));
adapter->vdll_ctrl.skb = dev_alloc_skb(NXPWIFI_SIZE_OF_CMD_BUFFER);
atomic_set(&adapter->iface_changing, 0);
}
/* Update trans_start for each Tx queue. */
void nxpwifi_set_trans_start(struct net_device *dev)
{
int i;
for (i = 0; i < dev->num_tx_queues; i++)
txq_trans_cond_update(netdev_get_tx_queue(dev, i));
netif_trans_update(dev);
}
/* Wake all netdev Tx queues. */
void nxpwifi_wake_up_net_dev_queue(struct net_device *netdev,
struct nxpwifi_adapter *adapter)
{
spin_lock_bh(&adapter->queue_lock);
netif_tx_wake_all_queues(netdev);
spin_unlock_bh(&adapter->queue_lock);
}
/* Stop all netdev Tx queues. */
void nxpwifi_stop_net_dev_queue(struct net_device *netdev,
struct nxpwifi_adapter *adapter)
{
spin_lock_bh(&adapter->queue_lock);
netif_tx_stop_all_queues(netdev);
spin_unlock_bh(&adapter->queue_lock);
}
/* Invalidate list heads. */
static void nxpwifi_invalidate_lists(struct nxpwifi_adapter *adapter)
{
struct nxpwifi_private *priv;
s32 i, j;
list_del(&adapter->cmd_free_q);
list_del(&adapter->cmd_pending_q);
list_del(&adapter->scan_pending_q);
for (i = 0; i < adapter->priv_num; i++)
list_del(&adapter->bss_prio_tbl[i].bss_prio_head);
for (i = 0; i < adapter->priv_num; i++) {
priv = adapter->priv[i];
for (j = 0; j < MAX_NUM_TID; ++j) {
list_del(&priv->wmm.tid_tbl_ptr[j].ra_list);
list_del(&priv->tx_ba_stream_tbl_ptr[j]);
list_del(&priv->rx_reorder_tbl_ptr[j]);
}
list_del(&priv->sta_list);
}
}
/* Cancel pending work, stop timers, and free adapter buffers. */
static void
nxpwifi_adapter_cleanup(struct nxpwifi_adapter *adapter)
{
timer_delete(&adapter->wakeup_timer);
nxpwifi_cancel_all_pending_cmd(adapter);
wake_up_interruptible(&adapter->cmd_wait_q.wait);
wake_up_interruptible(&adapter->hs_activate_wait_q);
if (adapter->vdll_ctrl.vdll_mem) {
vfree(adapter->vdll_ctrl.vdll_mem);
adapter->vdll_ctrl.vdll_mem = NULL;
adapter->vdll_ctrl.vdll_len = 0;
}
if (adapter->vdll_ctrl.skb) {
dev_kfree_skb_any(adapter->vdll_ctrl.skb);
adapter->vdll_ctrl.skb = NULL;
}
}
void nxpwifi_free_cmd_buffers(struct nxpwifi_adapter *adapter)
{
nxpwifi_invalidate_lists(adapter);
/* Free command buffer */
nxpwifi_dbg(adapter, INFO, "info: free cmd buffer\n");
nxpwifi_free_cmd_buffer(adapter);
if (adapter->sleep_cfm)
dev_kfree_skb_any(adapter->sleep_cfm);
}
/* Initialize locks and list heads. */
void nxpwifi_init_lock_list(struct nxpwifi_adapter *adapter)
{
struct nxpwifi_private *priv;
s32 i, j;
spin_lock_init(&adapter->int_lock);
spin_lock_init(&adapter->nxpwifi_cmd_lock);
spin_lock_init(&adapter->queue_lock);
for (i = 0; i < adapter->priv_num; i++) {
priv = adapter->priv[i];
spin_lock_init(&priv->wmm.ra_list_spinlock);
spin_lock_init(&priv->curr_bcn_buf_lock);
spin_lock_init(&priv->sta_list_spinlock);
}
/* Initialize cmd_free_q */
INIT_LIST_HEAD(&adapter->cmd_free_q);
/* Initialize cmd_pending_q */
INIT_LIST_HEAD(&adapter->cmd_pending_q);
/* Initialize scan_pending_q */
INIT_LIST_HEAD(&adapter->scan_pending_q);
spin_lock_init(&adapter->cmd_free_q_lock);
spin_lock_init(&adapter->cmd_pending_q_lock);
spin_lock_init(&adapter->scan_pending_q_lock);
skb_queue_head_init(&adapter->rx_mlme_q);
skb_queue_head_init(&adapter->rx_data_q);
skb_queue_head_init(&adapter->tx_data_q);
for (i = 0; i < adapter->priv_num; ++i) {
INIT_LIST_HEAD(&adapter->bss_prio_tbl[i].bss_prio_head);
spin_lock_init(&adapter->bss_prio_tbl[i].bss_prio_lock);
}
for (i = 0; i < adapter->priv_num; i++) {
priv = adapter->priv[i];
for (j = 0; j < MAX_NUM_TID; ++j) {
INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[j].ra_list);
INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr[j]);
INIT_LIST_HEAD(&priv->rx_reorder_tbl_ptr[j]);
spin_lock_init(&priv->tx_ba_stream_tbl_lock[j]);
spin_lock_init(&priv->rx_reorder_tbl_lock[j]);
}
INIT_LIST_HEAD(&priv->sta_list);
skb_queue_head_init(&priv->bypass_txq);
spin_lock_init(&priv->ack_status_lock);
xa_init_flags(&priv->ack_status_frames, XA_FLAGS_ALLOC);
}
}
/* Init firmware: alloc resources, init adapter/privs, send STA init. */
int nxpwifi_init_fw(struct nxpwifi_adapter *adapter)
{
int ret;
struct nxpwifi_private *priv;
u8 i;
bool first_sta = true;
adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING;
/* Allocate memory for member of adapter structure */
ret = nxpwifi_allocate_adapter(adapter);
if (ret)
return ret;
/* Initialize adapter structure */
nxpwifi_init_adapter(adapter);
for (i = 0; i < adapter->priv_num; i++) {
priv = adapter->priv[i];
/* Initialize private structure */
ret = nxpwifi_init_priv(priv);
if (ret)
return ret;
}
for (i = 0; i < adapter->priv_num; i++) {
ret = nxpwifi_sta_init_cmd(adapter->priv[i],
first_sta, true);
if (ret)
return ret;
first_sta = false;
}
spin_lock_bh(&adapter->cmd_pending_q_lock);
WARN_ON(!list_empty(&adapter->cmd_pending_q));
spin_unlock_bh(&adapter->cmd_pending_q_lock);
adapter->hw_status = NXPWIFI_HW_STATUS_READY;
return 0;
}
/* Remove all BSS priority nodes for this priv. */
static void nxpwifi_delete_bss_prio_tbl(struct nxpwifi_private *priv)
{
int i;
struct nxpwifi_adapter *adapter = priv->adapter;
struct nxpwifi_bss_prio_node *bssprio_node, *tmp_node;
struct list_head *head;
spinlock_t *lock; /* bss priority lock */
for (i = 0; i < adapter->priv_num; ++i) {
head = &adapter->bss_prio_tbl[i].bss_prio_head;
lock = &adapter->bss_prio_tbl[i].bss_prio_lock;
nxpwifi_dbg(adapter, INFO,
"info: delete BSS priority table,\t"
"bss_type = %d, bss_num = %d, i = %d,\t"
"head = %p\n",
priv->bss_type, priv->bss_num, i, head);
{
spin_lock_bh(lock);
list_for_each_entry_safe(bssprio_node, tmp_node, head,
list) {
if (bssprio_node->priv == priv) {
nxpwifi_dbg(adapter, INFO,
"info: Delete\t"
"node %p, next = %p\n",
bssprio_node, tmp_node);
list_del(&bssprio_node->list);
kfree(bssprio_node);
}
}
spin_unlock_bh(lock);
}
}
}
/* Free per-priv resources and BSS priority entries. */
void nxpwifi_free_priv(struct nxpwifi_private *priv)
{
nxpwifi_clean_txrx(priv);
nxpwifi_delete_bss_prio_tbl(priv);
nxpwifi_free_curr_bcn(priv);
}
/* Shutdown driver: stop work, drain queues, free resources. */
void
nxpwifi_shutdown_drv(struct nxpwifi_adapter *adapter)
{
struct nxpwifi_private *priv;
s32 i;
struct sk_buff *skb;
/* nxpwifi already shutdown */
if (adapter->hw_status == NXPWIFI_HW_STATUS_NOT_READY)
return;
/* cancel current command */
if (adapter->curr_cmd) {
nxpwifi_dbg(adapter, WARN,
"curr_cmd is still in processing\n");
timer_delete_sync(&adapter->cmd_timer);
nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd);
adapter->curr_cmd = NULL;
}
/* shut down nxpwifi */
nxpwifi_dbg(adapter, MSG,
"info: shutdown nxpwifi...\n");
/* Clean up Tx/Rx queues and delete BSS priority table */
for (i = 0; i < adapter->priv_num; i++) {
priv = adapter->priv[i];
nxpwifi_abort_cac(priv);
nxpwifi_free_priv(priv);
}
atomic_set(&adapter->tx_queued, 0);
while ((skb = skb_dequeue(&adapter->tx_data_q)))
nxpwifi_write_data_complete(adapter, skb, 0, 0);
while ((skb = skb_dequeue(&adapter->rx_mlme_q)))
dev_kfree_skb_any(skb);
while ((skb = skb_dequeue(&adapter->rx_data_q))) {
struct nxpwifi_rxinfo *rx_info = NXPWIFI_SKB_RXCB(skb);
atomic_dec(&adapter->rx_pending);
priv = adapter->priv[rx_info->bss_num];
if (priv)
priv->stats.rx_dropped++;
dev_kfree_skb_any(skb);
}
nxpwifi_adapter_cleanup(adapter);
adapter->hw_status = NXPWIFI_HW_STATUS_NOT_READY;
}
/* Download FW if needed; check winner and wait until ready. */
int nxpwifi_dnld_fw(struct nxpwifi_adapter *adapter,
struct nxpwifi_fw_image *pmfw)
{
int ret;
u32 poll_num = 1;
/* check if firmware is already running */
ret = adapter->if_ops.check_fw_status(adapter, poll_num);
if (!ret) {
nxpwifi_dbg(adapter, MSG,
"WLAN FW already running! Skip FW dnld\n");
return 0;
}
/* check if we are the winner for downloading FW */
if (adapter->if_ops.check_winner_status) {
adapter->winner = 0;
ret = adapter->if_ops.check_winner_status(adapter);
poll_num = MAX_FIRMWARE_POLL_TRIES;
if (ret) {
nxpwifi_dbg(adapter, MSG,
"WLAN read winner status failed!\n");
return ret;
}
if (!adapter->winner) {
nxpwifi_dbg(adapter, MSG,
"WLAN is not the winner! Skip FW dnld\n");
goto poll_fw;
}
}
if (pmfw) {
/* Download firmware with helper */
ret = adapter->if_ops.prog_fw(adapter, pmfw);
if (ret) {
nxpwifi_dbg(adapter, ERROR,
"prog_fw failed ret=%#x\n", ret);
return ret;
}
}
poll_fw:
/* Check if the firmware is downloaded successfully or not */
ret = adapter->if_ops.check_fw_status(adapter, poll_num);
if (ret)
nxpwifi_dbg(adapter, ERROR,
"FW failed to be active in time\n");
return ret;
}
EXPORT_SYMBOL_GPL(nxpwifi_dnld_fw);

View File

@@ -0,0 +1,787 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* nxpwifi: association and ad-hoc start/join
*
* 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 "11ac.h"
#include "11ax.h"
#define CAPINFO_MASK (~(BIT(15) | BIT(14) | BIT(12) | BIT(11) | BIT(9)))
/* Append generic IE as pass-through TLV for join */
static int
nxpwifi_cmd_append_generic_ie(struct nxpwifi_private *priv, u8 **buffer)
{
int ret_len = 0;
struct nxpwifi_ie_types_header ie_header;
/* Null Checks */
if (!buffer)
return 0;
if (!(*buffer))
return 0;
/*
* If there is a generic element buffer setup, append it to the return
* parameter buffer pointer.
*/
if (priv->gen_ie_buf_len) {
nxpwifi_dbg(priv->adapter, INFO,
"info: %s: append generic element len %d to %p\n",
__func__, priv->gen_ie_buf_len, *buffer);
/* Wrap the generic element buffer with a pass through TLV type */
ie_header.type = cpu_to_le16(TLV_TYPE_PASSTHROUGH);
ie_header.len = cpu_to_le16(priv->gen_ie_buf_len);
memcpy(*buffer, &ie_header, sizeof(ie_header));
/*
* Increment the return size and the return buffer pointer
* param
*/
*buffer += sizeof(ie_header);
ret_len += sizeof(ie_header);
/*
* Copy the generic element buffer to the output buffer, advance
* pointer
*/
memcpy(*buffer, priv->gen_ie_buf, priv->gen_ie_buf_len);
/*
* Increment the return size and the return buffer pointer
* param
*/
*buffer += priv->gen_ie_buf_len;
ret_len += priv->gen_ie_buf_len;
/* Reset the generic element buffer */
priv->gen_ie_buf_len = 0;
}
/* return the length appended to the buffer */
return ret_len;
}
/* Append TSF timestamp (AP TSF and local RX TSF) for reassoc */
static int
nxpwifi_cmd_append_tsf_tlv(struct nxpwifi_private *priv, u8 **buffer,
struct nxpwifi_bssdescriptor *bss_desc)
{
struct nxpwifi_ie_types_tsf_timestamp tsf_tlv;
__le64 tsf_val;
/* Null Checks */
if (!buffer)
return 0;
if (!*buffer)
return 0;
memset(&tsf_tlv, 0x00, sizeof(struct nxpwifi_ie_types_tsf_timestamp));
tsf_tlv.header.type = cpu_to_le16(TLV_TYPE_TSFTIMESTAMP);
tsf_tlv.header.len = cpu_to_le16(2 * sizeof(tsf_val));
memcpy(*buffer, &tsf_tlv, sizeof(tsf_tlv.header));
*buffer += sizeof(tsf_tlv.header);
/* TSF at the time when beacon/probe_response was received */
tsf_val = cpu_to_le64(bss_desc->fw_tsf);
memcpy(*buffer, &tsf_val, sizeof(tsf_val));
*buffer += sizeof(tsf_val);
tsf_val = cpu_to_le64(bss_desc->timestamp);
nxpwifi_dbg(priv->adapter, INFO,
"info: %s: TSF offset calc: %016llx - %016llx\n",
__func__, bss_desc->timestamp, bss_desc->fw_tsf);
memcpy(*buffer, &tsf_val, sizeof(tsf_val));
*buffer += sizeof(tsf_val);
return sizeof(tsf_tlv.header) + (2 * sizeof(tsf_val));
}
/* Compute intersection of two rate sets; rate1 updated in-place */
static int nxpwifi_get_common_rates(struct nxpwifi_private *priv, u8 *rate1,
u32 rate1_size, u8 *rate2, u32 rate2_size)
{
int ret;
u8 *ptr = rate1, *tmp;
u32 i, j;
tmp = kmemdup(rate1, rate1_size, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
memset(rate1, 0, rate1_size);
for (i = 0; i < rate2_size && rate2[i]; i++) {
for (j = 0; j < rate1_size && tmp[j]; j++) {
/*
* Check common rate, excluding the bit for
* basic rate
*/
if ((rate2[i] & 0x7F) == (tmp[j] & 0x7F)) {
*rate1++ = tmp[j];
break;
}
}
}
nxpwifi_dbg(priv->adapter, INFO, "info: Tx data rate set to %#x\n",
priv->data_rate);
if (!priv->is_data_rate_auto) {
while (*ptr) {
if ((*ptr & 0x7f) == priv->data_rate) {
ret = 0;
goto done;
}
ptr++;
}
nxpwifi_dbg(priv->adapter, ERROR,
"previously set fixed data rate %#x\t"
"is not compatible with the network\n",
priv->data_rate);
ret = -EPERM;
goto done;
}
ret = 0;
done:
kfree(tmp);
return ret;
}
/* Build common rates from BSS descriptor into out_rates */
static int
nxpwifi_setup_rates_from_bssdesc(struct nxpwifi_private *priv,
struct nxpwifi_bssdescriptor *bss_desc,
u8 *out_rates, u32 *out_rates_size)
{
u8 card_rates[NXPWIFI_SUPPORTED_RATES];
u32 card_rates_size;
int ret;
/* Copy AP supported rates */
memcpy(out_rates, bss_desc->supported_rates, NXPWIFI_SUPPORTED_RATES);
/* Get the STA supported rates */
card_rates_size = nxpwifi_get_active_data_rates(priv, card_rates);
/* Get the common rates between AP and STA supported rates */
ret = nxpwifi_get_common_rates(priv, out_rates, NXPWIFI_SUPPORTED_RATES,
card_rates, card_rates_size);
if (ret) {
*out_rates_size = 0;
nxpwifi_dbg(priv->adapter, ERROR,
"%s: cannot get common rates\n",
__func__);
} else {
*out_rates_size =
min_t(size_t, strlen(out_rates), NXPWIFI_SUPPORTED_RATES);
}
return ret;
}
/* Append WPS IE as pass-through TLV for join */
static int
nxpwifi_cmd_append_wps_ie(struct nxpwifi_private *priv, u8 **buffer)
{
int ret_len = 0;
struct nxpwifi_ie_types_header ie_header;
if (!buffer || !*buffer)
return 0;
/*
* If there is a wps element buffer setup, append it to the return
* parameter buffer pointer.
*/
if (priv->wps_ie_len) {
nxpwifi_dbg(priv->adapter, CMD,
"cmd: append wps element %d to %p\n",
priv->wps_ie_len, *buffer);
/* Wrap the generic element buffer with a pass through TLV type */
ie_header.type = cpu_to_le16(TLV_TYPE_PASSTHROUGH);
ie_header.len = cpu_to_le16(priv->wps_ie_len);
memcpy(*buffer, &ie_header, sizeof(ie_header));
*buffer += sizeof(ie_header);
ret_len += sizeof(ie_header);
memcpy(*buffer, priv->wps_ie, priv->wps_ie_len);
*buffer += priv->wps_ie_len;
ret_len += priv->wps_ie_len;
}
kfree(priv->wps_ie);
priv->wps_ie_len = 0;
return ret_len;
}
/* Append WPA/WPA2 RSN IE TLV */
static int nxpwifi_append_rsn_ie_wpa_wpa2(struct nxpwifi_private *priv,
u8 **buffer)
{
struct nxpwifi_ie_types_rsn_param_set *rsn_ie_tlv;
int rsn_ie_len;
if (!buffer || !(*buffer))
return 0;
rsn_ie_tlv = (struct nxpwifi_ie_types_rsn_param_set *)(*buffer);
rsn_ie_tlv->header.type = cpu_to_le16((u16)priv->wpa_ie[0]);
rsn_ie_tlv->header.type =
cpu_to_le16(le16_to_cpu(rsn_ie_tlv->header.type) & 0x00FF);
rsn_ie_tlv->header.len = cpu_to_le16((u16)priv->wpa_ie[1]);
rsn_ie_tlv->header.len = cpu_to_le16(le16_to_cpu(rsn_ie_tlv->header.len)
& 0x00FF);
if (le16_to_cpu(rsn_ie_tlv->header.len) <= (sizeof(priv->wpa_ie) - 2))
memcpy(rsn_ie_tlv->rsn_ie, &priv->wpa_ie[2],
le16_to_cpu(rsn_ie_tlv->header.len));
else
return -ENOMEM;
rsn_ie_len = sizeof(rsn_ie_tlv->header) +
le16_to_cpu(rsn_ie_tlv->header.len);
*buffer += rsn_ie_len;
return rsn_ie_len;
}
/* Build 802.11 association command and required TLVs */
int nxpwifi_cmd_802_11_associate(struct nxpwifi_private *priv,
struct host_cmd_ds_command *cmd,
struct nxpwifi_bssdescriptor *bss_desc)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct host_cmd_ds_802_11_associate *assoc = &cmd->params.associate;
struct nxpwifi_ie_types_host_mlme *host_mlme_tlv;
struct nxpwifi_ie_types_ssid_param_set *ssid_tlv;
struct nxpwifi_ie_types_phy_param_set *phy_tlv;
struct nxpwifi_ie_types_ss_param_set *ss_tlv;
struct nxpwifi_ie_types_rates_param_set *rates_tlv;
struct nxpwifi_ie_types_auth_type *auth_tlv;
struct nxpwifi_ie_types_sae_pwe_mode *sae_pwe_tlv;
struct nxpwifi_ie_types_chan_list_param_set *chan_tlv;
u8 rates[NXPWIFI_SUPPORTED_RATES];
u32 rates_size;
u16 tmp_cap;
u8 *pos;
int rsn_ie_len = 0;
int ret;
pos = (u8 *)assoc;
cmd->command = cpu_to_le16(HOST_CMD_802_11_ASSOCIATE);
/* Save so we know which BSS Desc to use in the response handler */
priv->attempted_bss_desc = bss_desc;
memcpy(assoc->peer_sta_addr,
bss_desc->mac_address, sizeof(assoc->peer_sta_addr));
pos += sizeof(assoc->peer_sta_addr);
/* Set the listen interval */
assoc->listen_interval = cpu_to_le16(priv->listen_interval);
/* Set the beacon period */
assoc->beacon_period = cpu_to_le16(bss_desc->beacon_period);
pos += sizeof(assoc->cap_info_bitmap);
pos += sizeof(assoc->listen_interval);
pos += sizeof(assoc->beacon_period);
pos += sizeof(assoc->dtim_period);
host_mlme_tlv = (struct nxpwifi_ie_types_host_mlme *)pos;
host_mlme_tlv->header.type = cpu_to_le16(TLV_TYPE_HOST_MLME);
host_mlme_tlv->header.len = cpu_to_le16(sizeof(host_mlme_tlv->host_mlme));
host_mlme_tlv->host_mlme = 1;
pos += sizeof(host_mlme_tlv->header) + sizeof(host_mlme_tlv->host_mlme);
ssid_tlv = (struct nxpwifi_ie_types_ssid_param_set *)pos;
ssid_tlv->header.type = cpu_to_le16(WLAN_EID_SSID);
ssid_tlv->header.len = cpu_to_le16((u16)bss_desc->ssid.ssid_len);
memcpy(ssid_tlv->ssid, bss_desc->ssid.ssid,
le16_to_cpu(ssid_tlv->header.len));
pos += sizeof(ssid_tlv->header) + le16_to_cpu(ssid_tlv->header.len);
phy_tlv = (struct nxpwifi_ie_types_phy_param_set *)pos;
phy_tlv->header.type = cpu_to_le16(WLAN_EID_DS_PARAMS);
phy_tlv->header.len = cpu_to_le16(sizeof(phy_tlv->fh_ds.ds_param_set));
memcpy(&phy_tlv->fh_ds.ds_param_set,
&bss_desc->phy_param_set.ds_param_set.current_chan,
sizeof(phy_tlv->fh_ds.ds_param_set));
pos += sizeof(phy_tlv->header) + le16_to_cpu(phy_tlv->header.len);
ss_tlv = (struct nxpwifi_ie_types_ss_param_set *)pos;
ss_tlv->header.type = cpu_to_le16(WLAN_EID_CF_PARAMS);
ss_tlv->header.len = cpu_to_le16(sizeof(ss_tlv->cf_ibss.cf_param_set));
pos += sizeof(ss_tlv->header) + le16_to_cpu(ss_tlv->header.len);
/* Get the common rates supported between the driver and the BSS Desc */
ret = nxpwifi_setup_rates_from_bssdesc(priv, bss_desc,
rates, &rates_size);
if (ret)
return ret;
/* Save the data rates into Current BSS state structure */
priv->curr_bss_params.num_of_rates = rates_size;
memcpy(&priv->curr_bss_params.data_rates, rates, rates_size);
/* Setup the Rates TLV in the association command */
rates_tlv = (struct nxpwifi_ie_types_rates_param_set *)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);
pos += sizeof(rates_tlv->header) + rates_size;
nxpwifi_dbg(adapter, INFO, "info: ASSOC_CMD: rates size = %d\n",
rates_size);
/* Add the Authentication type */
auth_tlv = (struct nxpwifi_ie_types_auth_type *)pos;
auth_tlv->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE);
auth_tlv->header.len = cpu_to_le16(sizeof(auth_tlv->auth_type));
if (priv->sec_info.wep_enabled)
auth_tlv->auth_type =
cpu_to_le16((u16)priv->sec_info.authentication_mode);
else
auth_tlv->auth_type = cpu_to_le16(NL80211_AUTHTYPE_OPEN_SYSTEM);
pos += sizeof(auth_tlv->header) + le16_to_cpu(auth_tlv->header.len);
if (priv->sec_info.authentication_mode == WLAN_AUTH_SAE) {
auth_tlv->auth_type = cpu_to_le16(NXPWIFI_AUTHTYPE_SAE);
if (bss_desc->bcn_rsnx_ie &&
bss_desc->bcn_rsnx_ie->datalen &&
(bss_desc->bcn_rsnx_ie->data[0] &
WLAN_RSNX_CAPA_SAE_H2E)) {
sae_pwe_tlv =
(struct nxpwifi_ie_types_sae_pwe_mode *)pos;
sae_pwe_tlv->header.type =
cpu_to_le16(TLV_TYPE_SAE_PWE_MODE);
sae_pwe_tlv->header.len =
cpu_to_le16(sizeof(sae_pwe_tlv->pwe[0]));
sae_pwe_tlv->pwe[0] = bss_desc->bcn_rsnx_ie->data[0];
pos += sizeof(sae_pwe_tlv->header) +
sizeof(sae_pwe_tlv->pwe[0]);
}
}
if (IS_SUPPORT_MULTI_BANDS(adapter) &&
!(ISSUPP_11NENABLED(adapter->fw_cap_info) &&
!bss_desc->disable_11n &&
(priv->config_bands & BAND_GN ||
priv->config_bands & BAND_AN) &&
bss_desc->bcn_ht_cap)) {
/*
* Append a channel TLV for the channel the attempted AP was
* found on
*/
chan_tlv = (struct nxpwifi_ie_types_chan_list_param_set *)pos;
chan_tlv->header.type = cpu_to_le16(TLV_TYPE_CHANLIST);
chan_tlv->header.len =
cpu_to_le16(sizeof(struct nxpwifi_chan_scan_param_set));
memset(chan_tlv->chan_scan_param, 0x00,
sizeof(struct nxpwifi_chan_scan_param_set));
chan_tlv->chan_scan_param[0].chan_number =
(bss_desc->phy_param_set.ds_param_set.current_chan);
nxpwifi_dbg(adapter, INFO, "info: Assoc: TLV Chan = %d\n",
chan_tlv->chan_scan_param[0].chan_number);
chan_tlv->chan_scan_param[0].band_cfg =
nxpwifi_band_to_radio_type((u8)bss_desc->bss_band);
nxpwifi_dbg(adapter, INFO, "info: Assoc: TLV Band = %d\n",
chan_tlv->chan_scan_param[0].band_cfg);
pos += sizeof(chan_tlv->header) +
sizeof(struct nxpwifi_chan_scan_param_set);
}
if (!priv->wps.session_enable) {
if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled)
rsn_ie_len = nxpwifi_append_rsn_ie_wpa_wpa2(priv, &pos);
if (rsn_ie_len == -ENOMEM)
return -ENOMEM;
}
if (ISSUPP_11NENABLED(adapter->fw_cap_info) &&
!bss_desc->disable_11n &&
(priv->config_bands & BAND_GN ||
priv->config_bands & BAND_AN))
nxpwifi_cmd_append_11n_tlv(priv, bss_desc, &pos);
if (ISSUPP_11ACENABLED(adapter->fw_cap_info) &&
!bss_desc->disable_11n && !bss_desc->disable_11ac &&
(priv->config_bands & BAND_GAC ||
priv->config_bands & BAND_AAC))
nxpwifi_cmd_append_11ac_tlv(priv, bss_desc, &pos);
if (ISSUPP_11AXENABLED(adapter->fw_cap_ext) &&
nxpwifi_11ax_bandconfig_allowed(priv, bss_desc))
nxpwifi_cmd_append_11ax_tlv(priv, bss_desc, &pos);
/* Append vendor specific element TLV */
nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_ASSOC, &pos);
nxpwifi_wmm_process_association_req(priv, &pos, &bss_desc->wmm_ie,
bss_desc->bcn_ht_cap);
if (priv->wps.session_enable && priv->wps_ie_len)
nxpwifi_cmd_append_wps_ie(priv, &pos);
nxpwifi_cmd_append_generic_ie(priv, &pos);
nxpwifi_cmd_append_tsf_tlv(priv, &pos, bss_desc);
nxpwifi_11h_process_join(priv, &pos, bss_desc);
cmd->size = cpu_to_le16((u16)(pos - (u8 *)assoc) + S_DS_GEN);
/* Set the Capability info at last */
tmp_cap = bss_desc->cap_info_bitmap;
if (priv->config_bands == BAND_B)
tmp_cap &= ~WLAN_CAPABILITY_SHORT_SLOT_TIME;
tmp_cap &= CAPINFO_MASK;
nxpwifi_dbg(adapter, INFO,
"info: ASSOC_CMD: tmp_cap=%4X CAPINFO_MASK=%4lX\n",
tmp_cap, CAPINFO_MASK);
assoc->cap_info_bitmap = cpu_to_le16(tmp_cap);
return ret;
}
static const char *assoc_failure_reason_to_str(u16 cap_info)
{
switch (cap_info) {
case CONNECT_ERR_AUTH_ERR_STA_FAILURE:
return "CONNECT_ERR_AUTH_ERR_STA_FAILURE";
case CONNECT_ERR_AUTH_MSG_UNHANDLED:
return "CONNECT_ERR_AUTH_MSG_UNHANDLED";
case CONNECT_ERR_ASSOC_ERR_TIMEOUT:
return "CONNECT_ERR_ASSOC_ERR_TIMEOUT";
case CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED:
return "CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED";
case CONNECT_ERR_STA_FAILURE:
return "CONNECT_ERR_STA_FAILURE";
}
return "Unknown connect failure";
}
/*
* Handle association command response.
* Parse cap_info/status_code/AID and copy IEs, update connection state,
* WMM and HT/HE parameters, queues and filters. On failure, return the
* IEEE status code or a mapped timeout error.
*/
int nxpwifi_ret_802_11_associate(struct nxpwifi_private *priv,
struct host_cmd_ds_command *resp)
{
struct nxpwifi_adapter *adapter = priv->adapter;
int ret = 0;
struct ieee_types_assoc_rsp *assoc_rsp;
struct nxpwifi_bssdescriptor *bss_desc;
bool enable_data = true;
u16 cap_info, status_code, aid;
const u8 *ie_ptr;
struct ieee80211_ht_operation *assoc_resp_ht_oper;
struct ieee80211_mgmt *hdr;
if (!priv->attempted_bss_desc) {
nxpwifi_dbg(adapter, ERROR,
"%s: failed, association terminated by host\n",
__func__);
goto done;
}
hdr = (struct ieee80211_mgmt *)&resp->params;
if (!memcmp(hdr->bssid, priv->attempted_bss_desc->mac_address,
ETH_ALEN))
assoc_rsp = (struct ieee_types_assoc_rsp *)&hdr->u.assoc_resp;
else
assoc_rsp = (struct ieee_types_assoc_rsp *)&resp->params;
cap_info = le16_to_cpu(assoc_rsp->cap_info_bitmap);
status_code = le16_to_cpu(assoc_rsp->status_code);
aid = le16_to_cpu(assoc_rsp->a_id);
if ((aid & (BIT(15) | BIT(14))) != (BIT(15) | BIT(14)))
nxpwifi_dbg(adapter, ERROR,
"invalid AID value 0x%x; bits 15:14 not set\n", aid);
aid &= ~(BIT(15) | BIT(14));
priv->assoc_rsp_size = min(le16_to_cpu(resp->size) - S_DS_GEN,
sizeof(priv->assoc_rsp_buf));
assoc_rsp->a_id = cpu_to_le16(aid);
memcpy(priv->assoc_rsp_buf, &resp->params, priv->assoc_rsp_size);
if (status_code) {
adapter->dbg.num_cmd_assoc_failure++;
nxpwifi_dbg(adapter, ERROR,
"ASSOC_RESP: failed,\t"
"status code=%d err=%#x a_id=%#x\n",
status_code, cap_info,
le16_to_cpu(assoc_rsp->a_id));
nxpwifi_dbg(adapter, ERROR, "assoc failure: reason %s\n",
assoc_failure_reason_to_str(cap_info));
if (cap_info == CONNECT_ERR_ASSOC_ERR_TIMEOUT) {
if (status_code == NXPWIFI_ASSOC_CMD_FAILURE_AUTH) {
ret = WLAN_STATUS_AUTH_TIMEOUT;
nxpwifi_dbg(adapter, ERROR,
"ASSOC_RESP: AUTH timeout\n");
} else {
ret = WLAN_STATUS_UNSPECIFIED_FAILURE;
nxpwifi_dbg(adapter, ERROR,
"ASSOC_RESP: UNSPECIFIED failure\n");
}
priv->assoc_rsp_size = 0;
} else {
ret = status_code;
}
goto done;
}
/* Send a Media Connected event, according to the Spec */
priv->media_connected = true;
adapter->ps_state = PS_STATE_AWAKE;
adapter->pps_uapsd_mode = false;
adapter->tx_lock_flag = false;
/* Set the attempted BSSID Index to current */
bss_desc = priv->attempted_bss_desc;
nxpwifi_dbg(adapter, INFO, "info: ASSOC_RESP: %s\n",
bss_desc->ssid.ssid);
/* Make a copy of current BSSID descriptor */
memcpy(&priv->curr_bss_params.bss_descriptor,
bss_desc, sizeof(struct nxpwifi_bssdescriptor));
/* Update curr_bss_params */
priv->curr_bss_params.bss_descriptor.channel =
bss_desc->phy_param_set.ds_param_set.current_chan;
priv->curr_bss_params.band = (u8)bss_desc->bss_band;
if (bss_desc->wmm_ie.element_id == WLAN_EID_VENDOR_SPECIFIC)
priv->curr_bss_params.wmm_enabled = true;
else
priv->curr_bss_params.wmm_enabled = false;
if ((priv->wmm_required || bss_desc->bcn_ht_cap) &&
priv->curr_bss_params.wmm_enabled)
priv->wmm_enabled = true;
else
priv->wmm_enabled = false;
priv->curr_bss_params.wmm_uapsd_enabled = false;
priv->curr_bss_params.wmm_uapsd_enabled = priv->wmm_enabled &&
(bss_desc->wmm_ie.qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD);
/* Store the bandwidth information from assoc response */
ie_ptr = cfg80211_find_ie(WLAN_EID_HT_OPERATION, assoc_rsp->ie_buffer,
priv->assoc_rsp_size
- sizeof(struct ieee_types_assoc_rsp));
if (ie_ptr) {
assoc_resp_ht_oper = (struct ieee80211_ht_operation *)(ie_ptr
+ sizeof(struct element));
priv->assoc_resp_ht_param = assoc_resp_ht_oper->ht_param;
priv->ht_param_present = true;
} else {
priv->ht_param_present = false;
}
nxpwifi_dbg(adapter, INFO,
"info: ASSOC_RESP: curr_pkt_filter is %#x\n",
priv->curr_pkt_filter);
if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled)
priv->wpa_is_gtk_set = false;
if (priv->wmm_enabled) {
/* Don't re-enable carrier until we get the WMM_GET_STATUS event */
enable_data = false;
} else {
/* Since WMM is not enabled, setup the queues with the defaults */
nxpwifi_wmm_setup_queue_priorities(priv, NULL);
nxpwifi_wmm_setup_ac_downgrade(priv);
}
if (enable_data)
nxpwifi_dbg(adapter, INFO,
"info: post association, re-enabling data flow\n");
/* Reset SNR/NF/RSSI values */
priv->data_rssi_last = 0;
priv->data_nf_last = 0;
priv->data_rssi_avg = 0;
priv->data_nf_avg = 0;
priv->bcn_rssi_last = 0;
priv->bcn_nf_last = 0;
priv->bcn_rssi_avg = 0;
priv->bcn_nf_avg = 0;
priv->rxpd_rate = 0;
priv->rxpd_htinfo = 0;
nxpwifi_save_curr_bcn(priv);
adapter->dbg.num_cmd_assoc_success++;
nxpwifi_dbg(adapter, MSG, "assoc: associated with %pM\n",
priv->attempted_bss_desc->mac_address);
/* Add the ra_list here for infra mode as there will be only 1 ra always */
nxpwifi_ralist_add(priv,
priv->curr_bss_params.bss_descriptor.mac_address);
netif_carrier_on(priv->netdev);
nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter);
if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled)
priv->scan_block = true;
else
priv->port_open = true;
done:
/* Need to indicate IOCTL complete */
if (adapter->curr_cmd->wait_q_enabled) {
if (ret)
adapter->cmd_wait_q.status = -1;
else
adapter->cmd_wait_q.status = 0;
}
return ret;
}
/* Associate to the specified BSS (STA only) */
int nxpwifi_associate(struct nxpwifi_private *priv,
struct nxpwifi_bssdescriptor *bss_desc)
{
/*
* Return error if the adapter is not STA role or table entry
* is not marked as infra.
*/
if ((GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA) ||
bss_desc->bss_mode != NL80211_IFTYPE_STATION)
return -EINVAL;
if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) &&
!bss_desc->disable_11n && !bss_desc->disable_11ac &&
priv->config_bands & BAND_AAC)
nxpwifi_set_11ac_ba_params(priv);
else
nxpwifi_set_ba_params(priv);
/*
* Clear any past association response stored for application
* retrieval
*/
priv->assoc_rsp_size = 0;
return nxpwifi_send_cmd(priv, HOST_CMD_802_11_ASSOCIATE,
HOST_ACT_GEN_SET, 0, bss_desc, true);
}
/* Send deauth to disconnect from an infrastructure BSS */
static int nxpwifi_deauthenticate_infra(struct nxpwifi_private *priv, u8 *mac)
{
u8 mac_address[ETH_ALEN];
int ret;
if (!mac || is_zero_ether_addr(mac))
memcpy(mac_address,
priv->curr_bss_params.bss_descriptor.mac_address,
ETH_ALEN);
else
memcpy(mac_address, mac, ETH_ALEN);
ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_DEAUTHENTICATE,
HOST_ACT_GEN_SET, 0, mac_address, true);
return ret;
}
/* Disconnect from the current BSS (STA/P2P/AP) */
int nxpwifi_deauthenticate(struct nxpwifi_private *priv, u8 *mac)
{
int ret = 0;
if (!priv->media_connected)
return 0;
priv->auth_flag = 0;
priv->auth_alg = WLAN_AUTH_NONE;
priv->host_mlme_reg = false;
priv->mgmt_frame_mask = 0;
ret = nxpwifi_send_cmd(priv, HOST_CMD_MGMT_FRAME_REG,
HOST_ACT_GEN_SET, 0,
&priv->mgmt_frame_mask, false);
if (ret) {
nxpwifi_dbg(priv->adapter, ERROR,
"could not unregister mgmt frame rx\n");
return ret;
}
switch (priv->bss_mode) {
case NL80211_IFTYPE_STATION:
ret = nxpwifi_deauthenticate_infra(priv, mac);
if (ret)
cfg80211_disconnected(priv->netdev, 0, NULL, 0,
true, GFP_KERNEL);
break;
case NL80211_IFTYPE_AP:
ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP,
HOST_ACT_GEN_SET, 0, NULL, true);
break;
default:
break;
}
return ret;
}
/* Deauthenticate/disconnect from all BSS. */
void nxpwifi_deauthenticate_all(struct nxpwifi_adapter *adapter)
{
struct nxpwifi_private *priv;
int i;
for (i = 0; i < adapter->priv_num; i++) {
priv = adapter->priv[i];
nxpwifi_deauthenticate(priv, NULL);
}
}
EXPORT_SYMBOL_GPL(nxpwifi_deauthenticate_all);
/* Convert band to radio type used in channel TLV. */
u8 nxpwifi_band_to_radio_type(u16 config_bands)
{
if (config_bands & BAND_A || config_bands & BAND_AN ||
config_bands & BAND_AAC || config_bands & BAND_AAX)
return HOST_SCAN_RADIO_TYPE_A;
return HOST_SCAN_RADIO_TYPE_BG;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,340 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* NXP Wireless LAN device driver: SDIO specific definitions
*
* Copyright 2011-2024 NXP
*/
#ifndef _NXPWIFI_SDIO_H
#define _NXPWIFI_SDIO_H
#include "main.h"
#define IW61X_SDIO_FW_NAME "nxp/sd_w61x_v1.bin.se"
#define BLOCK_MODE 1
#define BYTE_MODE 0
#define NXPWIFI_SDIO_IO_PORT_MASK 0xfffff
#define NXPWIFI_SDIO_BYTE_MODE_MASK 0x80000000
#define NXPWIFI_MAX_FUNC2_REG_NUM 13
#define NXPWIFI_SDIO_SCRATCH_SIZE 10
#define SDIO_MPA_ADDR_BASE 0x1000
#define CMD_PORT_UPLD_INT_MASK (0x1U << 6)
#define CMD_PORT_DNLD_INT_MASK (0x1U << 7)
#define HOST_TERM_CMD53 (0x1U << 2)
#define REG_PORT 0
#define MEM_PORT 0x10000
#define CMD53_NEW_MODE (0x1U << 0)
#define CMD_PORT_RD_LEN_EN (0x1U << 2)
#define CMD_PORT_AUTO_EN (0x1U << 0)
#define CMD_PORT_SLCT 0x8000
#define UP_LD_CMD_PORT_HOST_INT_STATUS (0x40U)
#define DN_LD_CMD_PORT_HOST_INT_STATUS (0x80U)
#define NXPWIFI_MP_AGGR_BSIZE_32K (32768)
/* we leave one block of 256 bytes for DMA alignment*/
#define NXPWIFI_MP_AGGR_BSIZE_MAX (65280)
/* Misc. Config Register : Auto Re-enable interrupts */
#define AUTO_RE_ENABLE_INT BIT(4)
/* Host Control Registers : Configuration */
#define CONFIGURATION_REG 0x00
/* Host Control Registers : Host power up */
#define HOST_POWER_UP (0x1U << 1)
/* Host Control Registers : Upload host interrupt mask */
#define UP_LD_HOST_INT_MASK (0x1U)
/* Host Control Registers : Download host interrupt mask */
#define DN_LD_HOST_INT_MASK (0x2U)
/* Host Control Registers : Upload host interrupt status */
#define UP_LD_HOST_INT_STATUS (0x1U)
/* Host Control Registers : Download host interrupt status */
#define DN_LD_HOST_INT_STATUS (0x2U)
/* Host Control Registers : Host interrupt status */
#define CARD_INT_STATUS_REG 0x28
/* Card Control Registers : Card I/O ready */
#define CARD_IO_READY (0x1U << 3)
/* Card Control Registers : Download card ready */
#define DN_LD_CARD_RDY (0x1U << 0)
/* Max retry number of CMD53 write */
#define MAX_WRITE_IOMEM_RETRY 2
/* SDIO Tx aggregation in progress ? */
#define MP_TX_AGGR_IN_PROGRESS(a) ((a)->mpa_tx.pkt_cnt > 0)
/* SDIO Tx aggregation buffer room for next packet ? */
#define MP_TX_AGGR_BUF_HAS_ROOM(a, len) ({ \
typeof(a) (_a) = a; \
(((_a)->mpa_tx.buf_len + (len)) <= (_a)->mpa_tx.buf_size); \
})
/* Copy current packet (SDIO Tx aggregation buffer) to SDIO buffer */
#define MP_TX_AGGR_BUF_PUT(a, payload, pkt_len, port) do { \
typeof(a) (_a) = (a); \
typeof(pkt_len) (_pkt_len) = pkt_len; \
typeof(port) (_port) = port; \
memmove(&(_a)->mpa_tx.buf[(_a)->mpa_tx.buf_len], \
payload, (_pkt_len)); \
(_a)->mpa_tx.buf_len += (_pkt_len); \
if (!(_a)->mpa_tx.pkt_cnt) \
(_a)->mpa_tx.start_port = (_port); \
if ((_a)->mpa_tx.start_port <= (_port)) \
(_a)->mpa_tx.ports |= (1 << ((_a)->mpa_tx.pkt_cnt)); \
else \
(_a)->mpa_tx.ports |= (1 << ((_a)->mpa_tx.pkt_cnt + 1 + \
((_a)->max_ports - \
(_a)->mp_end_port))); \
(_a)->mpa_tx.pkt_cnt++; \
} while (0)
/* SDIO Tx aggregation limit ? */
#define MP_TX_AGGR_PKT_LIMIT_REACHED(a) ({ \
typeof(a) (_a) = a; \
((_a)->mpa_tx.pkt_cnt == (_a)->mpa_tx.pkt_aggr_limit); \
})
/* Reset SDIO Tx aggregation buffer parameters */
#define MP_TX_AGGR_BUF_RESET(a) do { \
typeof(a) (_a) = (a); \
(_a)->mpa_tx.pkt_cnt = 0; \
(_a)->mpa_tx.buf_len = 0; \
(_a)->mpa_tx.ports = 0; \
(_a)->mpa_tx.start_port = 0; \
} while (0)
/* SDIO Rx aggregation limit ? */
#define MP_RX_AGGR_PKT_LIMIT_REACHED(a) ({ \
typeof(a) (_a) = a; \
((_a)->mpa_rx.pkt_cnt == (_a)->mpa_rx.pkt_aggr_limit); \
})
/* SDIO Rx aggregation in progress ? */
#define MP_RX_AGGR_IN_PROGRESS(a) ((a)->mpa_rx.pkt_cnt > 0)
/* SDIO Rx aggregation buffer room for next packet ? */
#define MP_RX_AGGR_BUF_HAS_ROOM(a, rx_len) ({ \
typeof(a) (_a) = a; \
((((_a)->mpa_rx.buf_len + (rx_len))) <= (_a)->mpa_rx.buf_size); \
})
/* Reset SDIO Rx aggregation buffer parameters */
#define MP_RX_AGGR_BUF_RESET(a) do { \
typeof(a) (_a) = (a); \
(_a)->mpa_rx.pkt_cnt = 0; \
(_a)->mpa_rx.buf_len = 0; \
(_a)->mpa_rx.ports = 0; \
(_a)->mpa_rx.start_port = 0; \
} while (0)
/* data structure for SDIO MPA TX */
struct nxpwifi_sdio_mpa_tx {
/* multiport tx aggregation buffer pointer */
u8 *buf;
u32 buf_len;
u32 pkt_cnt;
u32 ports;
u16 start_port;
u8 enabled;
u32 buf_size;
u32 pkt_aggr_limit;
};
struct nxpwifi_sdio_mpa_rx {
u8 *buf;
u32 buf_len;
u32 pkt_cnt;
u32 ports;
u16 start_port;
u32 *len_arr;
u8 enabled;
u32 buf_size;
u32 pkt_aggr_limit;
};
int nxpwifi_bus_register(void);
void nxpwifi_bus_unregister(void);
struct nxpwifi_sdio_card_reg {
u8 start_rd_port;
u8 start_wr_port;
u8 base_0_reg;
u8 base_1_reg;
u8 poll_reg;
u8 host_int_enable;
u8 host_int_rsr_reg;
u8 host_int_status_reg;
u8 host_int_mask_reg;
u8 host_strap_reg;
u8 host_strap_mask;
u8 host_strap_value;
u8 status_reg_0;
u8 status_reg_1;
u8 sdio_int_mask;
u32 data_port_mask;
u8 io_port_0_reg;
u8 io_port_1_reg;
u8 io_port_2_reg;
u8 max_mp_regs;
u8 rd_bitmap_l;
u8 rd_bitmap_u;
u8 rd_bitmap_1l;
u8 rd_bitmap_1u;
u8 wr_bitmap_l;
u8 wr_bitmap_u;
u8 wr_bitmap_1l;
u8 wr_bitmap_1u;
u8 rd_len_p0_l;
u8 rd_len_p0_u;
u8 card_misc_cfg_reg;
u8 card_cfg_2_1_reg;
u8 cmd_rd_len_0;
u8 cmd_rd_len_1;
u8 cmd_rd_len_2;
u8 cmd_rd_len_3;
u8 cmd_cfg_0;
u8 cmd_cfg_1;
u8 cmd_cfg_2;
u8 cmd_cfg_3;
u8 fw_dump_host_ready;
u8 fw_dump_ctrl;
u8 fw_dump_start;
u8 fw_dump_end;
u8 func1_dump_reg_start;
u8 func1_dump_reg_end;
u8 func1_scratch_reg;
u8 func1_spec_reg_num;
u8 func1_spec_reg_table[NXPWIFI_MAX_FUNC2_REG_NUM];
};
struct sdio_mmc_card {
struct sdio_func *func;
struct nxpwifi_adapter *adapter;
struct completion fw_done;
const char *firmware;
const char *firmware_sdiouart;
const struct nxpwifi_sdio_card_reg *reg;
u8 max_ports;
u8 mp_agg_pkt_limit;
u16 tx_buf_size;
u32 mp_tx_agg_buf_size;
u32 mp_rx_agg_buf_size;
u32 mp_rd_bitmap;
u32 mp_wr_bitmap;
u16 mp_end_port;
u32 mp_data_port_mask;
u8 curr_rd_port;
u8 curr_wr_port;
u8 *mp_regs;
bool can_dump_fw;
bool fw_dump_enh;
bool can_ext_scan;
struct nxpwifi_sdio_mpa_tx mpa_tx;
struct nxpwifi_sdio_mpa_rx mpa_rx;
struct work_struct work;
unsigned long work_flags;
};
struct nxpwifi_sdio_device {
const char *firmware;
const char *firmware_sdiouart;
const struct nxpwifi_sdio_card_reg *reg;
u8 max_ports;
u8 mp_agg_pkt_limit;
u16 tx_buf_size;
u32 mp_tx_agg_buf_size;
u32 mp_rx_agg_buf_size;
bool can_dump_fw;
bool fw_dump_enh;
bool can_ext_scan;
};
/* .cmdrsp_complete handler
*/
static inline int nxpwifi_sdio_cmdrsp_complete(struct nxpwifi_adapter *adapter,
struct sk_buff *skb)
{
dev_kfree_skb_any(skb);
return 0;
}
/* .event_complete handler
*/
static inline int nxpwifi_sdio_event_complete(struct nxpwifi_adapter *adapter,
struct sk_buff *skb)
{
dev_kfree_skb_any(skb);
return 0;
}
static inline bool
mp_rx_aggr_port_limit_reached(struct sdio_mmc_card *card)
{
u8 tmp;
if (card->curr_rd_port < card->mpa_rx.start_port) {
tmp = card->mp_end_port >> 1;
if (((card->max_ports - card->mpa_rx.start_port) +
card->curr_rd_port) >= tmp)
return true;
}
if ((card->curr_rd_port - card->mpa_rx.start_port) >=
(card->mp_end_port >> 1))
return true;
return false;
}
static inline bool
mp_tx_aggr_port_limit_reached(struct sdio_mmc_card *card)
{
u16 tmp;
if (card->curr_wr_port < card->mpa_tx.start_port) {
tmp = card->mp_end_port >> 1;
if (((card->max_ports - card->mpa_tx.start_port) +
card->curr_wr_port) >= tmp)
return true;
}
if ((card->curr_wr_port - card->mpa_tx.start_port) >=
(card->mp_end_port >> 1))
return true;
return false;
}
/* Prepare to copy current packet from card to SDIO Rx aggregation buffer */
static inline void mp_rx_aggr_setup(struct sdio_mmc_card *card,
u16 rx_len, u8 port)
{
card->mpa_rx.buf_len += rx_len;
if (!card->mpa_rx.pkt_cnt)
card->mpa_rx.start_port = port;
card->mpa_rx.ports |= (1 << port);
card->mpa_rx.len_arr[card->mpa_rx.pkt_cnt] = rx_len;
card->mpa_rx.pkt_cnt++;
}
#endif /* _NXPWIFI_SDIO_H */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,862 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NXP Wireless LAN device driver: station event handling
*
* 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"
static int
nxpwifi_sta_event_link_lost(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
adapter->dbg.num_event_link_lost++;
if (priv->media_connected) {
adapter->priv_link_lost = priv;
adapter->host_mlme_link_lost = true;
nxpwifi_queue_wiphy_work(adapter,
&adapter->host_mlme_work);
}
return 0;
}
static int
nxpwifi_sta_event_link_sensed(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
netif_carrier_on(priv->netdev);
nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter);
return 0;
}
static int
nxpwifi_sta_event_deauthenticated(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
if (priv->wps.session_enable) {
nxpwifi_dbg(adapter, INFO,
"info: receive deauth event in wps session\n");
} else {
adapter->dbg.num_event_deauth++;
if (priv->media_connected) {
priv->last_deauth_reason =
get_unaligned_le16(priv->adapter->event_body);
nxpwifi_queue_wiphy_work(priv->adapter,
&priv->reset_conn_state_work);
}
}
return 0;
}
static int
nxpwifi_sta_event_disassociated(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
if (priv->wps.session_enable) {
nxpwifi_dbg(adapter, INFO,
"info: receive disassoc event in wps session\n");
} else {
adapter->dbg.num_event_disassoc++;
if (priv->media_connected) {
priv->last_deauth_reason =
get_unaligned_le16(priv->adapter->event_body);
nxpwifi_queue_wiphy_work(priv->adapter,
&priv->reset_conn_state_work);
}
}
return 0;
}
static int
nxpwifi_sta_event_ps_awake(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
if (!adapter->pps_uapsd_mode &&
priv->port_open &&
priv->media_connected && adapter->sleep_period.period) {
adapter->pps_uapsd_mode = true;
nxpwifi_dbg(adapter, EVENT,
"event: PPS/UAPSD mode activated\n");
}
adapter->tx_lock_flag = false;
if (adapter->pps_uapsd_mode && adapter->gen_null_pkt) {
if (nxpwifi_check_last_packet_indication(priv)) {
if (adapter->data_sent) {
adapter->ps_state = PS_STATE_AWAKE;
adapter->pm_wakeup_card_req = false;
adapter->pm_wakeup_fw_try = false;
timer_delete(&adapter->wakeup_timer);
} else {
if (!nxpwifi_send_null_packet
(priv,
NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET |
NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET))
adapter->ps_state = PS_STATE_SLEEP;
}
return 0;
}
}
adapter->ps_state = PS_STATE_AWAKE;
adapter->pm_wakeup_card_req = false;
adapter->pm_wakeup_fw_try = false;
timer_delete(&adapter->wakeup_timer);
return 0;
}
static int
nxpwifi_sta_event_ps_sleep(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
adapter->ps_state = PS_STATE_PRE_SLEEP;
nxpwifi_check_ps_cond(adapter);
return 0;
}
static int
nxpwifi_sta_event_mic_err_multicast(struct nxpwifi_private *priv)
{
cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid,
NL80211_KEYTYPE_GROUP,
-1, NULL, GFP_KERNEL);
return 0;
}
static int
nxpwifi_sta_event_mic_err_unicast(struct nxpwifi_private *priv)
{
cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid,
NL80211_KEYTYPE_PAIRWISE,
-1, NULL, GFP_KERNEL);
return 0;
}
static int
nxpwifi_sta_event_deep_sleep_awake(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
adapter->if_ops.wakeup_complete(adapter);
if (adapter->is_deep_sleep)
adapter->is_deep_sleep = false;
return 0;
}
static int
nxpwifi_sta_event_wmm_status_change(struct nxpwifi_private *priv)
{
return nxpwifi_send_cmd(priv, HOST_CMD_WMM_GET_STATUS,
0, 0, NULL, false);
}
static int
nxpwifi_sta_event_bs_scan_report(struct nxpwifi_private *priv)
{
return nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_QUERY,
HOST_ACT_GEN_GET, 0, NULL, false);
}
static int
nxpwifi_sta_event_rssi_low(struct nxpwifi_private *priv)
{
cfg80211_cqm_rssi_notify(priv->netdev,
NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW,
0, GFP_KERNEL);
priv->subsc_evt_rssi_state = RSSI_LOW_RECVD;
return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO,
HOST_ACT_GEN_GET, 0, NULL, false);
}
static int
nxpwifi_sta_event_rssi_high(struct nxpwifi_private *priv)
{
cfg80211_cqm_rssi_notify(priv->netdev,
NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH,
0, GFP_KERNEL);
priv->subsc_evt_rssi_state = RSSI_HIGH_RECVD;
return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO,
HOST_ACT_GEN_GET, 0, NULL, false);
}
static int
nxpwifi_sta_event_port_release(struct nxpwifi_private *priv)
{
priv->port_open = true;
return 0;
}
static int
nxpwifi_sta_event_addba(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
return nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_RSP,
HOST_ACT_GEN_SET, 0,
adapter->event_body, false);
}
static int
nxpwifi_sta_event_delba(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_11n_delete_ba_stream(priv, adapter->event_body);
return 0;
}
static int
nxpwifi_sta_event_bs_stream_timeout(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct host_cmd_ds_11n_batimeout *event =
(struct host_cmd_ds_11n_batimeout *)adapter->event_body;
nxpwifi_11n_ba_stream_timeout(priv, event);
return 0;
}
static int
nxpwifi_sta_event_amsdu_aggr_ctrl(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
u16 ctrl;
ctrl = get_unaligned_le16(adapter->event_body);
adapter->tx_buf_size = min_t(u16, adapter->curr_tx_buf_size, ctrl);
return 0;
}
static int
nxpwifi_sta_event_hs_act_req(struct nxpwifi_private *priv)
{
return nxpwifi_send_cmd(priv, HOST_CMD_802_11_HS_CFG_ENH,
0, 0, NULL, false);
}
static int
nxpwifi_sta_event_channel_switch_ann(struct nxpwifi_private *priv)
{
struct nxpwifi_bssdescriptor *bss_desc;
bss_desc = &priv->curr_bss_params.bss_descriptor;
priv->csa_expire_time = jiffies + msecs_to_jiffies(DFS_CHAN_MOVE_TIME);
priv->csa_chan = bss_desc->channel;
return nxpwifi_send_cmd(priv, HOST_CMD_802_11_DEAUTHENTICATE,
HOST_ACT_GEN_SET, 0,
bss_desc->mac_address, false);
}
static int
nxpwifi_sta_event_radar_detected(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
return nxpwifi_11h_handle_radar_detected(priv, adapter->event_skb);
}
static int
nxpwifi_sta_event_channel_report_rdy(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
return nxpwifi_11h_handle_chanrpt_ready(priv, adapter->event_skb);
}
static int
nxpwifi_sta_event_tx_data_pause(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_process_tx_pause_event(priv, adapter->event_skb);
return 0;
}
static int
nxpwifi_sta_event_ext_scan_report(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
void *buf = adapter->event_skb->data;
int ret = 0;
/*
* We intend to skip this event during suspend, but handle
* it in interface disabled case
*/
if (adapter->ext_scan && (!priv->scan_aborting ||
!netif_running(priv->netdev)))
ret = nxpwifi_handle_event_ext_scan_report(priv, buf);
return ret;
}
static int
nxpwifi_sta_event_rxba_sync(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_11n_rxba_sync_event(priv, adapter->event_body,
adapter->event_skb->len -
sizeof(adapter->event_cause));
return 0;
}
static int
nxpwifi_sta_event_remain_on_chan_expired(struct nxpwifi_private *priv)
{
if (priv->auth_flag & HOST_MLME_AUTH_PENDING) {
priv->auth_flag = 0;
priv->auth_alg = WLAN_AUTH_NONE;
} else {
cfg80211_remain_on_channel_expired(&priv->wdev,
priv->roc_cfg.cookie,
&priv->roc_cfg.chan,
GFP_ATOMIC);
}
memset(&priv->roc_cfg, 0x00, sizeof(struct nxpwifi_roc_cfg));
return 0;
}
static int
nxpwifi_sta_event_bg_scan_stopped(struct nxpwifi_private *priv)
{
cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0);
if (priv->sched_scanning)
priv->sched_scanning = false;
return 0;
}
static int
nxpwifi_sta_event_multi_chan_info(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_process_multi_chan_event(priv, adapter->event_skb);
return 0;
}
static int
nxpwifi_sta_event_tx_status_report(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_parse_tx_status_event(priv, adapter->event_body);
return 0;
}
static int
nxpwifi_sta_event_bt_coex_wlan_para_change(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
if (!adapter->ignore_btcoex_events)
nxpwifi_bt_coex_wlan_param_update_event(priv,
adapter->event_skb);
return 0;
}
static int
nxpwifi_sta_event_vdll_ind(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
return nxpwifi_process_vdll_event(priv, adapter->event_skb);
}
static const struct nxpwifi_evt_entry evt_table_sta[] = {
{.event_cause = EVENT_LINK_LOST,
.event_handler = nxpwifi_sta_event_link_lost},
{.event_cause = EVENT_LINK_SENSED,
.event_handler = nxpwifi_sta_event_link_sensed},
{.event_cause = EVENT_DEAUTHENTICATED,
.event_handler = nxpwifi_sta_event_deauthenticated},
{.event_cause = EVENT_DISASSOCIATED,
.event_handler = nxpwifi_sta_event_disassociated},
{.event_cause = EVENT_PS_AWAKE,
.event_handler = nxpwifi_sta_event_ps_awake},
{.event_cause = EVENT_PS_SLEEP,
.event_handler = nxpwifi_sta_event_ps_sleep},
{.event_cause = EVENT_MIC_ERR_MULTICAST,
.event_handler = nxpwifi_sta_event_mic_err_multicast},
{.event_cause = EVENT_MIC_ERR_UNICAST,
.event_handler = nxpwifi_sta_event_mic_err_unicast},
{.event_cause = EVENT_DEEP_SLEEP_AWAKE,
.event_handler = nxpwifi_sta_event_deep_sleep_awake},
{.event_cause = EVENT_WMM_STATUS_CHANGE,
.event_handler = nxpwifi_sta_event_wmm_status_change},
{.event_cause = EVENT_BG_SCAN_REPORT,
.event_handler = nxpwifi_sta_event_bs_scan_report},
{.event_cause = EVENT_RSSI_LOW,
.event_handler = nxpwifi_sta_event_rssi_low},
{.event_cause = EVENT_RSSI_HIGH,
.event_handler = nxpwifi_sta_event_rssi_high},
{.event_cause = EVENT_PORT_RELEASE,
.event_handler = nxpwifi_sta_event_port_release},
{.event_cause = EVENT_ADDBA,
.event_handler = nxpwifi_sta_event_addba},
{.event_cause = EVENT_DELBA,
.event_handler = nxpwifi_sta_event_delba},
{.event_cause = EVENT_BA_STREAM_TIEMOUT,
.event_handler = nxpwifi_sta_event_bs_stream_timeout},
{.event_cause = EVENT_AMSDU_AGGR_CTRL,
.event_handler = nxpwifi_sta_event_amsdu_aggr_ctrl},
{.event_cause = EVENT_HS_ACT_REQ,
.event_handler = nxpwifi_sta_event_hs_act_req},
{.event_cause = EVENT_CHANNEL_SWITCH_ANN,
.event_handler = nxpwifi_sta_event_channel_switch_ann},
{.event_cause = EVENT_RADAR_DETECTED,
.event_handler = nxpwifi_sta_event_radar_detected},
{.event_cause = EVENT_CHANNEL_REPORT_RDY,
.event_handler = nxpwifi_sta_event_channel_report_rdy},
{.event_cause = EVENT_TX_DATA_PAUSE,
.event_handler = nxpwifi_sta_event_tx_data_pause},
{.event_cause = EVENT_EXT_SCAN_REPORT,
.event_handler = nxpwifi_sta_event_ext_scan_report},
{.event_cause = EVENT_RXBA_SYNC,
.event_handler = nxpwifi_sta_event_rxba_sync},
{.event_cause = EVENT_REMAIN_ON_CHAN_EXPIRED,
.event_handler = nxpwifi_sta_event_remain_on_chan_expired},
{.event_cause = EVENT_BG_SCAN_STOPPED,
.event_handler = nxpwifi_sta_event_bg_scan_stopped},
{.event_cause = EVENT_MULTI_CHAN_INFO,
.event_handler = nxpwifi_sta_event_multi_chan_info},
{.event_cause = EVENT_TX_STATUS_REPORT,
.event_handler = nxpwifi_sta_event_tx_status_report},
{.event_cause = EVENT_BT_COEX_WLAN_PARA_CHANGE,
.event_handler = nxpwifi_sta_event_bt_coex_wlan_para_change},
{.event_cause = EVENT_VDLL_IND,
.event_handler = nxpwifi_sta_event_vdll_ind},
{.event_cause = EVENT_DUMMY_HOST_WAKEUP_SIGNAL,
.event_handler = NULL},
{.event_cause = EVENT_MIB_CHANGED,
.event_handler = NULL},
{.event_cause = EVENT_INIT_DONE,
.event_handler = NULL},
{.event_cause = EVENT_SNR_LOW,
.event_handler = NULL},
{.event_cause = EVENT_MAX_FAIL,
.event_handler = NULL},
{.event_cause = EVENT_SNR_HIGH,
.event_handler = NULL},
{.event_cause = EVENT_DATA_RSSI_LOW,
.event_handler = NULL},
{.event_cause = EVENT_DATA_SNR_LOW,
.event_handler = NULL},
{.event_cause = EVENT_DATA_RSSI_HIGH,
.event_handler = NULL},
{.event_cause = EVENT_DATA_SNR_HIGH,
.event_handler = NULL},
{.event_cause = EVENT_LINK_QUALITY,
.event_handler = NULL},
{.event_cause = EVENT_PRE_BEACON_LOST,
.event_handler = NULL},
{.event_cause = EVENT_WEP_ICV_ERR,
.event_handler = NULL},
{.event_cause = EVENT_BW_CHANGE,
.event_handler = NULL},
{.event_cause = EVENT_HOSTWAKE_STAIE,
.event_handler = NULL},
{.event_cause = EVENT_UNKNOWN_DEBUG,
.event_handler = NULL},
};
static void nxpwifi_process_uap_tx_pause(struct nxpwifi_private *priv,
struct nxpwifi_ie_types_header *tlv)
{
struct nxpwifi_tx_pause_tlv *tp;
struct nxpwifi_sta_node *sta_ptr;
tp = (void *)tlv;
nxpwifi_dbg(priv->adapter, EVENT,
"uap tx_pause: %pM pause=%d, pkts=%d\n",
tp->peermac, tp->tx_pause,
tp->pkt_cnt);
if (ether_addr_equal(tp->peermac, priv->netdev->dev_addr)) {
if (tp->tx_pause)
priv->port_open = false;
else
priv->port_open = true;
} else if (is_multicast_ether_addr(tp->peermac)) {
nxpwifi_update_ralist_tx_pause(priv, tp->peermac, tp->tx_pause);
} else {
rcu_read_lock();
sta_ptr = nxpwifi_get_sta_entry(priv, tp->peermac);
if (sta_ptr && sta_ptr->tx_pause != tp->tx_pause) {
sta_ptr->tx_pause = tp->tx_pause;
nxpwifi_update_ralist_tx_pause(priv, tp->peermac,
tp->tx_pause);
}
rcu_read_unlock();
}
}
static void nxpwifi_process_sta_tx_pause(struct nxpwifi_private *priv,
struct nxpwifi_ie_types_header *tlv)
{
struct nxpwifi_tx_pause_tlv *tp;
tp = (void *)tlv;
nxpwifi_dbg(priv->adapter, EVENT,
"sta tx_pause: %pM pause=%d, pkts=%d\n",
tp->peermac, tp->tx_pause,
tp->pkt_cnt);
if (ether_addr_equal(tp->peermac, priv->cfg_bssid)) {
if (tp->tx_pause)
priv->port_open = false;
else
priv->port_open = true;
}
}
/*
* Reset connection state after a firmware-triggered disconnect.
* Clears link state, queues, RSSI/SNR and security settings,
* saves previous SSID/BSSID for possible reassociation,
* and notifies cfg80211.
*/
void nxpwifi_reset_connect_state(struct nxpwifi_private *priv, u16 reason_code,
bool from_ap)
{
struct nxpwifi_adapter *adapter = priv->adapter;
if (!priv->media_connected)
return;
nxpwifi_dbg(adapter, INFO,
"info: handles disconnect event\n");
priv->media_connected = false;
priv->auth_flag = 0;
priv->auth_alg = WLAN_AUTH_NONE;
priv->scan_block = false;
priv->port_open = false;
/* Free Tx and Rx packets, report disconnect to upper layer */
nxpwifi_clean_txrx(priv);
/* Reset SNR/NF/RSSI values */
priv->data_rssi_last = 0;
priv->data_nf_last = 0;
priv->data_rssi_avg = 0;
priv->data_nf_avg = 0;
priv->bcn_rssi_last = 0;
priv->bcn_nf_last = 0;
priv->bcn_rssi_avg = 0;
priv->bcn_nf_avg = 0;
priv->rxpd_rate = 0;
priv->rxpd_htinfo = 0;
priv->sec_info.wpa_enabled = false;
priv->sec_info.wpa2_enabled = false;
priv->wpa_ie_len = 0;
priv->sec_info.encryption_mode = 0;
/* Enable auto data rate */
priv->is_data_rate_auto = true;
priv->data_rate = 0;
priv->assoc_resp_ht_param = 0;
priv->ht_param_present = false;
if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA ||
GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) && priv->hist_data)
nxpwifi_hist_data_reset(priv);
/*
* Memorize the previous SSID and BSSID so
* it could be used for re-assoc
*/
nxpwifi_dbg(adapter, INFO,
"info: previous SSID=%s, SSID len=%u\n",
priv->prev_ssid.ssid, priv->prev_ssid.ssid_len);
nxpwifi_dbg(adapter, INFO,
"info: current SSID=%s, SSID len=%u\n",
priv->curr_bss_params.bss_descriptor.ssid.ssid,
priv->curr_bss_params.bss_descriptor.ssid.ssid_len);
memcpy(&priv->prev_ssid,
&priv->curr_bss_params.bss_descriptor.ssid,
sizeof(struct cfg80211_ssid));
memcpy(priv->prev_bssid,
priv->curr_bss_params.bss_descriptor.mac_address, ETH_ALEN);
/* Need to erase the current SSID and BSSID info */
memset(&priv->curr_bss_params, 0x00, sizeof(priv->curr_bss_params));
adapter->tx_lock_flag = false;
adapter->pps_uapsd_mode = false;
if (test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags) &&
adapter->curr_cmd)
return;
priv->media_connected = false;
nxpwifi_dbg(adapter, MSG,
"info: successfully disconnected from %pM: reason code %d\n",
priv->cfg_bssid, reason_code);
if (priv->bss_mode == NL80211_IFTYPE_STATION) {
if (adapter->host_mlme_link_lost)
nxpwifi_host_mlme_disconnect(adapter->priv_link_lost,
reason_code, NULL);
else
cfg80211_disconnected(priv->netdev, reason_code, NULL,
0, !from_ap, GFP_KERNEL);
}
eth_zero_addr(priv->cfg_bssid);
nxpwifi_stop_net_dev_queue(priv->netdev, adapter);
netif_carrier_off(priv->netdev);
if (!ISSUPP_FIRMWARE_SUPPLICANT(priv->adapter->fw_cap_info))
return;
nxpwifi_send_cmd(priv, HOST_CMD_GTK_REKEY_OFFLOAD_CFG,
HOST_ACT_GEN_REMOVE, 0, NULL, false);
}
void nxpwifi_reset_conn_state_work(struct wiphy *wiphy, struct wiphy_work *work)
{
struct nxpwifi_private *priv = container_of(work,
struct nxpwifi_private,
reset_conn_state_work);
nxpwifi_reset_connect_state(priv, priv->last_deauth_reason, true);
}
void nxpwifi_process_multi_chan_event(struct nxpwifi_private *priv,
struct sk_buff *event_skb)
{
struct nxpwifi_ie_types_multi_chan_info *chan_info;
struct nxpwifi_ie_types_mc_group_info *grp_info;
struct nxpwifi_adapter *adapter = priv->adapter;
struct nxpwifi_ie_types_header *tlv;
u16 tlv_buf_left, tlv_type, tlv_len;
int intf_num, bss_type, bss_num, i;
struct nxpwifi_private *intf_priv;
tlv_buf_left = event_skb->len - sizeof(u32);
chan_info = (void *)event_skb->data + sizeof(u32);
if (le16_to_cpu(chan_info->header.type) != TLV_TYPE_MULTI_CHAN_INFO ||
tlv_buf_left < sizeof(struct nxpwifi_ie_types_multi_chan_info)) {
nxpwifi_dbg(adapter, ERROR,
"unknown TLV in chan_info event\n");
return;
}
adapter->usb_mc_status = le16_to_cpu(chan_info->status);
nxpwifi_dbg(adapter, EVENT, "multi chan operation %s\n",
adapter->usb_mc_status ? "started" : "over");
tlv_buf_left -= sizeof(struct nxpwifi_ie_types_multi_chan_info);
tlv = (struct nxpwifi_ie_types_header *)chan_info->tlv_buffer;
while (tlv_buf_left >= (int)sizeof(struct nxpwifi_ie_types_header)) {
tlv_type = le16_to_cpu(tlv->type);
tlv_len = le16_to_cpu(tlv->len);
if ((sizeof(struct nxpwifi_ie_types_header) + tlv_len) >
tlv_buf_left) {
nxpwifi_dbg(adapter, ERROR, "wrong tlv: tlvLen=%d,\t"
"tlvBufLeft=%d\n", tlv_len, tlv_buf_left);
break;
}
if (tlv_type != TLV_TYPE_MC_GROUP_INFO) {
nxpwifi_dbg(adapter, ERROR, "wrong tlv type: 0x%x\n",
tlv_type);
break;
}
grp_info = (struct nxpwifi_ie_types_mc_group_info *)tlv;
intf_num = grp_info->intf_num;
for (i = 0; i < intf_num; i++) {
bss_type = grp_info->bss_type_numlist[i] >> 4;
bss_num = grp_info->bss_type_numlist[i] & BSS_NUM_MASK;
intf_priv = nxpwifi_get_priv_by_id(adapter, bss_num,
bss_type);
if (!intf_priv) {
nxpwifi_dbg(adapter, ERROR,
"Invalid bss_type bss_num\t"
"in multi channel event\n");
continue;
}
}
tlv_buf_left -= sizeof(struct nxpwifi_ie_types_header) +
tlv_len;
tlv = (void *)((u8 *)tlv + tlv_len +
sizeof(struct nxpwifi_ie_types_header));
}
}
void nxpwifi_process_tx_pause_event(struct nxpwifi_private *priv,
struct sk_buff *event_skb)
{
struct nxpwifi_ie_types_header *tlv;
u16 tlv_type, tlv_len;
int tlv_buf_left;
if (!priv->media_connected) {
nxpwifi_dbg(priv->adapter, ERROR,
"tx_pause event while disconnected; bss_role=%d\n",
priv->bss_role);
return;
}
tlv_buf_left = event_skb->len - sizeof(u32);
tlv = (void *)event_skb->data + sizeof(u32);
while (tlv_buf_left >= (int)sizeof(struct nxpwifi_ie_types_header)) {
tlv_type = le16_to_cpu(tlv->type);
tlv_len = le16_to_cpu(tlv->len);
if ((sizeof(struct nxpwifi_ie_types_header) + tlv_len) >
tlv_buf_left) {
nxpwifi_dbg(priv->adapter, ERROR,
"wrong tlv: tlvLen=%d, tlvBufLeft=%d\n",
tlv_len, tlv_buf_left);
break;
}
if (tlv_type == TLV_TYPE_TX_PAUSE) {
if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA)
nxpwifi_process_sta_tx_pause(priv, tlv);
else
nxpwifi_process_uap_tx_pause(priv, tlv);
}
tlv_buf_left -= sizeof(struct nxpwifi_ie_types_header) +
tlv_len;
tlv = (void *)((u8 *)tlv + tlv_len +
sizeof(struct nxpwifi_ie_types_header));
}
}
/*
* Handle BT coexistence event. Parse TLVs and update
* coexistence aggregation window and scan timing parameters.
*/
void nxpwifi_bt_coex_wlan_param_update_event(struct nxpwifi_private *priv,
struct sk_buff *event_skb)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct nxpwifi_ie_types_header *tlv;
struct nxpwifi_ie_types_btcoex_aggr_win_size *winsizetlv;
struct nxpwifi_ie_types_btcoex_scan_time *scantlv;
s32 len = event_skb->len - sizeof(u32);
u8 *cur_ptr = event_skb->data + sizeof(u32);
u16 tlv_type, tlv_len;
while (len >= sizeof(struct nxpwifi_ie_types_header)) {
tlv = (struct nxpwifi_ie_types_header *)cur_ptr;
tlv_len = le16_to_cpu(tlv->len);
tlv_type = le16_to_cpu(tlv->type);
if ((tlv_len + sizeof(struct nxpwifi_ie_types_header)) > len)
break;
switch (tlv_type) {
case TLV_BTCOEX_WL_AGGR_WINSIZE:
winsizetlv =
(struct nxpwifi_ie_types_btcoex_aggr_win_size *)tlv;
adapter->coex_win_size = winsizetlv->coex_win_size;
adapter->coex_tx_win_size =
winsizetlv->tx_win_size;
adapter->coex_rx_win_size =
winsizetlv->rx_win_size;
nxpwifi_coex_ampdu_rxwinsize(adapter);
nxpwifi_update_ampdu_txwinsize(adapter);
break;
case TLV_BTCOEX_WL_SCANTIME:
scantlv =
(struct nxpwifi_ie_types_btcoex_scan_time *)tlv;
adapter->coex_scan = scantlv->coex_scan;
adapter->coex_min_scan_time = le16_to_cpu(scantlv->min_scan_time);
adapter->coex_max_scan_time = le16_to_cpu(scantlv->max_scan_time);
break;
default:
break;
}
len -= tlv_len + sizeof(struct nxpwifi_ie_types_header);
cur_ptr += tlv_len +
sizeof(struct nxpwifi_ie_types_header);
}
nxpwifi_dbg(adapter, INFO, "coex_scan=%d min_scan=%d coex_win=%d, tx_win=%d rx_win=%d\n",
adapter->coex_scan, adapter->coex_min_scan_time,
adapter->coex_win_size, adapter->coex_tx_win_size,
adapter->coex_rx_win_size);
}
/*
* Dispatch station firmware event based on event_cause.
* Looks up the handler in the station event table and invokes it.
*/
int nxpwifi_process_sta_event(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
u32 eventcause = adapter->event_cause;
int evt, ret = 0;
for (evt = 0; evt < ARRAY_SIZE(evt_table_sta); evt++) {
if (eventcause == evt_table_sta[evt].event_cause) {
if (evt_table_sta[evt].event_handler)
ret = evt_table_sta[evt].event_handler(priv);
break;
}
}
if (evt == ARRAY_SIZE(evt_table_sta))
nxpwifi_dbg(adapter, EVENT,
"%s: unknown event id: %#x\n",
__func__, eventcause);
else
nxpwifi_dbg(adapter, EVENT,
"%s: event id: %#x\n",
__func__, eventcause);
return ret;
}

View File

@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NXP Wireless LAN device driver: station RX data handling
*
* Copyright 2011-2024 NXP
*/
#include <uapi/linux/ipv6.h>
#include <net/ndisc.h>
#include "cfg.h"
#include "util.h"
#include "fw.h"
#include "main.h"
#include "11n_aggr.h"
#include "11n_rxreorder.h"
/*
* Drop gratuitous IPv4 ARP and IPv6 neighbour advertisements when
* source and destination addresses are identical.
*/
static bool
nxpwifi_discard_gratuitous_arp(struct nxpwifi_private *priv,
struct sk_buff *skb)
{
const struct nxpwifi_arp_eth_header *arp;
struct ethhdr *eth;
struct ipv6hdr *ipv6;
struct icmp6hdr *icmpv6;
eth = (struct ethhdr *)skb->data;
switch (ntohs(eth->h_proto)) {
case ETH_P_ARP:
arp = (void *)(skb->data + sizeof(struct ethhdr));
if (arp->hdr.ar_op == htons(ARPOP_REPLY) ||
arp->hdr.ar_op == htons(ARPOP_REQUEST)) {
if (!memcmp(arp->ar_sip, arp->ar_tip, 4))
return true;
}
break;
case ETH_P_IPV6:
ipv6 = (void *)(skb->data + sizeof(struct ethhdr));
icmpv6 = (void *)(skb->data + sizeof(struct ethhdr) +
sizeof(struct ipv6hdr));
if (icmpv6->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT) {
if (!memcmp(&ipv6->saddr, &ipv6->daddr,
sizeof(struct in6_addr)))
return true;
}
break;
default:
break;
}
return false;
}
/*
* Process a received data packet.
* Convert 802.2/LLC/SNAP to Ethernet II when appropriate, trim the
* rxpd and extra headers, optionally drop gratuitous ARP/NA, cache
* RX rate for unicast, then deliver to the stack.
*/
int nxpwifi_process_rx_packet(struct nxpwifi_private *priv,
struct sk_buff *skb)
{
int ret;
struct rx_packet_hdr *rx_pkt_hdr;
struct rxpd *local_rx_pd;
int hdr_chop;
struct ethhdr *eth;
u16 rx_pkt_off;
u8 adj_rx_rate = 0;
local_rx_pd = (struct rxpd *)(skb->data);
rx_pkt_off = le16_to_cpu(local_rx_pd->rx_pkt_offset);
rx_pkt_hdr = (void *)local_rx_pd + rx_pkt_off;
if (sizeof(rx_pkt_hdr->eth803_hdr) + sizeof(rfc1042_header) +
rx_pkt_off > skb->len) {
priv->stats.rx_dropped++;
dev_kfree_skb_any(skb);
return -EINVAL;
}
if (sizeof(*rx_pkt_hdr) + rx_pkt_off <= skb->len &&
((!memcmp(&rx_pkt_hdr->rfc1042_hdr, bridge_tunnel_header,
sizeof(bridge_tunnel_header))) ||
(!memcmp(&rx_pkt_hdr->rfc1042_hdr, rfc1042_header,
sizeof(rfc1042_header)) &&
rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_AARP) &&
rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_IPX)))) {
/*
* Replace the 803 header and rfc1042 header (llc/snap) with an
* EthernetII header, keep the src/dst and snap_type
* (ethertype).
* The firmware only passes up SNAP frames converting
* all RX Data from 802.11 to 802.2/LLC/SNAP frames.
* To create the Ethernet II, just move the src, dst address
* right before the snap_type.
*/
eth = (struct ethhdr *)
((u8 *)&rx_pkt_hdr->eth803_hdr
+ sizeof(rx_pkt_hdr->eth803_hdr) +
sizeof(rx_pkt_hdr->rfc1042_hdr)
- sizeof(rx_pkt_hdr->eth803_hdr.h_dest)
- sizeof(rx_pkt_hdr->eth803_hdr.h_source)
- sizeof(rx_pkt_hdr->rfc1042_hdr.snap_type));
memcpy(eth->h_source, rx_pkt_hdr->eth803_hdr.h_source,
sizeof(eth->h_source));
memcpy(eth->h_dest, rx_pkt_hdr->eth803_hdr.h_dest,
sizeof(eth->h_dest));
/*
* Chop off the rxpd + the excess memory from the 802.2/llc/snap
* header that was removed.
*/
hdr_chop = (u8 *)eth - (u8 *)local_rx_pd;
} else {
/* Chop off the rxpd */
hdr_chop = (u8 *)&rx_pkt_hdr->eth803_hdr - (u8 *)local_rx_pd;
}
/*
* Chop off the leading header bytes so the it points to the start of
* either the reconstructed EthII frame or the 802.2/llc/snap frame
*/
skb_pull(skb, hdr_chop);
if (priv->hs2_enabled &&
nxpwifi_discard_gratuitous_arp(priv, skb)) {
nxpwifi_dbg(priv->adapter, INFO, "Bypassed Gratuitous ARP\n");
dev_kfree_skb_any(skb);
return 0;
}
/* Only stash RX bitrate for unicast packets. */
if (likely(!is_multicast_ether_addr(rx_pkt_hdr->eth803_hdr.h_dest))) {
priv->rxpd_rate = local_rx_pd->rx_rate;
priv->rxpd_htinfo = local_rx_pd->rate_info;
}
if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA ||
GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) {
adj_rx_rate = nxpwifi_adjust_data_rate(priv,
local_rx_pd->rx_rate,
local_rx_pd->rate_info);
nxpwifi_hist_data_add(priv, adj_rx_rate, local_rx_pd->snr,
local_rx_pd->nf);
}
ret = nxpwifi_recv_packet(priv, skb);
if (ret)
nxpwifi_dbg(priv->adapter, ERROR,
"recv packet failed\n");
return ret;
}
/*
* Process a received buffer on the STA path.
* Validate RxPD and lengths, handle monitor/mgmt frames, fast-path
* non-unicast frames, and feed unicast data into 11n reorder/BA logic.
*/
int nxpwifi_process_sta_rx_packet(struct nxpwifi_private *priv,
struct sk_buff *skb)
{
struct nxpwifi_adapter *adapter = priv->adapter;
int ret = 0;
struct rxpd *local_rx_pd;
struct rx_packet_hdr *rx_pkt_hdr;
u8 ta[ETH_ALEN];
u16 rx_pkt_type, rx_pkt_offset, rx_pkt_length, seq_num;
local_rx_pd = (struct rxpd *)(skb->data);
rx_pkt_type = le16_to_cpu(local_rx_pd->rx_pkt_type);
rx_pkt_offset = le16_to_cpu(local_rx_pd->rx_pkt_offset);
rx_pkt_length = le16_to_cpu(local_rx_pd->rx_pkt_length);
seq_num = le16_to_cpu(local_rx_pd->seq_num);
rx_pkt_hdr = (void *)local_rx_pd + rx_pkt_offset;
if ((rx_pkt_offset + rx_pkt_length) > skb->len ||
sizeof(rx_pkt_hdr->eth803_hdr) + rx_pkt_offset > skb->len) {
nxpwifi_dbg(adapter, ERROR,
"wrong rx packet: len=%d, rx_pkt_offset=%d, rx_pkt_length=%d\n",
skb->len, rx_pkt_offset, rx_pkt_length);
priv->stats.rx_dropped++;
dev_kfree_skb_any(skb);
return ret;
}
if (priv->adapter->enable_net_mon && rx_pkt_type == PKT_TYPE_802DOT11) {
ret = nxpwifi_recv_packet_to_monif(priv, skb);
if (ret)
dev_kfree_skb_any(skb);
return ret;
}
if (rx_pkt_type == PKT_TYPE_MGMT) {
ret = nxpwifi_process_mgmt_packet(priv, skb);
if (ret && (ret != -EINPROGRESS))
nxpwifi_dbg(adapter, DATA, "Rx of mgmt packet failed");
if (ret != -EINPROGRESS)
dev_kfree_skb_any(skb);
return ret;
}
/*
* If the packet is not an unicast packet then send the packet
* directly to os. Don't pass thru rx reordering
*/
if (!IS_11N_ENABLED(priv) ||
!ether_addr_equal_unaligned(priv->curr_addr,
rx_pkt_hdr->eth803_hdr.h_dest)) {
nxpwifi_process_rx_packet(priv, skb);
return ret;
}
if (nxpwifi_queuing_ra_based(priv)) {
memcpy(ta, rx_pkt_hdr->eth803_hdr.h_source, ETH_ALEN);
} else {
if (rx_pkt_type != PKT_TYPE_BAR &&
local_rx_pd->priority < MAX_NUM_TID)
priv->rx_seq[local_rx_pd->priority] = seq_num;
memcpy(ta, priv->curr_bss_params.bss_descriptor.mac_address,
ETH_ALEN);
}
/* Reorder and send to OS */
ret = nxpwifi_11n_rx_reorder_pkt(priv, seq_num, local_rx_pd->priority,
ta, (u8)rx_pkt_type, skb);
if (ret || rx_pkt_type == PKT_TYPE_BAR)
dev_kfree_skb_any(skb);
if (ret)
priv->stats.rx_dropped++;
return ret;
}

View File

@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NXP Wireless LAN device driver: station TX data handling
*
* Copyright 2011-2024 NXP
*/
#include "cfg.h"
#include "util.h"
#include "fw.h"
#include "main.h"
#include "cmdevt.h"
#include "wmm.h"
/*
* Fill TxPD for TX packets by inserting it before payload and setting required fields.
*/
void nxpwifi_process_sta_txpd(struct nxpwifi_private *priv,
struct sk_buff *skb)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct txpd *local_tx_pd;
struct nxpwifi_txinfo *tx_info = NXPWIFI_SKB_TXCB(skb);
unsigned int pad;
u16 pkt_type, pkt_length, pkt_offset;
int hroom = adapter->intf_hdr_len;
u32 tx_control;
pkt_type = nxpwifi_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0;
pad = ((uintptr_t)skb->data - (sizeof(*local_tx_pd) + hroom)) &
(NXPWIFI_DMA_ALIGN_SZ - 1);
skb_push(skb, sizeof(*local_tx_pd) + pad);
local_tx_pd = (struct txpd *)skb->data;
memset(local_tx_pd, 0, sizeof(struct txpd));
local_tx_pd->bss_num = priv->bss_num;
local_tx_pd->bss_type = priv->bss_type;
pkt_length = (u16)(skb->len - (sizeof(struct txpd) + pad));
if (pkt_type == PKT_TYPE_MGMT)
pkt_length -= NXPWIFI_MGMT_FRAME_HEADER_SIZE;
local_tx_pd->tx_pkt_length = cpu_to_le16(pkt_length);
local_tx_pd->priority = (u8)skb->priority;
local_tx_pd->pkt_delay_2ms =
nxpwifi_wmm_compute_drv_pkt_delay(priv, skb);
if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS ||
tx_info->flags & NXPWIFI_BUF_FLAG_ACTION_TX_STATUS) {
local_tx_pd->tx_token_id = tx_info->ack_frame_id;
local_tx_pd->flags |= NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS;
}
if (local_tx_pd->priority <
ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl)) {
/*
* Set the priority specific tx_control field, setting of 0 will
* cause the default value to be used later in this function
*/
tx_control =
priv->wmm.user_pri_pkt_tx_ctrl[local_tx_pd->priority];
local_tx_pd->tx_control = cpu_to_le32(tx_control);
}
if (adapter->pps_uapsd_mode) {
if (nxpwifi_check_last_packet_indication(priv)) {
adapter->tx_lock_flag = true;
local_tx_pd->flags =
NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET;
}
}
/* Offset of actual data */
pkt_offset = sizeof(struct txpd) + pad;
if (pkt_type == PKT_TYPE_MGMT) {
/* Set the packet type and add header for management frame */
local_tx_pd->tx_pkt_type = cpu_to_le16(pkt_type);
pkt_offset += NXPWIFI_MGMT_FRAME_HEADER_SIZE;
}
local_tx_pd->tx_pkt_offset = cpu_to_le16(pkt_offset);
/* make space for adapter->intf_hdr_len */
skb_push(skb, hroom);
if (!local_tx_pd->tx_control)
/* TxCtrl set by user or default */
local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl);
}
/* Send a NULL-data frame with TxPD at highest priority. */
int nxpwifi_send_null_packet(struct nxpwifi_private *priv, u8 flags)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct txpd *local_tx_pd;
struct nxpwifi_tx_param tx_param;
/* sizeof(struct txpd) + Interface specific header */
#define NULL_PACKET_HDR 64
u32 data_len = NULL_PACKET_HDR;
struct sk_buff *skb;
int ret;
struct nxpwifi_txinfo *tx_info = NULL;
if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags))
return -EPERM;
if (!priv->media_connected)
return -EPERM;
if (adapter->data_sent)
return -EBUSY;
skb = dev_alloc_skb(data_len);
if (!skb)
return -ENOMEM;
tx_info = NXPWIFI_SKB_TXCB(skb);
memset(tx_info, 0, sizeof(*tx_info));
tx_info->bss_num = priv->bss_num;
tx_info->bss_type = priv->bss_type;
tx_info->pkt_len = data_len -
(sizeof(struct txpd) + adapter->intf_hdr_len);
skb_reserve(skb, sizeof(struct txpd) + adapter->intf_hdr_len);
skb_push(skb, sizeof(struct txpd));
local_tx_pd = (struct txpd *)skb->data;
local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl);
local_tx_pd->flags = flags;
local_tx_pd->priority = WMM_HIGHEST_PRIORITY;
local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd));
local_tx_pd->bss_num = priv->bss_num;
local_tx_pd->bss_type = priv->bss_type;
skb_push(skb, adapter->intf_hdr_len);
tx_param.next_pkt_len = 0;
ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA,
skb, &tx_param);
switch (ret) {
case -EBUSY:
dev_kfree_skb_any(skb);
nxpwifi_dbg(adapter, ERROR,
"%s: host_to_card failed: ret=%d\n",
__func__, ret);
adapter->dbg.num_tx_host_to_card_failure++;
break;
case 0:
dev_kfree_skb_any(skb);
nxpwifi_dbg(adapter, DATA,
"data: %s: host_to_card succeeded\n",
__func__);
adapter->tx_lock_flag = true;
break;
case -EINPROGRESS:
adapter->tx_lock_flag = true;
break;
default:
dev_kfree_skb_any(skb);
nxpwifi_dbg(adapter, ERROR,
"%s: host_to_card failed: ret=%d\n",
__func__, ret);
adapter->dbg.num_tx_host_to_card_failure++;
break;
}
return ret;
}
/* Check whether a lastpacket indication needs to be sent. */
u8 nxpwifi_check_last_packet_indication(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
u8 ret = false;
if (!adapter->sleep_period.period)
return ret;
if (nxpwifi_wmm_lists_empty(adapter))
ret = true;
if (ret && !adapter->cmd_sent && !adapter->curr_cmd &&
!nxpwifi_is_command_pending(adapter)) {
adapter->delay_null_pkt = false;
ret = true;
} else {
ret = false;
adapter->delay_null_pkt = true;
}
return ret;
}

View File

@@ -0,0 +1,352 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NXP Wireless LAN device driver: generic TX/RX data handling
*
* Copyright 2011-2024 NXP
*/
#include "cfg.h"
#include "util.h"
#include "fw.h"
#include "main.h"
#include "wmm.h"
/*
* Parse the RxPD, select the target interface, and dispatch the packet for
* handling.
*/
int nxpwifi_handle_rx_packet(struct nxpwifi_adapter *adapter,
struct sk_buff *skb)
{
struct nxpwifi_private *priv =
nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY);
struct rxpd *local_rx_pd;
struct nxpwifi_rxinfo *rx_info = NXPWIFI_SKB_RXCB(skb);
int ret;
local_rx_pd = (struct rxpd *)(skb->data);
/* Get the BSS number from rxpd, get corresponding priv */
priv = nxpwifi_get_priv_by_id(adapter, local_rx_pd->bss_num &
BSS_NUM_MASK, local_rx_pd->bss_type);
if (!priv)
priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY);
if (!priv) {
nxpwifi_dbg(adapter, ERROR,
"data: priv not found. Drop RX packet\n");
dev_kfree_skb_any(skb);
return -EINVAL;
}
nxpwifi_dbg_dump(adapter, DAT_D, "rx pkt:", skb->data,
min_t(size_t, skb->len, DEBUG_DUMP_DATA_MAX_LEN));
memset(rx_info, 0, sizeof(*rx_info));
rx_info->bss_num = priv->bss_num;
rx_info->bss_type = priv->bss_type;
if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP)
ret = nxpwifi_process_uap_rx_packet(priv, skb);
else
ret = nxpwifi_process_sta_rx_packet(priv, skb);
return ret;
}
EXPORT_SYMBOL_GPL(nxpwifi_handle_rx_packet);
/*
* Add TxPD, validate, send the packet to firmware, then run completion
* callback.
*/
int nxpwifi_process_tx(struct nxpwifi_private *priv, struct sk_buff *skb,
struct nxpwifi_tx_param *tx_param)
{
int hroom, ret;
struct nxpwifi_adapter *adapter = priv->adapter;
struct txpd *local_tx_pd = NULL;
struct nxpwifi_sta_node *dest_node;
struct ethhdr *hdr = (void *)skb->data;
if (unlikely(!skb->len ||
skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN)) {
ret = -EINVAL;
goto out;
}
hroom = adapter->intf_hdr_len;
if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) {
rcu_read_lock();
dest_node = nxpwifi_get_sta_entry(priv, hdr->h_dest);
if (dest_node) {
dest_node->stats.tx_bytes += skb->len;
dest_node->stats.tx_packets++;
}
rcu_read_unlock();
nxpwifi_process_uap_txpd(priv, skb);
} else {
nxpwifi_process_sta_txpd(priv, skb);
}
if ((adapter->data_sent || adapter->tx_lock_flag)) {
skb_queue_tail(&adapter->tx_data_q, skb);
atomic_inc(&adapter->tx_queued);
return 0;
}
if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA)
local_tx_pd = (struct txpd *)(skb->data + hroom);
ret = adapter->if_ops.host_to_card(adapter,
NXPWIFI_TYPE_DATA,
skb, tx_param);
nxpwifi_dbg_dump(adapter, DAT_D, "tx pkt:", skb->data,
min_t(size_t, skb->len, DEBUG_DUMP_DATA_MAX_LEN));
out:
switch (ret) {
case -ENOSR:
nxpwifi_dbg(adapter, DATA, "data: -ENOSR is returned\n");
break;
case -EBUSY:
if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) &&
adapter->pps_uapsd_mode && adapter->tx_lock_flag) {
priv->adapter->tx_lock_flag = false;
if (local_tx_pd)
local_tx_pd->flags = 0;
}
nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n");
break;
case -EINPROGRESS:
break;
case -EINVAL:
nxpwifi_dbg(adapter, ERROR,
"malformed skb (length: %u, headroom: %u)\n",
skb->len, skb_headroom(skb));
fallthrough;
case 0:
nxpwifi_write_data_complete(adapter, skb, 0, ret);
break;
default:
nxpwifi_dbg(adapter, ERROR,
"nxpwifi_write_data_async failed: 0x%X\n",
ret);
adapter->dbg.num_tx_host_to_card_failure++;
nxpwifi_write_data_complete(adapter, skb, 0, ret);
break;
}
return ret;
}
static int nxpwifi_host_to_card(struct nxpwifi_adapter *adapter,
struct sk_buff *skb,
struct nxpwifi_tx_param *tx_param)
{
struct txpd *local_tx_pd = NULL;
u8 *head_ptr = skb->data;
int ret = 0;
struct nxpwifi_private *priv;
struct nxpwifi_txinfo *tx_info;
tx_info = NXPWIFI_SKB_TXCB(skb);
priv = nxpwifi_get_priv_by_id(adapter, tx_info->bss_num,
tx_info->bss_type);
if (!priv) {
nxpwifi_dbg(adapter, ERROR,
"data: priv not found. Drop TX packet\n");
adapter->dbg.num_tx_host_to_card_failure++;
nxpwifi_write_data_complete(adapter, skb, 0, 0);
return ret;
}
if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA)
local_tx_pd = (struct txpd *)(head_ptr + adapter->intf_hdr_len);
ret = adapter->if_ops.host_to_card(adapter,
NXPWIFI_TYPE_DATA,
skb, tx_param);
switch (ret) {
case -ENOSR:
nxpwifi_dbg(adapter, ERROR, "data: -ENOSR is returned\n");
break;
case -EBUSY:
if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) &&
adapter->pps_uapsd_mode &&
adapter->tx_lock_flag) {
priv->adapter->tx_lock_flag = false;
if (local_tx_pd)
local_tx_pd->flags = 0;
}
skb_queue_head(&adapter->tx_data_q, skb);
if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT)
atomic_add(tx_info->aggr_num, &adapter->tx_queued);
else
atomic_inc(&adapter->tx_queued);
nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n");
break;
case -EINPROGRESS:
break;
case 0:
nxpwifi_write_data_complete(adapter, skb, 0, ret);
break;
default:
nxpwifi_dbg(adapter, ERROR,
"nxpwifi_write_data_async failed: 0x%X\n", ret);
adapter->dbg.num_tx_host_to_card_failure++;
nxpwifi_write_data_complete(adapter, skb, 0, ret);
break;
}
return ret;
}
static int
nxpwifi_dequeue_tx_queue(struct nxpwifi_adapter *adapter)
{
struct sk_buff *skb, *skb_next;
struct nxpwifi_txinfo *tx_info;
struct nxpwifi_tx_param tx_param;
skb = skb_dequeue(&adapter->tx_data_q);
if (!skb)
return -ENOMEM;
tx_info = NXPWIFI_SKB_TXCB(skb);
if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT)
atomic_sub(tx_info->aggr_num, &adapter->tx_queued);
else
atomic_dec(&adapter->tx_queued);
if (!skb_queue_empty(&adapter->tx_data_q))
skb_next = skb_peek(&adapter->tx_data_q);
else
skb_next = NULL;
tx_param.next_pkt_len = ((skb_next) ? skb_next->len : 0);
if (!tx_param.next_pkt_len) {
if (!nxpwifi_wmm_lists_empty(adapter))
tx_param.next_pkt_len = 1;
}
return nxpwifi_host_to_card(adapter, skb, &tx_param);
}
void
nxpwifi_process_tx_queue(struct nxpwifi_adapter *adapter)
{
do {
if (adapter->data_sent || adapter->tx_lock_flag)
break;
if (nxpwifi_dequeue_tx_queue(adapter))
break;
} while (!skb_queue_empty(&adapter->tx_data_q));
}
/*
* Packet send completion callback handler.
*
* It either frees the buffer directly or forwards it to another
* completion callback which checks conditions, updates statistics,
* wakes up stalled traffic queue if required, and then frees the buffer.
*/
int nxpwifi_write_data_complete(struct nxpwifi_adapter *adapter,
struct sk_buff *skb, int aggr, int status)
{
struct nxpwifi_private *priv;
struct nxpwifi_txinfo *tx_info;
struct netdev_queue *txq;
int index;
if (!skb)
return 0;
tx_info = NXPWIFI_SKB_TXCB(skb);
priv = nxpwifi_get_priv_by_id(adapter, tx_info->bss_num,
tx_info->bss_type);
if (!priv)
goto done;
nxpwifi_set_trans_start(priv->netdev);
if (tx_info->flags & NXPWIFI_BUF_FLAG_BRIDGED_PKT)
atomic_dec_return(&adapter->pending_bridged_pkts);
if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT)
goto done;
if (!status) {
priv->stats.tx_packets++;
priv->stats.tx_bytes += tx_info->pkt_len;
if (priv->tx_timeout_cnt)
priv->tx_timeout_cnt = 0;
} else {
priv->stats.tx_errors++;
}
if (aggr)
/* For skb_aggr, do not wake up tx queue */
goto done;
atomic_dec(&adapter->tx_pending);
index = nxpwifi_1d_to_wmm_queue[skb->priority];
if (atomic_dec_return(&priv->wmm_tx_pending[index]) < LOW_TX_PENDING) {
txq = netdev_get_tx_queue(priv->netdev, index);
if (netif_tx_queue_stopped(txq)) {
netif_tx_wake_queue(txq);
nxpwifi_dbg(adapter, DATA, "wake queue: %d\n", index);
}
}
done:
dev_kfree_skb_any(skb);
return 0;
}
EXPORT_SYMBOL_GPL(nxpwifi_write_data_complete);
void nxpwifi_parse_tx_status_event(struct nxpwifi_private *priv,
void *event_body)
{
struct tx_status_event *tx_status = (void *)priv->adapter->event_body;
struct sk_buff *ack_skb;
struct nxpwifi_txinfo *tx_info;
if (!tx_status->tx_token_id)
return;
spin_lock_bh(&priv->ack_status_lock);
ack_skb = xa_erase(&priv->ack_status_frames, tx_status->tx_token_id);
spin_unlock_bh(&priv->ack_status_lock);
if (ack_skb) {
tx_info = NXPWIFI_SKB_TXCB(ack_skb);
if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS) {
/* consumes ack_skb */
skb_complete_wifi_ack(ack_skb, !tx_status->status);
} else {
/* Remove broadcast address which was added by driver */
memmove(ack_skb->data +
sizeof(struct ieee80211_hdr_3addr) +
NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16),
ack_skb->data +
sizeof(struct ieee80211_hdr_3addr) +
NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16) +
ETH_ALEN, ack_skb->len -
(sizeof(struct ieee80211_hdr_3addr) +
NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16) +
ETH_ALEN));
ack_skb->len = ack_skb->len - ETH_ALEN;
/*
* Remove driver's proprietary header including 2 bytes
* of packet length and pass actual management frame buffer
* to cfg80211.
*/
cfg80211_mgmt_tx_status(&priv->wdev, tx_info->cookie,
ack_skb->data +
NXPWIFI_MGMT_FRAME_HEADER_SIZE +
sizeof(u16), ack_skb->len -
(NXPWIFI_MGMT_FRAME_HEADER_SIZE
+ sizeof(u16)),
!tx_status->status, GFP_ATOMIC);
dev_kfree_skb_any(ack_skb);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,488 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NXP Wireless LAN device driver: AP event handling
*
* Copyright 2011-2024 NXP
*/
#include "cfg.h"
#include "main.h"
#include "cmdevt.h"
#include "11n.h"
#define NXPWIFI_BSS_START_EVT_FIX_SIZE 12
static int
nxpwifi_uap_event_ps_awake(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
if (!adapter->pps_uapsd_mode &&
priv->media_connected && adapter->sleep_period.period) {
adapter->pps_uapsd_mode = true;
nxpwifi_dbg(adapter, EVENT,
"event: PPS/UAPSD mode activated\n");
}
adapter->tx_lock_flag = false;
if (adapter->pps_uapsd_mode && adapter->gen_null_pkt) {
if (nxpwifi_check_last_packet_indication(priv)) {
if (adapter->data_sent) {
adapter->ps_state = PS_STATE_AWAKE;
adapter->pm_wakeup_card_req = false;
adapter->pm_wakeup_fw_try = false;
} else {
if (!nxpwifi_send_null_packet
(priv,
NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET |
NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET))
adapter->ps_state = PS_STATE_SLEEP;
}
return 0;
}
}
adapter->ps_state = PS_STATE_AWAKE;
adapter->pm_wakeup_card_req = false;
adapter->pm_wakeup_fw_try = false;
return 0;
}
static int
nxpwifi_uap_event_ps_sleep(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
adapter->ps_state = PS_STATE_PRE_SLEEP;
nxpwifi_check_ps_cond(adapter);
return 0;
}
static int
nxpwifi_uap_event_sta_deauth(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
u8 *deauth_mac;
deauth_mac = adapter->event_body +
NXPWIFI_UAP_EVENT_EXTRA_HEADER;
cfg80211_del_sta(priv->netdev->ieee80211_ptr, deauth_mac, GFP_KERNEL);
if (priv->ap_11n_enabled) {
nxpwifi_11n_del_rx_reorder_tbl_by_ta(priv, deauth_mac);
nxpwifi_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac);
}
nxpwifi_wmm_del_peer_ra_list(priv, deauth_mac);
return 0;
}
static int
nxpwifi_uap_event_sta_assoc(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct station_info *sinfo;
struct nxpwifi_assoc_event *event;
struct nxpwifi_sta_node *node;
int len, i;
sinfo = kzalloc_obj(*sinfo, GFP_KERNEL);
if (!sinfo)
return -ENOMEM;
event = (struct nxpwifi_assoc_event *)
(adapter->event_body + NXPWIFI_UAP_EVENT_EXTRA_HEADER);
if (le16_to_cpu(event->type) == TLV_TYPE_UAP_MGMT_FRAME) {
len = -1;
if (ieee80211_is_assoc_req(event->frame_control))
len = 0;
else if (ieee80211_is_reassoc_req(event->frame_control))
/*
* There will be ETH_ALEN bytes of
* current_ap_addr before the re-assoc ies.
*/
len = ETH_ALEN;
if (len != -1) {
sinfo->assoc_req_ies = &event->data[len];
len = (u8 *)sinfo->assoc_req_ies -
(u8 *)&event->frame_control;
sinfo->assoc_req_ies_len =
le16_to_cpu(event->len) - (u16)len;
}
}
cfg80211_new_sta(priv->netdev->ieee80211_ptr, event->sta_addr, sinfo,
GFP_KERNEL);
node = nxpwifi_add_sta_entry(priv, event->sta_addr);
if (!node) {
nxpwifi_dbg(adapter, ERROR,
"could not create station entry!\n");
kfree(sinfo);
return -ENOENT;
}
if (!priv->ap_11n_enabled) {
kfree(sinfo);
return 0;
}
nxpwifi_set_sta_ht_cap(priv, sinfo->assoc_req_ies,
sinfo->assoc_req_ies_len, node);
for (i = 0; i < MAX_NUM_TID; i++) {
if (node->is_11n_enabled || node->is_11ax_enabled)
node->ampdu_sta[i] =
priv->aggr_prio_tbl[i].ampdu_user;
else
node->ampdu_sta[i] = BA_STREAM_NOT_ALLOWED;
}
memset(node->rx_seq, 0xff, sizeof(node->rx_seq));
kfree(sinfo);
return 0;
}
static int
nxpwifi_check_uap_capabilities(struct nxpwifi_private *priv,
struct sk_buff *event)
{
int evt_len;
u8 *curr;
u16 tlv_len;
struct nxpwifi_ie_types_data *tlv_hdr;
struct ieee80211_wmm_param_ie *wmm_param_ie = NULL;
int mask = IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK;
priv->wmm_enabled = false;
skb_pull(event, NXPWIFI_BSS_START_EVT_FIX_SIZE);
evt_len = event->len;
curr = event->data;
nxpwifi_dbg_dump(priv->adapter, EVT_D, "uap capabilities:",
event->data, event->len);
skb_push(event, NXPWIFI_BSS_START_EVT_FIX_SIZE);
while ((evt_len >= sizeof(tlv_hdr->header))) {
tlv_hdr = (struct nxpwifi_ie_types_data *)curr;
tlv_len = le16_to_cpu(tlv_hdr->header.len);
if (evt_len < tlv_len + sizeof(tlv_hdr->header))
break;
switch (le16_to_cpu(tlv_hdr->header.type)) {
case WLAN_EID_HT_CAPABILITY:
priv->ap_11n_enabled = true;
break;
case WLAN_EID_VHT_CAPABILITY:
priv->ap_11ac_enabled = true;
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 = (void *)(curr + 2);
wmm_param_ie->len = (u8)tlv_len;
wmm_param_ie->element_id =
WLAN_EID_VENDOR_SPECIFIC;
nxpwifi_dbg(priv->adapter, EVENT,
"info: check uap capabilities:\t"
"wmm parameter set count: %d\n",
wmm_param_ie->qos_info & mask);
nxpwifi_wmm_setup_ac_downgrade(priv);
priv->wmm_enabled = true;
nxpwifi_wmm_setup_queue_priorities(priv, wmm_param_ie);
break;
default:
break;
}
curr += (tlv_len + sizeof(tlv_hdr->header));
evt_len -= (tlv_len + sizeof(tlv_hdr->header));
}
return 0;
}
static int
nxpwifi_uap_event_bss_start(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
priv->port_open = false;
eth_hw_addr_set(priv->netdev, adapter->event_body + 2);
if (priv->hist_data)
nxpwifi_hist_data_reset(priv);
return nxpwifi_check_uap_capabilities(priv, adapter->event_skb);
}
static int
nxpwifi_uap_event_addba(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
if (priv->media_connected)
nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_RSP,
HOST_ACT_GEN_SET, 0,
adapter->event_body, false);
return 0;
}
static int
nxpwifi_uap_event_delba(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
if (priv->media_connected)
nxpwifi_11n_delete_ba_stream(priv, adapter->event_body);
return 0;
}
static int
nxpwifi_uap_event_ba_stream_timeout(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct host_cmd_ds_11n_batimeout *ba_timeout;
if (priv->media_connected) {
ba_timeout = (void *)adapter->event_body;
nxpwifi_11n_ba_stream_timeout(priv, ba_timeout);
}
return 0;
}
static int
nxpwifi_uap_event_amsdu_aggr_ctrl(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
u16 ctrl;
ctrl = get_unaligned_le16(adapter->event_body);
nxpwifi_dbg(adapter, EVENT,
"event: AMSDU_AGGR_CTRL %d\n", ctrl);
if (priv->media_connected) {
adapter->tx_buf_size =
min_t(u16, adapter->curr_tx_buf_size, ctrl);
nxpwifi_dbg(adapter, EVENT,
"event: tx_buf_size %d\n",
adapter->tx_buf_size);
}
return 0;
}
static int
nxpwifi_uap_event_bss_idle(struct nxpwifi_private *priv)
{
priv->media_connected = false;
priv->port_open = false;
nxpwifi_clean_txrx(priv);
nxpwifi_del_all_sta_list(priv);
return 0;
}
static int
nxpwifi_uap_event_bss_active(struct nxpwifi_private *priv)
{
priv->media_connected = true;
priv->port_open = true;
return 0;
}
static int
nxpwifi_uap_event_mic_countermeasures(struct nxpwifi_private *priv)
{
/* For future development */
return 0;
}
static int
nxpwifi_uap_event_radar_detected(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
return nxpwifi_11h_handle_radar_detected(priv, adapter->event_skb);
}
static int
nxpwifi_uap_event_channel_report_rdy(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
return nxpwifi_11h_handle_chanrpt_ready(priv, adapter->event_skb);
}
static int
nxpwifi_uap_event_tx_data_pause(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_process_tx_pause_event(priv, adapter->event_skb);
return 0;
}
static int
nxpwifi_uap_event_ext_scan_report(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
void *buf = adapter->event_skb->data;
int ret = 0;
if (adapter->ext_scan)
ret = nxpwifi_handle_event_ext_scan_report(priv, buf);
return ret;
}
static int
nxpwifi_uap_event_rxba_sync(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_11n_rxba_sync_event(priv, adapter->event_body,
adapter->event_skb->len -
sizeof(adapter->event_cause));
return 0;
}
static int
nxpwifi_uap_event_remain_on_chan_expired(struct nxpwifi_private *priv)
{
cfg80211_remain_on_channel_expired(&priv->wdev,
priv->roc_cfg.cookie,
&priv->roc_cfg.chan,
GFP_ATOMIC);
memset(&priv->roc_cfg, 0x00, sizeof(struct nxpwifi_roc_cfg));
return 0;
}
static int
nxpwifi_uap_event_multi_chan_info(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_process_multi_chan_event(priv, adapter->event_skb);
return 0;
}
static int
nxpwifi_uap_event_tx_status_report(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_parse_tx_status_event(priv, adapter->event_body);
return 0;
}
static int
nxpwifi_uap_event_bt_coex_wlan_para_change(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
nxpwifi_bt_coex_wlan_param_update_event(priv, adapter->event_skb);
return 0;
}
static int
nxpwifi_uap_event_vdll_ind(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
return nxpwifi_process_vdll_event(priv, adapter->event_skb);
}
static const struct nxpwifi_evt_entry evt_table_uap[] = {
{.event_cause = EVENT_PS_AWAKE,
.event_handler = nxpwifi_uap_event_ps_awake},
{.event_cause = EVENT_PS_SLEEP,
.event_handler = nxpwifi_uap_event_ps_sleep},
{.event_cause = EVENT_UAP_STA_DEAUTH,
.event_handler = nxpwifi_uap_event_sta_deauth},
{.event_cause = EVENT_UAP_STA_ASSOC,
.event_handler = nxpwifi_uap_event_sta_assoc},
{.event_cause = EVENT_UAP_BSS_START,
.event_handler = nxpwifi_uap_event_bss_start},
{.event_cause = EVENT_ADDBA,
.event_handler = nxpwifi_uap_event_addba},
{.event_cause = EVENT_DELBA,
.event_handler = nxpwifi_uap_event_delba},
{.event_cause = EVENT_BA_STREAM_TIEMOUT,
.event_handler = nxpwifi_uap_event_ba_stream_timeout},
{.event_cause = EVENT_AMSDU_AGGR_CTRL,
.event_handler = nxpwifi_uap_event_amsdu_aggr_ctrl},
{.event_cause = EVENT_UAP_BSS_IDLE,
.event_handler = nxpwifi_uap_event_bss_idle},
{.event_cause = EVENT_UAP_BSS_ACTIVE,
.event_handler = nxpwifi_uap_event_bss_active},
{.event_cause = EVENT_UAP_MIC_COUNTERMEASURES,
.event_handler = nxpwifi_uap_event_mic_countermeasures},
{.event_cause = EVENT_RADAR_DETECTED,
.event_handler = nxpwifi_uap_event_radar_detected},
{.event_cause = EVENT_CHANNEL_REPORT_RDY,
.event_handler = nxpwifi_uap_event_channel_report_rdy},
{.event_cause = EVENT_TX_DATA_PAUSE,
.event_handler = nxpwifi_uap_event_tx_data_pause},
{.event_cause = EVENT_EXT_SCAN_REPORT,
.event_handler = nxpwifi_uap_event_ext_scan_report},
{.event_cause = EVENT_RXBA_SYNC,
.event_handler = nxpwifi_uap_event_rxba_sync},
{.event_cause = EVENT_REMAIN_ON_CHAN_EXPIRED,
.event_handler = nxpwifi_uap_event_remain_on_chan_expired},
{.event_cause = EVENT_MULTI_CHAN_INFO,
.event_handler = nxpwifi_uap_event_multi_chan_info},
{.event_cause = EVENT_TX_STATUS_REPORT,
.event_handler = nxpwifi_uap_event_tx_status_report},
{.event_cause = EVENT_BT_COEX_WLAN_PARA_CHANGE,
.event_handler = nxpwifi_uap_event_bt_coex_wlan_para_change},
{.event_cause = EVENT_VDLL_IND,
.event_handler = nxpwifi_uap_event_vdll_ind},
};
/* Handle APinterface events by dispatching them to eventspecific routines. */
int nxpwifi_process_uap_event(struct nxpwifi_private *priv)
{
struct nxpwifi_adapter *adapter = priv->adapter;
u32 eventcause = adapter->event_cause;
int evt, ret = 0;
for (evt = 0; evt < ARRAY_SIZE(evt_table_uap); evt++) {
if (eventcause == evt_table_uap[evt].event_cause) {
if (evt_table_uap[evt].event_handler)
ret = evt_table_uap[evt].event_handler(priv);
break;
}
}
if (evt == ARRAY_SIZE(evt_table_uap))
nxpwifi_dbg(adapter, EVENT,
"%s: unknown event id: %#x\n",
__func__, eventcause);
else
nxpwifi_dbg(adapter, EVENT,
"%s: event id: %#x\n",
__func__, eventcause);
return ret;
}

View File

@@ -0,0 +1,478 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NXP Wireless LAN device driver: AP TX and RX data handling
*
* Copyright 2011-2024 NXP
*/
#include "cfg.h"
#include "main.h"
#include "wmm.h"
#include "11n_aggr.h"
#include "11n_rxreorder.h"
/*
* Drop bridged pkts from RA list until pending <= low threshold; return true if
* any.
*/
static bool
nxpwifi_uap_del_tx_pkts_in_ralist(struct nxpwifi_private *priv,
struct list_head *ra_list_head,
int tid)
{
struct nxpwifi_ra_list_tbl *ra_list;
struct sk_buff *skb, *tmp;
bool pkt_deleted = false;
struct nxpwifi_txinfo *tx_info;
struct nxpwifi_adapter *adapter = priv->adapter;
list_for_each_entry(ra_list, ra_list_head, list) {
if (skb_queue_empty(&ra_list->skb_head))
continue;
skb_queue_walk_safe(&ra_list->skb_head, skb, tmp) {
tx_info = NXPWIFI_SKB_TXCB(skb);
if (tx_info->flags & NXPWIFI_BUF_FLAG_BRIDGED_PKT) {
__skb_unlink(skb, &ra_list->skb_head);
nxpwifi_write_data_complete(adapter, skb, 0,
-1);
if (ra_list->tx_paused)
priv->wmm.pkts_paused[tid]--;
else
atomic_dec(&priv->wmm.tx_pkts_queued);
pkt_deleted = true;
}
if ((atomic_read(&adapter->pending_bridged_pkts) <=
NXPWIFI_BRIDGED_PKTS_THR_LOW))
break;
}
}
return pkt_deleted;
}
/* Delete bridged pkts from one RA list; rotate index to keep fairness. */
static void nxpwifi_uap_cleanup_tx_queues(struct nxpwifi_private *priv)
{
struct list_head *ra_list;
int i;
spin_lock_bh(&priv->wmm.ra_list_spinlock);
for (i = 0; i < MAX_NUM_TID; i++, priv->del_list_idx++) {
if (priv->del_list_idx == MAX_NUM_TID)
priv->del_list_idx = 0;
ra_list = &priv->wmm.tid_tbl_ptr[priv->del_list_idx].ra_list;
if (nxpwifi_uap_del_tx_pkts_in_ralist(priv, ra_list, i)) {
priv->del_list_idx++;
break;
}
}
spin_unlock_bh(&priv->wmm.ra_list_spinlock);
}
static void
nxpwifi_uap_queue_bridged_pkt(struct nxpwifi_private *priv,
struct sk_buff *skb)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct uap_rxpd *uap_rx_pd;
struct rx_packet_hdr *rx_pkt_hdr;
struct sk_buff *new_skb;
struct nxpwifi_txinfo *tx_info;
int hdr_chop;
struct ethhdr *p_ethhdr;
struct nxpwifi_sta_node *src_node;
int index;
uap_rx_pd = (struct uap_rxpd *)(skb->data);
rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset);
if ((atomic_read(&adapter->pending_bridged_pkts) >=
NXPWIFI_BRIDGED_PKTS_THR_HIGH)) {
nxpwifi_dbg(adapter, ERROR,
"Tx: Bridge packet limit reached. Drop packet!\n");
kfree_skb(skb);
nxpwifi_uap_cleanup_tx_queues(priv);
return;
}
if (sizeof(*rx_pkt_hdr) +
le16_to_cpu(uap_rx_pd->rx_pkt_offset) > skb->len) {
priv->stats.rx_dropped++;
dev_kfree_skb_any(skb);
return;
}
if ((!memcmp(&rx_pkt_hdr->rfc1042_hdr, bridge_tunnel_header,
sizeof(bridge_tunnel_header))) ||
(!memcmp(&rx_pkt_hdr->rfc1042_hdr, rfc1042_header,
sizeof(rfc1042_header)) &&
rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_AARP) &&
rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_IPX))) {
/*
* Replace the 803 header and rfc1042 header (llc/snap) with
* an Ethernet II header, keep the src/dst and snap_type
* (ethertype).
*
* The firmware only passes up SNAP frames converting all RX
* data from 802.11 to 802.2/LLC/SNAP frames.
*
* To create the Ethernet II, just move the src, dst address
* right before the snap_type.
*/
p_ethhdr = (struct ethhdr *)
((u8 *)(&rx_pkt_hdr->eth803_hdr)
+ sizeof(rx_pkt_hdr->eth803_hdr)
+ sizeof(rx_pkt_hdr->rfc1042_hdr)
- sizeof(rx_pkt_hdr->eth803_hdr.h_dest)
- sizeof(rx_pkt_hdr->eth803_hdr.h_source)
- sizeof(rx_pkt_hdr->rfc1042_hdr.snap_type));
memcpy(p_ethhdr->h_source, rx_pkt_hdr->eth803_hdr.h_source,
sizeof(p_ethhdr->h_source));
memcpy(p_ethhdr->h_dest, rx_pkt_hdr->eth803_hdr.h_dest,
sizeof(p_ethhdr->h_dest));
/*
* Chop off the rxpd + the excess memory from
* 802.2/llc/snap header that was removed.
*/
hdr_chop = (u8 *)p_ethhdr - (u8 *)uap_rx_pd;
} else {
/* Chop off the rxpd */
hdr_chop = (u8 *)&rx_pkt_hdr->eth803_hdr - (u8 *)uap_rx_pd;
}
/*
* Chop off the leading header bytes so that it points
* to the start of either the reconstructed EthII frame
* or the 802.2/llc/snap frame.
*/
skb_pull(skb, hdr_chop);
if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) {
nxpwifi_dbg(adapter, ERROR,
"data: Tx: insufficient skb headroom %d\n",
skb_headroom(skb));
/* Insufficient skb headroom - allocate a new skb */
new_skb =
skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN);
if (unlikely(!new_skb)) {
nxpwifi_dbg(adapter, ERROR,
"Tx: cannot allocate new_skb\n");
kfree_skb(skb);
priv->stats.tx_dropped++;
return;
}
kfree_skb(skb);
skb = new_skb;
nxpwifi_dbg(adapter, INFO,
"info: new skb headroom %d\n",
skb_headroom(skb));
}
tx_info = NXPWIFI_SKB_TXCB(skb);
memset(tx_info, 0, sizeof(*tx_info));
tx_info->bss_num = priv->bss_num;
tx_info->bss_type = priv->bss_type;
tx_info->flags |= NXPWIFI_BUF_FLAG_BRIDGED_PKT;
rcu_read_lock();
src_node = nxpwifi_get_sta_entry(priv, rx_pkt_hdr->eth803_hdr.h_source);
if (src_node) {
src_node->stats.last_rx = jiffies;
src_node->stats.rx_bytes += skb->len;
src_node->stats.rx_packets++;
src_node->stats.last_tx_rate = uap_rx_pd->rx_rate;
src_node->stats.last_tx_htinfo = uap_rx_pd->ht_info;
}
rcu_read_unlock();
if (is_unicast_ether_addr(rx_pkt_hdr->eth803_hdr.h_dest)) {
/*
* Update bridge packet statistics as the
* packet is not going to kernel/upper layer.
*/
priv->stats.rx_bytes += skb->len;
priv->stats.rx_packets++;
/*
* Sending bridge packet to TX queue, so save the packet
* length in TXCB to update statistics in TX complete.
*/
tx_info->pkt_len = skb->len;
}
__net_timestamp(skb);
index = nxpwifi_1d_to_wmm_queue[skb->priority];
atomic_inc(&priv->wmm_tx_pending[index]);
nxpwifi_wmm_add_buf_txqueue(priv, skb);
atomic_inc(&adapter->tx_pending);
atomic_inc(&adapter->pending_bridged_pkts);
nxpwifi_queue_work(adapter, &adapter->main_work);
}
/* AP fwd: mcast/bcast -> up + bridge; unicast -> bridge if RA assoc, else up. */
int nxpwifi_handle_uap_rx_forward(struct nxpwifi_private *priv,
struct sk_buff *skb)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct uap_rxpd *uap_rx_pd;
struct rx_packet_hdr *rx_pkt_hdr;
u8 ra[ETH_ALEN];
struct sk_buff *skb_uap;
struct nxpwifi_sta_node *node;
uap_rx_pd = (struct uap_rxpd *)(skb->data);
rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset);
/* don't do packet forwarding in disconnected state */
if (!priv->media_connected) {
nxpwifi_dbg(adapter, ERROR,
"drop packet in disconnected state.\n");
dev_kfree_skb_any(skb);
return 0;
}
memcpy(ra, rx_pkt_hdr->eth803_hdr.h_dest, ETH_ALEN);
if (is_multicast_ether_addr(ra)) {
skb_uap = skb_copy(skb, GFP_ATOMIC);
if (likely(skb_uap)) {
nxpwifi_uap_queue_bridged_pkt(priv, skb_uap);
} else {
nxpwifi_dbg(adapter, ERROR,
"failed to copy skb for uAP\n");
priv->stats.rx_dropped++;
dev_kfree_skb_any(skb);
return -ENOMEM;
}
} else {
node = nxpwifi_get_sta_entry_rcu(priv, ra);
if (node) {
/* Requeue Intra-BSS packet */
nxpwifi_uap_queue_bridged_pkt(priv, skb);
return 0;
}
}
/* Forward unicat/Inter-BSS packets to kernel. */
return nxpwifi_process_rx_packet(priv, skb);
}
int nxpwifi_uap_recv_packet(struct nxpwifi_private *priv,
struct sk_buff *skb)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct nxpwifi_sta_node *src_node, *dst_node;
struct ethhdr *p_ethhdr;
struct sk_buff *skb_uap;
struct nxpwifi_txinfo *tx_info;
if (!skb)
return -ENOMEM;
p_ethhdr = (void *)skb->data;
rcu_read_lock();
src_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_source);
if (src_node) {
src_node->stats.last_rx = jiffies;
src_node->stats.rx_bytes += skb->len;
src_node->stats.rx_packets++;
}
dst_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_dest);
rcu_read_unlock();
if (is_multicast_ether_addr(p_ethhdr->h_dest) || dst_node) {
if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN)
skb_uap =
skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN);
else
skb_uap = skb_copy(skb, GFP_ATOMIC);
if (likely(skb_uap)) {
tx_info = NXPWIFI_SKB_TXCB(skb_uap);
memset(tx_info, 0, sizeof(*tx_info));
tx_info->bss_num = priv->bss_num;
tx_info->bss_type = priv->bss_type;
tx_info->flags |= NXPWIFI_BUF_FLAG_BRIDGED_PKT;
__net_timestamp(skb_uap);
nxpwifi_wmm_add_buf_txqueue(priv, skb_uap);
atomic_inc(&adapter->tx_pending);
atomic_inc(&adapter->pending_bridged_pkts);
if ((atomic_read(&adapter->pending_bridged_pkts) >=
NXPWIFI_BRIDGED_PKTS_THR_HIGH)) {
nxpwifi_dbg(adapter, ERROR,
"Tx: Bridge packet limit reached. Drop packet!\n");
nxpwifi_uap_cleanup_tx_queues(priv);
}
} else {
nxpwifi_dbg(adapter, ERROR, "failed to allocate skb_uap");
}
nxpwifi_queue_work(adapter, &adapter->main_work);
/* Don't forward Intra-BSS unicast packet to upper layer*/
if (dst_node)
return 0;
}
skb->dev = priv->netdev;
skb->protocol = eth_type_trans(skb, priv->netdev);
skb->ip_summed = CHECKSUM_NONE;
/* Forward multicast/broadcast packet to upper layer*/
netif_rx(skb);
return 0;
}
/* Process AP RX: check RxPD/len, handle mgmt or 11n reorder/AMSDU, then forward. */
int nxpwifi_process_uap_rx_packet(struct nxpwifi_private *priv,
struct sk_buff *skb)
{
struct nxpwifi_adapter *adapter = priv->adapter;
int ret;
struct uap_rxpd *uap_rx_pd;
struct rx_packet_hdr *rx_pkt_hdr;
u16 rx_pkt_type;
u8 ta[ETH_ALEN], pkt_type;
struct nxpwifi_sta_node *node;
uap_rx_pd = (struct uap_rxpd *)(skb->data);
rx_pkt_type = le16_to_cpu(uap_rx_pd->rx_pkt_type);
rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset);
if (le16_to_cpu(uap_rx_pd->rx_pkt_offset) +
sizeof(rx_pkt_hdr->eth803_hdr) > skb->len) {
nxpwifi_dbg(adapter, ERROR,
"wrong rx packet for struct ethhdr: len=%d, offset=%d\n",
skb->len, le16_to_cpu(uap_rx_pd->rx_pkt_offset));
priv->stats.rx_dropped++;
dev_kfree_skb_any(skb);
return 0;
}
ether_addr_copy(ta, rx_pkt_hdr->eth803_hdr.h_source);
if ((le16_to_cpu(uap_rx_pd->rx_pkt_offset) +
le16_to_cpu(uap_rx_pd->rx_pkt_length)) > (u16)skb->len) {
nxpwifi_dbg(adapter, ERROR,
"wrong rx packet: len=%d, offset=%d, length=%d\n",
skb->len, le16_to_cpu(uap_rx_pd->rx_pkt_offset),
le16_to_cpu(uap_rx_pd->rx_pkt_length));
priv->stats.rx_dropped++;
rcu_read_lock();
node = nxpwifi_get_sta_entry(priv, ta);
if (node)
node->stats.tx_failed++;
rcu_read_unlock();
dev_kfree_skb_any(skb);
return 0;
}
if (rx_pkt_type == PKT_TYPE_MGMT) {
ret = nxpwifi_process_mgmt_packet(priv, skb);
if (ret && (ret != -EINPROGRESS))
nxpwifi_dbg(adapter, DATA, "Rx of mgmt packet failed");
if (ret != -EINPROGRESS)
dev_kfree_skb_any(skb);
return ret;
}
if (rx_pkt_type != PKT_TYPE_BAR && uap_rx_pd->priority < MAX_NUM_TID) {
rcu_read_lock();
node = nxpwifi_get_sta_entry(priv, ta);
if (node)
node->rx_seq[uap_rx_pd->priority] =
le16_to_cpu(uap_rx_pd->seq_num);
rcu_read_unlock();
}
if (!priv->ap_11n_enabled ||
(!nxpwifi_11n_get_rx_reorder_tbl(priv, uap_rx_pd->priority, ta) &&
(le16_to_cpu(uap_rx_pd->rx_pkt_type) != PKT_TYPE_AMSDU))) {
ret = nxpwifi_handle_uap_rx_forward(priv, skb);
return ret;
}
/* Reorder and send to kernel */
pkt_type = (u8)le16_to_cpu(uap_rx_pd->rx_pkt_type);
ret = nxpwifi_11n_rx_reorder_pkt(priv, le16_to_cpu(uap_rx_pd->seq_num),
uap_rx_pd->priority, ta, pkt_type, skb);
if (ret || rx_pkt_type == PKT_TYPE_BAR)
dev_kfree_skb_any(skb);
if (ret)
priv->stats.rx_dropped++;
return ret;
}
/*
* Build TxPD for AP TX: push aligned TxPD; set bss, len/off, prio, delay, txctl,
* flags.
*/
void nxpwifi_process_uap_txpd(struct nxpwifi_private *priv,
struct sk_buff *skb)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct uap_txpd *txpd;
struct nxpwifi_txinfo *tx_info = NXPWIFI_SKB_TXCB(skb);
int pad;
u16 pkt_type, pkt_offset;
int hroom = adapter->intf_hdr_len;
pkt_type = nxpwifi_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0;
pad = ((uintptr_t)skb->data - (sizeof(*txpd) + hroom)) &
(NXPWIFI_DMA_ALIGN_SZ - 1);
skb_push(skb, sizeof(*txpd) + pad);
txpd = (struct uap_txpd *)skb->data;
memset(txpd, 0, sizeof(*txpd));
txpd->bss_num = priv->bss_num;
txpd->bss_type = priv->bss_type;
txpd->tx_pkt_length = cpu_to_le16((u16)(skb->len - (sizeof(*txpd) +
pad)));
txpd->priority = (u8)skb->priority;
txpd->pkt_delay_2ms = nxpwifi_wmm_compute_drv_pkt_delay(priv, skb);
if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS ||
tx_info->flags & NXPWIFI_BUF_FLAG_ACTION_TX_STATUS) {
txpd->tx_token_id = tx_info->ack_frame_id;
txpd->flags |= NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS;
}
if (txpd->priority < ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl))
/*
* Set the priority specific tx_control field, setting of 0 will
* cause the default value to be used later in this function.
*/
txpd->tx_control =
cpu_to_le32(priv->wmm.user_pri_pkt_tx_ctrl[txpd->priority]);
/* Offset of actual data */
pkt_offset = sizeof(*txpd) + pad;
if (pkt_type == PKT_TYPE_MGMT) {
/* Set the packet type and add header for management frame */
txpd->tx_pkt_type = cpu_to_le16(pkt_type);
pkt_offset += NXPWIFI_MGMT_FRAME_HEADER_SIZE;
}
txpd->tx_pkt_offset = cpu_to_le16(pkt_offset);
/* make space for adapter->intf_hdr_len */
skb_push(skb, hroom);
if (!txpd->tx_control)
/* TxCtrl set by user or default */
txpd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* NXP Wireless LAN device driver: utility functions
*
* Copyright 2011-2024 NXP
*/
#ifndef _NXPWIFI_UTIL_H_
#define _NXPWIFI_UTIL_H_
#include "fw.h"
struct nxpwifi_adapter;
struct nxpwifi_private;
struct nxpwifi_dma_mapping {
dma_addr_t addr;
size_t len;
};
struct nxpwifi_cb {
struct nxpwifi_dma_mapping dma_mapping;
union {
struct nxpwifi_rxinfo rx_info;
struct nxpwifi_txinfo tx_info;
};
};
/* size/addr for nxpwifi_debug_info */
#define item_size(n) (sizeof_field(struct nxpwifi_debug_info, n))
#define item_addr(n) (offsetof(struct nxpwifi_debug_info, n))
/* size/addr for struct nxpwifi_adapter */
#define adapter_item_size(n) (sizeof_field(struct nxpwifi_adapter, n))
#define adapter_item_addr(n) (offsetof(struct nxpwifi_adapter, n))
struct nxpwifi_debug_data {
char name[32]; /* variable/array name */
u32 size; /* size of the variable/array */
size_t addr; /* address of the variable/array */
int num; /* number of variables in an array */
};
static inline struct nxpwifi_rxinfo *NXPWIFI_SKB_RXCB(struct sk_buff *skb)
{
struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb;
BUILD_BUG_ON(sizeof(struct nxpwifi_cb) > sizeof(skb->cb));
return &cb->rx_info;
}
static inline struct nxpwifi_txinfo *NXPWIFI_SKB_TXCB(struct sk_buff *skb)
{
struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb;
return &cb->tx_info;
}
static inline void nxpwifi_store_mapping(struct sk_buff *skb,
struct nxpwifi_dma_mapping *mapping)
{
struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb;
memcpy(&cb->dma_mapping, mapping, sizeof(*mapping));
}
static inline void nxpwifi_get_mapping(struct sk_buff *skb,
struct nxpwifi_dma_mapping *mapping)
{
struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb;
memcpy(mapping, &cb->dma_mapping, sizeof(*mapping));
}
static inline dma_addr_t NXPWIFI_SKB_DMA_ADDR(struct sk_buff *skb)
{
struct nxpwifi_dma_mapping mapping;
nxpwifi_get_mapping(skb, &mapping);
return mapping.addr;
}
int nxpwifi_debug_info_to_buffer(struct nxpwifi_private *priv, char *buf,
struct nxpwifi_debug_info *info);
static inline void le16_unaligned_add_cpu(__le16 *var, u16 val)
{
put_unaligned_le16(get_unaligned_le16(var) + val, var);
}
/*
* Iterate over TLVs safely.
* Ensures no out-of-bound access even if firmware sends malformed data.
*/
#define nxpwifi_for_each_tlv(tlv, buf, buf_len) \
for (tlv = (const struct nxpwifi_tlv *)(buf); \
(u8 *)(tlv) + sizeof(*tlv) <= (u8 *)(buf) + (buf_len) && \
(u8 *)(tlv) + sizeof(*tlv) + le16_to_cpu(tlv->len) <= \
(u8 *)(buf) + (buf_len); \
tlv = (const struct nxpwifi_tlv *)((u8 *)(tlv) + sizeof(*tlv) + \
le16_to_cpu(tlv->len)))
/* Return first TLV matching @type in given buffer. */
static inline const struct nxpwifi_tlv *
nxpwifi_find_tlv(u16 type, const u8 *buf, u32 buf_len)
{
const struct nxpwifi_tlv *tlv;
nxpwifi_for_each_tlv(tlv, buf, buf_len) {
if (le16_to_cpu(tlv->type) == type)
return tlv;
}
return NULL;
}
int nxpwifi_append_data_tlv(u16 id, u8 *data, int len, u8 *pos, u8 *cmd_end);
int nxpwifi_download_vdll_block(struct nxpwifi_adapter *adapter,
u8 *block, u16 block_len);
int nxpwifi_process_vdll_event(struct nxpwifi_private *priv,
struct sk_buff *skb);
u64 nxpwifi_roc_cookie(struct nxpwifi_adapter *adapter);
void nxpwifi_queue_work(struct nxpwifi_adapter *adapter,
struct work_struct *work);
void nxpwifi_queue_delayed_work(struct nxpwifi_adapter *adapter,
struct delayed_work *dwork,
unsigned long delay);
void nxpwifi_queue_wiphy_work(struct nxpwifi_adapter *adapter,
struct wiphy_work *work);
void nxpwifi_queue_delayed_wiphy_work(struct nxpwifi_adapter *adapter,
struct wiphy_delayed_work *dwork,
unsigned long delay);
/*
* Firmware cannot run AP and STA on different channels simultaneously,
* and doing so may trigger a crash. Check whether check_chan can be set
* safely; return true if allowed, false if another channel is already
* active in firmware.
*/
bool nxpwifi_is_channel_setting_allowable(struct nxpwifi_private *priv,
struct ieee80211_channel *check_chan);
void nxpwifi_convert_chan_to_band_cfg(struct nxpwifi_private *priv,
u8 *band_cfg,
struct cfg80211_chan_def *chan_def);
#endif /* !_NXPWIFI_UTIL_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -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[];
/* 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);
void nxpwifi_wmm_init_tos_to_tid_inv(struct nxpwifi_private *priv);
#endif /* !_NXPWIFI_WMM_H_ */