* [PATCH next] rfkill: Replace strcpy() with memcpy()
From: david.laight.linux @ 2026-06-06 20:26 UTC (permalink / raw)
To: Kees Cook, linux-hardening, Arnd Bergmann, linux-kernel,
linux-wireless
Cc: Johannes Berg, David Laight
From: David Laight <david.laight.linux@gmail.com>
The length of the string is calculated in order to allocate the correct
sized memory block, use the same length to copy the string.
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
This is one of a group of patches that remove potentially unbounded
strcpy() calls.
They are mostly replaced by strscpy() or, when strlen() has just been
called, with memcpy() (usually including the '\0').
Calls with copy string literals into arrays are left unchanged.
They are safe and easily detected as such.
The changes were made by getting the compiler to detect the calls and
then fixing the code by hand.
Note that all the changes are only compile tested.
Some Makefiles were changed to allow files to contain strcpy().
As well as 'difficult to fix' files, this included 'show' functions
as they really need to use sysfs_emit() or seq_printf().
All the patches are being sent individually to avoid very long cc lists.
Apologies for the terse commit messages and likely unexpected tags.
(There are about 100 patches in total.)
net/rfkill/core.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/net/rfkill/core.c b/net/rfkill/core.c
index 4827e1fb8804..9e143c4bfe6a 100644
--- a/net/rfkill/core.c
+++ b/net/rfkill/core.c
@@ -1000,6 +1000,7 @@ struct rfkill * __must_check rfkill_alloc(const char *name,
{
struct rfkill *rfkill;
struct device *dev;
+ size_t name_len;
if (WARN_ON(!ops))
return NULL;
@@ -1013,14 +1014,15 @@ struct rfkill * __must_check rfkill_alloc(const char *name,
if (WARN_ON(type == RFKILL_TYPE_ALL || type >= NUM_RFKILL_TYPES))
return NULL;
- rfkill = kzalloc(sizeof(*rfkill) + strlen(name) + 1, GFP_KERNEL);
+ name_len = strlen(name);
+ rfkill = kzalloc(sizeof(*rfkill) + name_len + 1, GFP_KERNEL);
if (!rfkill)
return NULL;
spin_lock_init(&rfkill->lock);
INIT_LIST_HEAD(&rfkill->node);
rfkill->type = type;
- strcpy(rfkill->name, name);
+ memcpy(rfkill->name, name, name_len);
rfkill->ops = ops;
rfkill->data = ops_data;
--
2.39.5
^ permalink raw reply related
* [PATCH net-next] drivers/net/wireless/microchip/wilc1000: Use strscpy() to copy device name
From: david.laight.linux @ 2026-06-06 20:26 UTC (permalink / raw)
To: Kees Cook, linux-hardening, Arnd Bergmann, linux-kernel,
linux-wireless
Cc: Ajay Singh, Claudiu Beznea, David Laight
From: David Laight <david.laight.linux@gmail.com>
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
This is one of a group of patches that remove potentially unbounded
strcpy() calls.
They are mostly replaced by strscpy() or, when strlen() has just been
called, with memcpy() (usually including the '\0').
Calls with copy string literals into arrays are left unchanged.
They are safe and easily detected as such.
The changes were made by getting the compiler to detect the calls and
then fixing the code by hand.
Note that all the changes are only compile tested.
Some Makefiles were changed to allow files to contain strcpy().
As well as 'difficult to fix' files, this included 'show' functions
as they really need to use sysfs_emit() or seq_printf().
All the patches are being sent individually to avoid very long cc lists.
Apologies for the terse commit messages and likely unexpected tags.
(There are about 100 patches in total.)
drivers/net/wireless/microchip/wilc1000/netdev.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/microchip/wilc1000/netdev.c b/drivers/net/wireless/microchip/wilc1000/netdev.c
index 956cb578bf37..66580233646d 100644
--- a/drivers/net/wireless/microchip/wilc1000/netdev.c
+++ b/drivers/net/wireless/microchip/wilc1000/netdev.c
@@ -960,7 +960,7 @@ struct wilc_vif *wilc_netdev_ifc_init(struct wilc *wl, const char *name,
vif = netdev_priv(ndev);
ndev->ieee80211_ptr = &vif->priv.wdev;
- strcpy(ndev->name, name);
+ strscpy(ndev->name, name);
vif->wilc = wl;
vif->ndev = ndev;
ndev->ml_priv = vif;
--
2.39.5
^ permalink raw reply related
* [BUG] iwlwifi: Intel 3160 system freeze on suspend with WoWLAN magic-packet armed — persists on kernel 6.17 despite recent d3.c patches
From: dheeraj y @ 2026-06-06 16:17 UTC (permalink / raw)
To: linux-wireless; +Cc: kvalo, johannes
Hi linux-wireless,
Note: I encountered this issue myself, discussed it with Claude, and
asked it to help draft this email.
I am reporting a reproducible system freeze on the Intel Dual Band Wireless
AC 3160 when WoWLAN magic-packet wake is armed. This persists on kernel 6.17
which already includes the recent d3.c patches. I explain below why those
patches do not address this specific failure.
---
SYSTEM INFORMATION
------------------
Hostname: yoga-node
CPU: Intel Core i7-5500U @ 2.40GHz (x86_64)
RAM: 7.2 GiB
OS: Ubuntu 25.10
Kernel: 6.17.0-23-generic
Wi-Fi card: Intel Dual Band Wireless AC 3160 [Wilkins Peak 1]
PCI ID: 8086:08b4 (rev 93)
Subsystem: 8086:8270
Driver: iwlwifi / iwlmvm
Firmware: 17.bfb58538.0 (3160-17.ucode) — final firmware release
$ lspci -nn | grep -i network
02:00.0 Network controller [0280]: Intel Corporation Wireless 3160
[8086:08b4] (rev 93)
$ lspci -k -s 02:00.0
02:00.0 Network controller: Intel Corporation Wireless 3160 (rev 93)
Subsystem: Intel Corporation Dual Band Wireless AC 3160
[Wilkins Peak 1]
Kernel driver in use: iwlwifi
Kernel modules: iwlwifi
$ dmesg | grep "loaded firmware version"
iwlwifi 0000:02:00.0: loaded firmware version 17.bfb58538.0
3160-17.ucode op_mode iwlmvm
$ cat /sys/power/mem_sleep
s2idle [deep]
$ cat /proc/acpi/wakeup | grep -E "RP03|PXSX"
PXSX S4 *disabled
PXSX S4 *disabled
RP03 S4 *enabled pci:0000:00:1c.2
PXSX S4 *enabled pci:0000:02:00.0 ← Wi-Fi card, ACPI wake enabled
PXSX S4 *disabled pci:0000:03:00.0
$ cat /etc/modprobe.d/iwlwifi.conf
options iwlwifi power_save=0 bt_coex_active=0
---
WOWLAN CAPABILITIES ADVERTISED BY DRIVER
-----------------------------------------
$ iw phy | grep -A20 "WoWLAN support"
WoWLAN support:
* wake up on disconnect
* wake up on magic packet
* wake up on pattern match, up to 20 patterns of 16-128 bytes
* can do GTK rekeying
* wake up on GTK rekey failure
* wake up on EAP identity request
* wake up on 4-way handshake
* wake up on rfkill release
* wake up on network detection, up to 11 match sets
The driver fully advertises WoWLAN magic-packet support.
---
STEPS TO REPRODUCE
------------------
1. Arm WoWLAN:
$ sudo iw phy phy0 wowlan enable magic-packet
2. Verify:
$ sudo iw phy phy0 wowlan show
WoWLAN is enabled:
* wake up on magic packet
3. Suspend:
$ sudo systemctl suspend
4. Result: system freezes. Power LED stays solid (not blinking).
SSH becomes unreachable. Only force shutdown recovers the machine.
5. Without WoWLAN armed (default 0x1):
$ nmcli -f 802-11-wireless.wake-on-wlan connection show "SSID"
802-11-wireless.wake-on-wlan: 0x1 (default)
→ suspend and resume work perfectly every time.
---
WHAT I TESTED
-------------
Test 1: Manual iw arm
$ sudo iw phy phy0 wowlan enable magic-packet
$ sudo systemctl suspend
Result: freeze
Test 2: NetworkManager profile set to magic (0x8)
$ sudo nmcli connection modify "SSID" 802-11-wireless.wake-on-wlan magic
$ sudo nmcli connection down "SSID" && sudo nmcli connection up "SSID"
$ sudo iw phy phy0 wowlan show
WoWLAN is enabled:
* wake up on magic packet
$ sudo systemctl suspend
Result: freeze
Test 3: iwlwifi module options to disable deep power states
$ echo "options iwlwifi power_save=0 bt_coex_active=0" \
| sudo tee /etc/modprobe.d/iwlwifi.conf
Result: fixed the 30-second pre-suspend delay but freeze still occurs
Test 4: systemd sleep hook to arm WoWLAN after NetworkManager sleeps
Created /lib/systemd/system-sleep/wowlan-arm.sh:
#!/bin/bash
if [ "$1" = "pre" ]; then
/usr/sbin/iw phy phy0 wowlan enable magic-packet
fi
Result: freeze — hook itself caused the hang even earlier in suspend path
---
SUSPEND JOURNAL LOG (last freeze attempt)
------------------------------------------
Jun 06 19:41:11 NetworkManager: sleep requested
Jun 06 19:41:11 NetworkManager: device (p2p-dev-wlp2s0):
disconnected -> unmanaged (unmanaged-sleeping)
Jun 06 19:41:11 systemd: Reached target sleep.target
Jun 06 19:41:11 systemd: Starting systemd-suspend.service
Jun 06 19:41:11 systemd-sleep: Performing sleep operation 'suspend'...
Jun 06 19:41:11 kernel: PM: suspend entry (deep)
[log ends here — machine froze, never returned from suspend]
Note: with power_save=0 the previous 30-second delay between
"sleep requested" and "sleep.target reached" was eliminated,
confirming the module option helps but does not fix the core issue.
---
KNOWN CRASH PATH
----------------
From Debian Bug #1065112 (identical symptoms on kernels 6.1 and 6.7):
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1065112
drivers/net/wireless/intel/iwlwifi/iwl-trans.c
:: iwl_trans_txq_enable_cfg() — reports "bad state"
drivers/net/wireless/intel/iwlwifi/mvm/d3.c
:: iwl_mvm_send_wowlan_get_status() — "failed to query wakeup status (-5)"
Prior discussion on this list (2017), Johannes Berg concluded this is
likely broken system/card firmware since it reproduced on Windows too:
https://www.mail-archive.com/linux-wireless@vger.kernel.org/msg32540.html
---
WHY THE RECENT d3.c PATCHES DO NOT FIX THIS
---------------------------------------------
Kernel 6.17 already includes both recent d3.c patches, yet the freeze
persists. Here is why neither patch applies to this case:
1. "Use the sync timepoint API in suspend" (Nov 2024, Daniel Gabay)
Fixes a race between async debug dump worker and suspend flow:
"Timeout waiting for hardware access (CSR_GP_CNTRL 0xffffffff)"
This is a debug dump path race — unrelated to the WoWLAN arm
handshake. The 3160 freeze happens in iwl_mvm_send_wowlan_get_status,
not in the debug timepoint path.
2. "fix WoWLAN command version lookup" (Jun 2024, Yedidya Benshimol)
Fixes IWL_FW_CMD_VER_UNKNOWN for the notification-based WoWLAN
status API introduced for newer firmware. The 3160 uses 17.ucode
which predates this notification API entirely — the lookup fix has
no effect on the legacy command path the 3160 uses.
Both patches target device families newer than the 3160. The 3160
uses a legacy firmware interface that has never been patched.
---
ROOT CAUSE ASSESSMENT
----------------------
The Intel 3160 firmware (17.ucode) fails the WoWLAN arm handshake
during the D3 suspend transition. The firmware enters a bad state
before the host CPU completes the sleep sequence, causing a full
system hang. This is a firmware defect — the driver is doing its job
correctly but receives no valid acknowledgement from the firmware.
Since 17.ucode is the final firmware release for this card with no
further updates planned, the only viable fix would be a driver-level
workaround, specifically one of:
a) Runtime detection of the broken capability and graceful skip of
WoWLAN arming on affected 3160 hardware (PCI ID 8086:08b4), or
b) A known-broken device quirk entry for 8086:08b4 that prevents
WoWLAN from being armed, avoiding the freeze while keeping
normal suspend functional.
---
REQUEST
-------
Is there a known patch or workaround for PCI ID 8086:08b4?
If not, would a quirk patch be acceptable upstream?
I am willing to test any proposed patch.
Thank you.
^ permalink raw reply
* Re: [PATCH v3 0/3] wifi: wcn36xx: firmware trust boundary hardening
From: Jeff Johnson @ 2026-06-06 15:54 UTC (permalink / raw)
To: Loic Poulain, Tristan Madani
Cc: Johannes Berg, wcn36xx, linux-wireless, Tristan Madani
In-Reply-To: <20260421135018.352774-1-tristmd@gmail.com>
On Tue, 21 Apr 2026 13:50:15 +0000, Tristan Madani wrote:
> This series adds missing bounds checks for firmware-controlled fields
> in the Qualcomm wcn36xx driver.
>
> Patch 1 prevents heap overflow from oversized HAL responses. Patch 2
> validates PRINT_REG_INFO count. Patch 3 checks trigger BA response
> length.
>
> [...]
Applied, thanks!
[1/3] wifi: wcn36xx: fix heap overflow from oversized firmware HAL response
commit: 88a240d86d3d64521f9194abe185ac71cc74d0bd
[2/3] wifi: wcn36xx: fix OOB read from firmware count in PRINT_REG_INFO indication
commit: df2187acfca6c6cca372c5d35f42394d9c270b09
[3/3] wifi: wcn36xx: fix OOB read from short trigger BA firmware response
commit: b5e6f21923ca89d90256e7346301056f6502691e
Best regards,
--
Jeff Johnson <jeff.johnson@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH ath-next] wifi: ath9k_htc: allocate tx_buf and buf together
From: Jeff Johnson @ 2026-06-06 15:54 UTC (permalink / raw)
To: linux-wireless, Rosen Penev
Cc: Toke Høiland-Jørgensen, linux-kernel
In-Reply-To: <20260521232020.261405-1-rosenp@gmail.com>
On Thu, 21 May 2026 16:20:20 -0700, Rosen Penev wrote:
> Use a flexible array member to combine allocations. No need to have them
> separate as they are always together.
>
>
Applied, thanks!
[1/1] wifi: ath9k_htc: allocate tx_buf and buf together
commit: 38b2fb7d2df16f5801f7d88a4739942b95a5f6aa
Best regards,
--
Jeff Johnson <jeff.johnson@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH ath-next] wifi: ath9k: remove disabling of bands
From: Jeff Johnson @ 2026-06-06 15:54 UTC (permalink / raw)
To: linux-wireless, Rosen Penev
Cc: Toke Høiland-Jørgensen, linux-kernel
In-Reply-To: <20260521231806.261220-1-rosenp@gmail.com>
On Thu, 21 May 2026 16:18:06 -0700, Rosen Penev wrote:
> The old platform data code that used this is gone and this serves no
> purpose.
>
> The modern way to disable bands is ieee80211-freq-limit, which is
> already implemented.
>
>
> [...]
Applied, thanks!
[1/1] wifi: ath9k: remove disabling of bands
commit: f0e5e8703fd61dacaa0c18016146e64cf32ddcb7
Best regards,
--
Jeff Johnson <jeff.johnson@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH ath-next] wifi: ath9k: remove TX99 power array zero init
From: Jeff Johnson @ 2026-06-06 15:54 UTC (permalink / raw)
To: linux-wireless, Rosen Penev
Cc: Toke Høiland-Jørgensen, linux-kernel
In-Reply-To: <20260517222136.1660347-1-rosenp@gmail.com>
On Sun, 17 May 2026 15:21:36 -0700, Rosen Penev wrote:
> This array is fully initialized in the loop itself. No need to zero
> initialize and then overwrite.
>
> Remove static from the array. This was a holdover from when the array
> was a static global variable. It no longer confers any benefit.
>
> Also add a min() call to avoid the manual if/ternary operation.
>
> [...]
Applied, thanks!
[1/1] wifi: ath9k: remove TX99 power array zero init
commit: 13cdd324cc155200ea328257c746004d898ff3de
Best regards,
--
Jeff Johnson <jeff.johnson@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH ath-next] wifi: ath9k: Clear DMA descriptors without memset
From: Jeff Johnson @ 2026-06-06 15:54 UTC (permalink / raw)
To: linux-wireless, Rosen Penev
Cc: Toke Høiland-Jørgensen, linux-kernel
In-Reply-To: <20260517042716.2218386-1-rosenp@gmail.com>
On Sat, 16 May 2026 21:27:16 -0700, Rosen Penev wrote:
> Clear ath9k DMA descriptors with explicit status word stores instead of
> memset(). The descriptor rings are coherent DMA memory, which may be
> mapped uncached on 32-bit powerpc. The optimized memset() path can use
> dcbz there and trigger an alignment warning.
>
> Use WRITE_ONCE() for the descriptor status words so the compiler keeps
> the clears as ordinary stores instead of folding them back into bulk
> memset(). This covers AR9003 TX status descriptors as well as the RX
> status area cleared when setting up RX descriptors.
>
> [...]
Applied, thanks!
[1/1] wifi: ath9k: Clear DMA descriptors without memset
commit: 44589c155e6f4531100ddd3b6478190edd075035
Best regards,
--
Jeff Johnson <jeff.johnson@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCHv2 ath-next] wifi: ath9k_htc: use module_usb_driver
From: Jeff Johnson @ 2026-06-06 15:54 UTC (permalink / raw)
To: linux-wireless, Rosen Penev
Cc: Toke Høiland-Jørgensen, linux-kernel
In-Reply-To: <20260506234848.189840-1-rosenp@gmail.com>
On Wed, 06 May 2026 16:48:48 -0700, Rosen Penev wrote:
> This follows the pattern with other USB Wifi drivers. There is nothing
> special being done in the _init and _exit functions here. Simplifies and
> saves some lines of code.
>
>
Applied, thanks!
[1/1] wifi: ath9k_htc: use module_usb_driver
commit: 6a03ff9d5765ab701c12c9fc781fa6de171e1a2e
Best regards,
--
Jeff Johnson <jeff.johnson@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH wireless-next] wifi: wcn36xx: allocate chan_surveys with main struct
From: Jeff Johnson @ 2026-06-06 15:53 UTC (permalink / raw)
To: linux-wireless, Rosen Penev; +Cc: Loic Poulain, wcn36xx, linux-kernel
In-Reply-To: <20260519020317.635011-1-rosenp@gmail.com>
On Mon, 18 May 2026 19:03:17 -0700, Rosen Penev wrote:
> Avoid allocating separately with a flexible array member. Simplifies
> allocation slightly.
>
>
Applied, thanks!
[1/1] wifi: wcn36xx: allocate chan_surveys with main struct
commit: 0bd50e363581a9f833c051f7543ffd1fd3455509
Best regards,
--
Jeff Johnson <jeff.johnson@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH] wifi: wcn36xx: fix spelling mistakes in dxe header comment
From: Jeff Johnson @ 2026-06-06 15:53 UTC (permalink / raw)
To: loic.poulain, Stepan Ionichev; +Cc: wcn36xx, linux-wireless, linux-kernel
In-Reply-To: <20260503165832.1675-1-sozdayvek@gmail.com>
On Sun, 03 May 2026 21:58:32 +0500, Stepan Ionichev wrote:
> Fix three spelling mistakes in the DMA Transfer Engine (DXE)
> description comment at the top of dxe.c.
>
> No functional change.
>
>
Applied, thanks!
[1/1] wifi: wcn36xx: fix spelling mistakes in dxe header comment
commit: a969232fa359950f7cf2ea415938562263369909
Best regards,
--
Jeff Johnson <jeff.johnson@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH wireless-next] wifi: wcn36xx: allocate chan_surveys with main struct
From: Loic Poulain @ 2026-06-06 13:28 UTC (permalink / raw)
To: Rosen Penev
Cc: linux-wireless, open list:QUALCOMM WCN36XX WIRELESS DRIVER,
open list
In-Reply-To: <20260519020317.635011-1-rosenp@gmail.com>
On Tue, May 19, 2026 at 4:03 AM Rosen Penev <rosenp@gmail.com> wrote:
>
> Avoid allocating separately with a flexible array member. Simplifies
> allocation slightly.
>
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
Reviewed-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> ---
> drivers/net/wireless/ath/wcn36xx/main.c | 13 ++-----------
> drivers/net/wireless/ath/wcn36xx/wcn36xx.h | 2 +-
> 2 files changed, 3 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/net/wireless/ath/wcn36xx/main.c b/drivers/net/wireless/ath/wcn36xx/main.c
> index c3f0860873de..ad8a4bd910d2 100644
> --- a/drivers/net/wireless/ath/wcn36xx/main.c
> +++ b/drivers/net/wireless/ath/wcn36xx/main.c
> @@ -1568,7 +1568,8 @@ static int wcn36xx_probe(struct platform_device *pdev)
>
> wcnss = dev_get_drvdata(pdev->dev.parent);
>
> - hw = ieee80211_alloc_hw(sizeof(struct wcn36xx), &wcn36xx_ops);
> + n_channels = wcn_band_2ghz.n_channels + wcn_band_5ghz.n_channels;
> + hw = ieee80211_alloc_hw(struct_size(wcn, chan_survey, n_channels), &wcn36xx_ops);
> if (!hw) {
> wcn36xx_err("failed to alloc hw\n");
> ret = -ENOMEM;
> @@ -1590,16 +1591,6 @@ static int wcn36xx_probe(struct platform_device *pdev)
> goto out_wq;
> }
>
> - n_channels = wcn_band_2ghz.n_channels + wcn_band_5ghz.n_channels;
> - wcn->chan_survey = devm_kcalloc(wcn->dev,
> - n_channels,
> - sizeof(struct wcn36xx_chan_survey),
> - GFP_KERNEL);
> - if (!wcn->chan_survey) {
> - ret = -ENOMEM;
> - goto out_wq;
> - }
> -
> ret = dma_set_mask_and_coherent(wcn->dev, DMA_BIT_MASK(32));
> if (ret < 0) {
> wcn36xx_err("failed to set DMA mask: %d\n", ret);
> diff --git a/drivers/net/wireless/ath/wcn36xx/wcn36xx.h b/drivers/net/wireless/ath/wcn36xx/wcn36xx.h
> index 7ee79593cd23..8c43f67bd780 100644
> --- a/drivers/net/wireless/ath/wcn36xx/wcn36xx.h
> +++ b/drivers/net/wireless/ath/wcn36xx/wcn36xx.h
> @@ -298,7 +298,7 @@ struct wcn36xx {
> struct ieee80211_channel *channel;
>
> spinlock_t survey_lock; /* protects chan_survey */
> - struct wcn36xx_chan_survey *chan_survey;
> + struct wcn36xx_chan_survey chan_survey[];
> };
>
> static inline bool wcn36xx_is_fw_version(struct wcn36xx *wcn,
> --
> 2.54.0
>
^ permalink raw reply
* [PATCH] wifi: mac80211: bound S1G TIM PVB walk to the TIM element
From: Bryam Vargas @ 2026-06-06 7:43 UTC (permalink / raw)
To: Johannes Berg; +Cc: Lachlan Hodges, linux-wireless, linux-kernel
ieee80211_s1g_check_tim() parses the S1G Partial Virtual Bitmap (PVB) of a
received TIM element. The TIM is handed in as the element payload:
ieee802_11_parse_elems_full() stores elems->tim = elem->data and
elems->tim_len = elem->datalen (net/mac80211/parse.c), so the valid bytes
are [tim, tim + tim_len).
When walking the encoded blocks the function passes the walker an end
sentinel of (const u8 *)tim + tim_len + 2, i.e. two bytes past the end of
the element. ieee80211_s1g_find_target_block() loops while (ptr + 1 <= end)
and dereferences ptr (and the per-mode ieee80211_s1g_len_*() helpers read
*ptr), so it can read up to two bytes beyond the TIM element -- an
out-of-bounds read of adjacent skb/heap data when the TIM is the last
element in the frame. The +2 appears to account for the element id/len
header, but tim already points past that header at the element payload, so
the addend is wrong.
Pass the correct element end, (const u8 *)tim + tim_len.
Fixes: e0c47c6229c2 ("wifi: mac80211: support parsing S1G TIM PVB")
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
Everything below the --- is dropped by git am.
Class / impact: CWE-125 out-of-bounds read (CWE-193 off-by-two end
sentinel). An S1G (802.11ah) station that has the PS_NULLFUNC_STACK
software path processes the TIM of every beacon from its AP
(ieee80211_rx_mgmt_beacon() -> ieee80211_check_tim(..., s1g=true) ->
ieee80211_s1g_check_tim()). A beacon -- which is unauthenticated and can be
spoofed over the air by anything in radio range -- carrying a short S1G TIM
element placed last in the frame makes the PVB walker read up to two bytes
past the element. The bytes only influence the boolean "is my AID buffered"
result, so this is a robustness/hardening fix (no leak to userspace, no
corruption); KASAN flags it as a slab out-of-bounds read.
Affected: introduced this merge window by e0c47c6229c2; the buggy +2 is
present at mainline v7.1-rc6 and at torvalds/master 8e65320d (2026-06-06).
It is not in any released stable tree yet, so no Cc: stable here -- but note
"[PATCH AUTOSEL 6.17-6.12] wifi: mac80211: support parsing S1G TIM PVB" is
proposing to backport the feature (with this bug) to 6.12..6.17; if that
lands this fix should ride along.
A/B verification:
1) Full-system KASAN (x86-64, KASAN_GENERIC+INLINE, kasan.fault=report).
A test module calls the real in-kernel ieee80211_s1g_check_tim() on a TIM
kmalloc()'d at exactly tim_len bytes, with a crafted single-mode PVB block
whose block offset never matches the target so the walk runs to the
element end. Without this patch:
BUG: KASAN: slab-out-of-bounds in ieee80211_s1g_find_target_block
Read of size 1 at addr ffff88810340d345 by task insmod
ieee80211_s1g_find_target_block
<test caller>
The buggy address is located 0 bytes to the right of
allocated 5-byte region [ffff88810340d340, ffff88810340d345)
cache kmalloc-8
A well-formed PVB (block offset matches the target AID) produces no
report. With this patch the crafted case produces no report.
2) ABI-invariant userspace AddressSanitizer extraction of the verbatim
walker (-m64 and -m32): both report
heap-buffer-overflow READ of size 1 ... 0 bytes after 5-byte region
in ieee80211_s1g_find_target_block
without the patch; clean with the patch and clean on a well-formed PVB.
include/linux/ieee80211-s1g.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/linux/ieee80211-s1g.h b/include/linux/ieee80211-s1g.h
index 22dde4cbc1b0..3f9626ad3d97 100644
--- a/include/linux/ieee80211-s1g.h
+++ b/include/linux/ieee80211-s1g.h
@@ -556,7 +556,7 @@ static inline bool ieee80211_s1g_check_tim(const struct ieee80211_tim_ie *tim,
*/
err = ieee80211_s1g_find_target_block(&enc_blk, &target_aid,
tim->virtual_map,
- (const u8 *)tim + tim_len + 2);
+ (const u8 *)tim + tim_len);
if (err)
return false;
^ permalink raw reply related
* mac80211: fix HT MCS verification to ignore multi-stream basic rates
From: Armando Torres @ 2026-06-06 6:02 UTC (permalink / raw)
To: linux-wireless
Hello,
mac80211 currently fails HT association if the AP marks any MCS rates
as basic that the STA doesn't support. Some APs (observed with a
Netgear CGM4331COM/Xfinity gateway) mark
3-stream MCS rates (basic_set byte 2 = 0xff) as required, even though
the 802.11 spec does not mandate that all clients support multi-stream
rates.
This causes Intel BE200 cards (2-stream only) to fall back to legacy
54 Mbps mode instead of negotiating HT/VHT/HE, even though the AP
and card are fully capable.
Fix by only verifying single-stream (byte 0) basic MCS rates, which
are the only ones truly mandatory for basic interoperability.
Tested with:
- Intel Wi-Fi 7 BE200 (iwlmld driver, firmware 103)
- Netgear CGM4331COM gateway (Xfinity)
- Linux 6.17.0-1025-oem
Before: width: 20 MHz (no HT), rx bitrate: 54 Mbps
After: width: 80 MHz, rx bitrate: 680 Mbps (HE/Wi-Fi 6)
Signed-off-by: Armando Torres <armando.j.tor@gmail.com>
---
net/mac80211/mlme.c | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff net/mac80211/mlme.c.bak net/mac80211/mlme.c
522d521
< ieee80211_apply_htcap_overrides(sdata, &sta_ht_cap);
535,540c534,539
< /* Simply check that all basic rates are in the STA RX mask */
< for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) {
< if ((ht_op->basic_set[i] & sta_ht_cap.mcs.rx_mask[i]) !=
< ht_op->basic_set[i])
< return false;
< }
---
> /* Check that single-stream basic rates are supported (byte 0 only).
> * Ignore multi-stream requirements as client may not support them.
> */
> if ((ht_op->basic_set[0] & sta_ht_cap.mcs.rx_mask[0]) !=
> ht_op->basic_set[0])
> return false;
^ permalink raw reply
* wireless-regdb: Missing 5470-5725 MHz WAS/RLAN band for Georgia (GE)?
From: ekze @ 2026-06-06 4:37 UTC (permalink / raw)
To: linux-wireless
Hello,
I noticed that the current wireless-regdb entry for Georgia (GE) does not
include the 5470-5725 MHz WAS/RLAN band.
According to the Georgian Communications Commission frequency plan, Georgia
allows WAS/RLAN operation in 5470-5725 MHz with 1 W averaged EIRP, subject
to mitigation requirements based on EN 301 893, including DFS/TPC
requirements. The document also notes that if TPC is not used, the EIRP
limits should be reduced by 3 dB.
Official source:
Georgian Communications Commission, National Frequency Plan / Frequency
Allocation Table:
https://comcom.ge/uploads/other/14/14075.pdf
Relevant entries in the document:
- 5150-5350 MHz: WAS/RLAN, 200 mW averaged EIRP, indoor only
- 5470-5725 MHz: WAS/RLAN, 1 W averaged EIRP
- 5945-6425 MHz: RLAN, 200 mW averaged EIRP
The notes for 5250-5350 MHz and 5470-5725 MHz reference EN 301 893
mitigation requirements and TPC, with a 3 dB EIRP reduction if TPC is not
used.
Would the following update for GE be appropriate?
diff --git a/db.txt b/db.txt
--- a/db.txt
+++ b/db.txt
@@
country GE: DFS-ETSI
(2402 - 2482 @ 40), (20)
(5170 - 5250 @ 80), (18), AUTO-BW
(5250 - 5330 @ 80), (18), DFS, AUTO-BW
+ (5470 - 5725 @ 160), (27), DFS
(5945 - 6425 @ 320), (23), NO-OUTDOOR
(57000 - 66000 @ 2160), (40)
I used 27 dBm here because the official document lists 1 W EIRP with TPC,
and says that the limit should be reduced by 3 dB if TPC is not used.
Please let me know if a different representation would be preferred.
Thanks,
ekze
^ permalink raw reply
* Re: [PATCH v3 2/3] remoteproc: qcom_wcnss_iris: Add support for WCN3610
From: Jeff Johnson @ 2026-06-06 0:33 UTC (permalink / raw)
To: Krzysztof Kozlowski, Kerigan Creighton, linux-wireless
Cc: loic.poulain, wcn36xx, andersson, mathieu.poirier,
linux-remoteproc, linux-arm-msm, robh, krzk+dt, conor+dt,
devicetree, linux-kernel, Dmitry Baryshkov
In-Reply-To: <4f94c20c-d06f-48e5-95fb-5380c84a1b99@kernel.org>
On 3/5/2026 11:25 PM, Krzysztof Kozlowski wrote:
> On 06/03/2026 01:43, Kerigan Creighton wrote:
>> WCN3610 has the same regulator requirements as
>> WCN3620, so in qcom_wcnss_iris, we can use wcn3620_data.
>>
>> A separate compatible is needed for WCN3610 because the
>> wcn36xx driver uses it for chip-specific configuration.
>> Specifically, it sets BTC (Bluetooth Coexistence) CFGs,
>> disables ENABLE_DYNAMIC_RA_START_RATE, and disables
>> STA_POWERSAVE for this specific chip for stable
>> functionality.
>
> This goes to the binding description where you describe the hardware,
> how I asked.
>
> Please wrap commit message according to Linux coding style / submission
> process (neither too early nor over the limit):
> https://elixir.bootlin.com/linux/v6.4-rc1/source/Documentation/process/submitting-patches.rst#L597
This series is sitting in my patchwork queue.
Based upon Krzysztof's comments there should be a v4 that moves some
descriptive text from 2/3 to 1/3.
Bjorn: Once v4 lands, do you want to take this series or should I?
(Need to know if I should wait for ACK of 2/3 or give ACK for 3/3).
/jeff
^ permalink raw reply
* Re: [PATCH v3 2/3] wifi: wcn36xx: fix OOB read from firmware count in PRINT_REG_INFO indication
From: Jeff Johnson @ 2026-06-05 21:38 UTC (permalink / raw)
To: Tristan Madani, Loic Poulain
Cc: Johannes Berg, wcn36xx, linux-wireless, Tristan Madani
In-Reply-To: <20260421135018.352774-3-tristmd@gmail.com>
On 4/21/2026 6:50 AM, Tristan Madani wrote:
> From: Tristan Madani <tristan@talencesecurity.com>
>
> The firmware-controlled rsp->count field is used as the loop bound for
> indexing into the flexible rsp->regs[] array without validation against
> the message length. A count exceeding the actual data causes out-of-
> bounds reads from the heap-allocated message buffer.
>
> Add a check that count fits within the received message.
>
> Fixes: 43efa3c0f241 ("wcn36xx: Implement print_reg indication")
> Signed-off-by: Tristan Madani <tristan@talencesecurity.com>
Propagating from v2 so that b4 will pick it up...
Reviewed-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH v3 1/3] wifi: wcn36xx: fix heap overflow from oversized firmware HAL response
From: Loic Poulain @ 2026-06-05 20:11 UTC (permalink / raw)
To: Jeff Johnson
Cc: Tristan Madani, Johannes Berg, wcn36xx, linux-wireless,
Tristan Madani
In-Reply-To: <7c01afaf-5530-4ca2-b2f3-dbe95ddfe6ee@oss.qualcomm.com>
Hi Jeff,
On Fri, Jun 5, 2026 at 3:43 AM Jeff Johnson
<jeff.johnson@oss.qualcomm.com> wrote:
>
> On 4/21/2026 6:50 AM, Tristan Madani wrote:
> > From: Tristan Madani <tristan@talencesecurity.com>
> >
> > The firmware response dispatcher copies all synchronous HAL responses
> > into the 4096-byte hal_buf without validating the response length. A
> > response exceeding WCN36XX_HAL_BUF_SIZE causes a heap buffer overflow
> > with firmware-controlled content.
> >
> > Add a bounds check on the response length.
> >
> > Fixes: 8e84c2582169 ("wcn36xx: mac80211 driver for Qualcomm WCN3660/WCN3680 hardware")
> > Signed-off-by: Tristan Madani <tristan@talencesecurity.com>
> > ---
> > Changes in v3:
> > - Regenerated from wireless-next with proper git format-patch to
> > produce valid index hashes (v2 had post-processed index lines).
> >
> > Changes in v2:
> > - No code changes from v1.
> >
> > drivers/net/wireless/ath/wcn36xx/smd.c | 4 ++++
> > 1 file changed, 4 insertions(+)
> >
> > diff --git a/drivers/net/wireless/ath/wcn36xx/smd.c b/drivers/net/wireless/ath/wcn36xx/smd.c
> > index 813553edcb789..f65328329f4f0 100644
> > --- a/drivers/net/wireless/ath/wcn36xx/smd.c
> > +++ b/drivers/net/wireless/ath/wcn36xx/smd.c
> > @@ -3293,6 +3293,10 @@ int wcn36xx_smd_rsp_process(struct rpmsg_device *rpdev,
> > case WCN36XX_HAL_EXIT_IMPS_RSP:
> > case WCN36XX_HAL_UPDATE_CHANNEL_LIST_RSP:
> > case WCN36XX_HAL_ADD_BCN_FILTER_RSP:
> > + if (len > WCN36XX_HAL_BUF_SIZE) {
> > + wcn36xx_warn("HAL response too large: %d\n", len);
> > + break;
> > + }
> > memcpy(wcn->hal_buf, buf, len);
> > wcn->hal_rsp_len = len;
> > complete(&wcn->hal_rsp_compl);
>
> AI review points out that this logic will bypass the complete() meaning
> callers waiting for completion will be stuck (either forever or until the
> specified timeout expires). It proposes setting len = 0 instead of break and
> having each waiter deal with the issue that wcn->hal_rsp_len is 0.
>
> Further probing gave the observation that there is only one waiter
> wcn36xx_smd_send_and_wait() so there isn't a "wait forever" scenario.
>
> It also confirmed that setting wcn->hal_rsp_len = 0 would subsequently be
> processed by wcn36xx_smd_rsp_status_check() which would return -EIO due to len
> < sizeof(header) + sizeof(status_rsp).
>
> So setting len = 0 and still calling complete() would avoid the timeout and
> would cause -EIO vs -ETIME to be propagated.
>
> I can go either way with this since it is not expected to occur.
Thanks, the above analysis is indeed correct, but let's make it simple
and go as is, with the timeout.
Regards,
Loic
^ permalink raw reply
* [PATCH v12 22/22] mmc: core: add NXP IW61x base ID and block size quirk
From: Jeff Chen @ 2026-06-05 16:13 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, briannorris, johannes, francesco, wyatt.hsu,
s.hauer, ulf.hansson, Jeff Chen
In-Reply-To: <20260605161335.2415583-1-jeff.chen_1@nxp.com>
The NXP IW61x series SDIO chipset identifies itself with a base card ID
(0x0204) during the initial MMC bus scan, while the specific WLAN
function reports a different ID (0x0205).
To ensure that the MMC_QUIRK_BLKSZ_FOR_BYTE_MODE quirk is correctly
inherited by all SDIO functions (including Wi-Fi), it must be attached
to the base card ID at the core level.
Add the SDIO_DEVICE_ID_NXP_IW61X_BASE definition and apply the required
fixup in the SDIO quirk table.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/mmc/core/quirks.h | 3 +++
include/linux/mmc/sdio_ids.h | 1 +
2 files changed, 4 insertions(+)
diff --git a/drivers/mmc/core/quirks.h b/drivers/mmc/core/quirks.h
index 940549d3b95d..ae3ece89d0aa 100644
--- a/drivers/mmc/core/quirks.h
+++ b/drivers/mmc/core/quirks.h
@@ -208,6 +208,9 @@ static const struct mmc_fixup __maybe_unused sdio_fixup_methods[] = {
SDIO_FIXUP(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8887_F0,
add_limit_rate_quirk, 150000000),
+ SDIO_FIXUP(SDIO_VENDOR_ID_NXP, SDIO_DEVICE_ID_NXP_IW61X_BASE,
+ add_quirk, MMC_QUIRK_BLKSZ_FOR_BYTE_MODE),
+
END_FIXUP
};
diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h
index 0685dd717e85..7dac5428afe0 100644
--- a/include/linux/mmc/sdio_ids.h
+++ b/include/linux/mmc/sdio_ids.h
@@ -118,6 +118,7 @@
#define SDIO_DEVICE_ID_MICROCHIP_WILC1000 0x5347
#define SDIO_VENDOR_ID_NXP 0x0471
+#define SDIO_DEVICE_ID_NXP_IW61X_BASE 0x0204
#define SDIO_DEVICE_ID_NXP_IW61X 0x0205
#define SDIO_VENDOR_ID_REALTEK 0x024c
--
2.34.1
^ permalink raw reply related
* [PATCH v12 21/22] wifi: nxpwifi: add MAINTAINERS entry for nxpwifi driver
From: Jeff Chen @ 2026-06-05 16:13 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, briannorris, johannes, francesco, wyatt.hsu,
s.hauer, ulf.hansson, Jeff Chen
In-Reply-To: <20260605161335.2415583-1-jeff.chen_1@nxp.com>
Add a new section to the MAINTAINERS file for the nxpwifi driver,
including primary maintainer, reviewers, mailing list, and file path
patterns. This ensures proper tracking, patch routing, and community
visibility for the NXP Wi-Fi SDIO driver.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 142a91386338..55be8e5ce3cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19329,6 +19329,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
--
2.34.1
^ permalink raw reply related
* [PATCH v12 20/22] wifi: nxpwifi: add Kconfig and Makefile for kernel integration
From: Jeff Chen @ 2026-06-05 16:13 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, briannorris, johannes, francesco, wyatt.hsu,
s.hauer, ulf.hansson, Jeff Chen
In-Reply-To: <20260605161335.2415583-1-jeff.chen_1@nxp.com>
Add Kconfig and Makefile entries to integrate the nxpwifi driver into the
kernel build system. Enable selection of the driver under the
WLAN_VENDOR_NXP menu and support building the SDIO transport backend.
Add WLAN_VENDOR_NXP to drivers/net/wireless, define the NXPWIFI and
NXPWIFI_SDIO configuration options, and create Makefiles for the nxp/ and
nxpwifi/ directories. Register the nxpwifi core and nxpwifi_sdio transport
modules as build targets.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/Kconfig | 1 +
drivers/net/wireless/Makefile | 1 +
drivers/net/wireless/nxp/Kconfig | 17 ++++++++++
drivers/net/wireless/nxp/Makefile | 3 ++
drivers/net/wireless/nxp/nxpwifi/Kconfig | 22 +++++++++++++
drivers/net/wireless/nxp/nxpwifi/Makefile | 39 +++++++++++++++++++++++
6 files changed, 83 insertions(+)
create mode 100644 drivers/net/wireless/nxp/Kconfig
create mode 100644 drivers/net/wireless/nxp/Makefile
create mode 100644 drivers/net/wireless/nxp/nxpwifi/Kconfig
create mode 100644 drivers/net/wireless/nxp/nxpwifi/Makefile
diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig
index c6599594dc99..4d7b81182925 100644
--- a/drivers/net/wireless/Kconfig
+++ b/drivers/net/wireless/Kconfig
@@ -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"
diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile
index e1c4141c6004..0c6b3cc719db 100644
--- a/drivers/net/wireless/Makefile
+++ b/drivers/net/wireless/Makefile
@@ -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/
diff --git a/drivers/net/wireless/nxp/Kconfig b/drivers/net/wireless/nxp/Kconfig
new file mode 100644
index 000000000000..68b32d4536e5
--- /dev/null
+++ b/drivers/net/wireless/nxp/Kconfig
@@ -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
diff --git a/drivers/net/wireless/nxp/Makefile b/drivers/net/wireless/nxp/Makefile
new file mode 100644
index 000000000000..27b41a0afdd2
--- /dev/null
+++ b/drivers/net/wireless/nxp/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_NXPWIFI) += nxpwifi/
diff --git a/drivers/net/wireless/nxp/nxpwifi/Kconfig b/drivers/net/wireless/nxp/nxpwifi/Kconfig
new file mode 100644
index 000000000000..3637068574b8
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/Kconfig
@@ -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.
diff --git a/drivers/net/wireless/nxp/nxpwifi/Makefile b/drivers/net/wireless/nxp/nxpwifi/Makefile
new file mode 100644
index 000000000000..8f581429f28d
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/Makefile
@@ -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
--
2.34.1
^ permalink raw reply related
* [PATCH v12 14/22] wifi: nxpwifi: add debugfs support for diagnostics and testing
From: Jeff Chen @ 2026-06-05 16:13 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, briannorris, johannes, francesco, wyatt.hsu,
s.hauer, ulf.hansson, Jeff Chen
In-Reply-To: <20260605161335.2415583-1-jeff.chen_1@nxp.com>
Add a debugfs interface for the nxpwifi driver to support diagnostics,
runtime inspection, and testing. Provide entries for examining firmware
state, driver statistics, power-management information, and radar/DFS
behavior, helping with both development and field debugging.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/debugfs.c | 1094 ++++++++++++++++++++
1 file changed, 1094 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/debugfs.c
diff --git a/drivers/net/wireless/nxp/nxpwifi/debugfs.c b/drivers/net/wireless/nxp/nxpwifi/debugfs.c
new file mode 100644
index 000000000000..ccaf0eae37e3
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/debugfs.c
@@ -0,0 +1,1094 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nxpwifi: debugfs
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#include <linux/debugfs.h>
+
+#include "main.h"
+#include "cmdevt.h"
+#include "11n.h"
+
+static struct dentry *nxpwifi_dfs_dir;
+
+static char *bss_modes[] = {
+ "UNSPECIFIED",
+ "ADHOC",
+ "STATION",
+ "AP",
+ "AP_VLAN",
+ "WDS",
+ "MONITOR",
+ "MESH_POINT",
+ "P2P_CLIENT",
+ "P2P_GO",
+ "P2P_DEVICE",
+};
+
+/*
+ * debugfs "info" read handler: dump driver name/version, interface, BSS mode,
+ * link state, MAC, counters; STA adds SSID/BSSID/channel/country/region and
+ * multicast list.
+ */
+static ssize_t
+nxpwifi_info_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv =
+ (struct nxpwifi_private *)file->private_data;
+ struct net_device *netdev = priv->netdev;
+ struct netdev_hw_addr *ha;
+ struct netdev_queue *txq;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page, fmt[64];
+ struct nxpwifi_bss_info info;
+ ssize_t ret;
+ int i = 0;
+
+ if (!p)
+ return -ENOMEM;
+
+ memset(&info, 0, sizeof(info));
+ ret = nxpwifi_get_bss_info(priv, &info);
+ if (ret)
+ goto free_and_exit;
+
+ nxpwifi_drv_get_driver_version(priv->adapter, fmt, sizeof(fmt) - 1);
+
+ nxpwifi_get_ver_ext(priv, 0);
+
+ p += sprintf(p, "driver_name = ");
+ p += sprintf(p, "\"nxpwifi\"\n");
+ p += sprintf(p, "driver_version = %s", fmt);
+ p += sprintf(p, "\nverext = %s", priv->version_str);
+ p += sprintf(p, "\ninterface_name=\"%s\"\n", netdev->name);
+
+ if (info.bss_mode >= ARRAY_SIZE(bss_modes))
+ p += sprintf(p, "bss_mode=\"%d\"\n", info.bss_mode);
+ else
+ p += sprintf(p, "bss_mode=\"%s\"\n", bss_modes[info.bss_mode]);
+
+ p += sprintf(p, "media_state=\"%s\"\n",
+ (!priv->media_connected ? "Disconnected" : "Connected"));
+ p += sprintf(p, "mac_address=\"%pM\"\n", netdev->dev_addr);
+
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) {
+ p += sprintf(p, "multicast_count=\"%d\"\n",
+ netdev_mc_count(netdev));
+ p += sprintf(p, "essid=\"%.*s\"\n", info.ssid.ssid_len,
+ info.ssid.ssid);
+ p += sprintf(p, "bssid=\"%pM\"\n", info.bssid);
+ p += sprintf(p, "channel=\"%d\"\n", (int)info.bss_chan);
+ p += sprintf(p, "country_code = \"%s\"\n", info.country_code);
+ p += sprintf(p, "region_code=\"0x%x\"\n",
+ priv->adapter->region_code);
+
+ netdev_for_each_mc_addr(ha, netdev)
+ p += sprintf(p, "multicast_address[%d]=\"%pM\"\n",
+ i++, ha->addr);
+ }
+
+ p += sprintf(p, "num_tx_bytes = %lu\n", priv->stats.tx_bytes);
+ p += sprintf(p, "num_rx_bytes = %lu\n", priv->stats.rx_bytes);
+ p += sprintf(p, "num_tx_pkts = %lu\n", priv->stats.tx_packets);
+ p += sprintf(p, "num_rx_pkts = %lu\n", priv->stats.rx_packets);
+ p += sprintf(p, "num_tx_pkts_dropped = %lu\n", priv->stats.tx_dropped);
+ p += sprintf(p, "num_rx_pkts_dropped = %lu\n", priv->stats.rx_dropped);
+ p += sprintf(p, "num_tx_pkts_err = %lu\n", priv->stats.tx_errors);
+ p += sprintf(p, "num_rx_pkts_err = %lu\n", priv->stats.rx_errors);
+ p += sprintf(p, "carrier %s\n", ((netif_carrier_ok(priv->netdev))
+ ? "on" : "off"));
+ p += sprintf(p, "tx queue");
+ for (i = 0; i < netdev->num_tx_queues; i++) {
+ txq = netdev_get_tx_queue(netdev, i);
+ p += sprintf(p, " %d:%s", i, netif_tx_queue_stopped(txq) ?
+ "stopped" : "started");
+ }
+ p += sprintf(p, "\n");
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page,
+ (unsigned long)p - page);
+
+free_and_exit:
+ free_page(page);
+ return ret;
+}
+
+/*
+ * debugfs "getlog" read handler: dump firmware/802.11 counters (retry, RTS/ACK, dup,
+ * frag, mcast, FCS, beacon stats).
+ */
+static ssize_t
+nxpwifi_getlog_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv =
+ (struct nxpwifi_private *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ ssize_t ret;
+ struct nxpwifi_ds_get_stats stats;
+
+ if (!p)
+ return -ENOMEM;
+
+ memset(&stats, 0, sizeof(stats));
+ ret = nxpwifi_get_stats_info(priv, &stats);
+ if (ret)
+ goto free_and_exit;
+
+ p += sprintf(p, "\n"
+ "mcasttxframe %u\n"
+ "failed %u\n"
+ "retry %u\n"
+ "multiretry %u\n"
+ "framedup %u\n"
+ "rtssuccess %u\n"
+ "rtsfailure %u\n"
+ "ackfailure %u\n"
+ "rxfrag %u\n"
+ "mcastrxframe %u\n"
+ "fcserror %u\n"
+ "txframe %u\n"
+ "wepicverrcnt-1 %u\n"
+ "wepicverrcnt-2 %u\n"
+ "wepicverrcnt-3 %u\n"
+ "wepicverrcnt-4 %u\n"
+ "bcn_rcv_cnt %u\n"
+ "bcn_miss_cnt %u\n",
+ stats.mcast_tx_frame,
+ stats.failed,
+ stats.retry,
+ stats.multi_retry,
+ stats.frame_dup,
+ stats.rts_success,
+ stats.rts_failure,
+ stats.ack_failure,
+ stats.rx_frag,
+ stats.mcast_rx_frame,
+ stats.fcs_error,
+ stats.tx_frame,
+ stats.wep_icv_error[0],
+ stats.wep_icv_error[1],
+ stats.wep_icv_error[2],
+ stats.wep_icv_error[3],
+ stats.bcn_rcv_cnt,
+ stats.bcn_miss_cnt);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page,
+ (unsigned long)p - page);
+
+free_and_exit:
+ free_page(page);
+ return ret;
+}
+
+/*
+ * debugfs "histogram" read handler: report sample count and per-rate/SNR/noise
+ * floor/signal strength histograms.
+ */
+static ssize_t
+nxpwifi_histogram_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv =
+ (struct nxpwifi_private *)file->private_data;
+ ssize_t ret;
+ struct nxpwifi_histogram_data *phist_data;
+ int i, value;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+
+ if (!p)
+ return -ENOMEM;
+
+ if (!priv || !priv->hist_data) {
+ ret = -EFAULT;
+ goto free_and_exit;
+ }
+
+ phist_data = priv->hist_data;
+
+ p += sprintf(p, "\n"
+ "total samples = %d\n",
+ atomic_read(&phist_data->num_samples));
+
+ p += sprintf(p,
+ "rx rates (in Mbps): 0=1M 1=2M 2=5.5M 3=11M 4=6M 5=9M 6=12M\n"
+ "7=18M 8=24M 9=36M 10=48M 11=54M 12-27=MCS0-15(BW20) 28-43=MCS0-15(BW40)\n");
+
+ if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info)) {
+ p += sprintf(p,
+ "44-53=MCS0-9(VHT:BW20) 54-63=MCS0-9(VHT:BW40) 64-73=MCS0-9(VHT:BW80)\n\n");
+ } else {
+ p += sprintf(p, "\n");
+ }
+
+ for (i = 0; i < NXPWIFI_MAX_RX_RATES; i++) {
+ value = atomic_read(&phist_data->rx_rate[i]);
+ if (value)
+ p += sprintf(p, "rx_rate[%02d] = %d\n", i, value);
+ }
+
+ if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info)) {
+ for (i = NXPWIFI_MAX_RX_RATES; i < NXPWIFI_MAX_AC_RX_RATES;
+ i++) {
+ value = atomic_read(&phist_data->rx_rate[i]);
+ if (value)
+ p += sprintf(p, "rx_rate[%02d] = %d\n",
+ i, value);
+ }
+ }
+
+ for (i = 0; i < NXPWIFI_MAX_SNR; i++) {
+ value = atomic_read(&phist_data->snr[i]);
+ if (value)
+ p += sprintf(p, "snr[%02ddB] = %d\n", i, value);
+ }
+ for (i = 0; i < NXPWIFI_MAX_NOISE_FLR; i++) {
+ value = atomic_read(&phist_data->noise_flr[i]);
+ if (value)
+ p += sprintf(p, "noise_flr[%02ddBm] = %d\n",
+ (int)(i - 128), value);
+ }
+ for (i = 0; i < NXPWIFI_MAX_SIG_STRENGTH; i++) {
+ value = atomic_read(&phist_data->sig_str[i]);
+ if (value)
+ p += sprintf(p, "sig_strength[-%02ddBm] = %d\n",
+ i, value);
+ }
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page,
+ (unsigned long)p - page);
+
+free_and_exit:
+ free_page(page);
+ return ret;
+}
+
+static ssize_t
+nxpwifi_histogram_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv = (void *)file->private_data;
+
+ if (priv && priv->hist_data)
+ nxpwifi_hist_data_reset(priv);
+ return 0;
+}
+
+static struct nxpwifi_debug_info info;
+
+/* debugfs "debug" read handler: dump adapter debug info and BA/reorder tables. */
+static ssize_t
+nxpwifi_debug_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv =
+ (struct nxpwifi_private *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ ssize_t ret;
+
+ if (!p)
+ return -ENOMEM;
+
+ ret = nxpwifi_get_debug_info(priv, &info);
+ if (ret)
+ goto free_and_exit;
+
+ p += nxpwifi_debug_info_to_buffer(priv, p, &info);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page,
+ (unsigned long)p - page);
+
+free_and_exit:
+ free_page(page);
+ return ret;
+}
+
+static u32 saved_reg_type, saved_reg_offset, saved_reg_value;
+
+/*
+ * debugfs "regrdwr" write handler: parse <type offset value> and store for
+ * readback/IO.
+ */
+static ssize_t
+nxpwifi_regrdwr_write(struct file *file,
+ const char __user *ubuf, size_t count, loff_t *ppos)
+{
+ char *buf;
+ int ret;
+ u32 reg_type = 0, reg_offset = 0, reg_value = UINT_MAX;
+ int rv;
+
+ buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1)));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ rv = sscanf(buf, "%u %x %x", ®_type, ®_offset, ®_value);
+
+ if (rv != 3) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (reg_type == 0 || reg_offset == 0) {
+ ret = -EINVAL;
+ goto done;
+ } else {
+ saved_reg_type = reg_type;
+ saved_reg_offset = reg_offset;
+ saved_reg_value = reg_value;
+ ret = count;
+ }
+done:
+ kfree(buf);
+ return ret;
+}
+
+/*
+ * debugfs "regrdwr" read handler: perform pending register read/write and return
+ * <type offset value>.
+ */
+static ssize_t
+nxpwifi_regrdwr_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv =
+ (struct nxpwifi_private *)file->private_data;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ int pos = 0, ret = 0;
+ u32 reg_value;
+
+ if (!buf)
+ return -ENOMEM;
+
+ if (!saved_reg_type) {
+ /* No command has been given */
+ pos += snprintf(buf, PAGE_SIZE, "0");
+ goto done;
+ }
+ /* Set command has been given */
+ if (saved_reg_value != UINT_MAX) {
+ ret = nxpwifi_reg_write(priv, saved_reg_type, saved_reg_offset,
+ saved_reg_value);
+
+ pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n",
+ saved_reg_type, saved_reg_offset,
+ saved_reg_value);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
+
+ goto done;
+ }
+ /* Get command has been given */
+ ret = nxpwifi_reg_read(priv, saved_reg_type,
+ saved_reg_offset, ®_value);
+ if (ret) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", saved_reg_type,
+ saved_reg_offset, reg_value);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
+
+done:
+ free_page(addr);
+ return ret;
+}
+
+/* debugfs "debug_mask" read handler: show driver debug mask. */
+
+static ssize_t
+nxpwifi_debug_mask_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv =
+ (struct nxpwifi_private *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)page;
+ size_t ret = 0;
+ int pos = 0;
+
+ if (!buf)
+ return -ENOMEM;
+
+ pos += snprintf(buf, PAGE_SIZE, "debug mask=0x%08x\n",
+ priv->adapter->debug_mask);
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
+
+ free_page(page);
+ return ret;
+}
+
+/* debugfs "debug_mask" write handler: set driver debug mask. */
+
+static ssize_t
+nxpwifi_debug_mask_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ int ret;
+ unsigned long debug_mask;
+ struct nxpwifi_private *priv = (void *)file->private_data;
+ char *buf;
+
+ buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1)));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ if (kstrtoul(buf, 0, &debug_mask)) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ priv->adapter->debug_mask = debug_mask;
+ ret = count;
+done:
+ kfree(buf);
+ return ret;
+}
+
+/* debugfs "verext" write handler: select extended version string. */
+static ssize_t
+nxpwifi_verext_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ int ret;
+ u32 versionstrsel;
+ struct nxpwifi_private *priv = (void *)file->private_data;
+
+ ret = kstrtou32_from_user(ubuf, count, 10, &versionstrsel);
+ if (ret)
+ return ret;
+
+ priv->versionstrsel = versionstrsel;
+
+ return count;
+}
+
+/* debugfs "verext" read handler: show extended version string. */
+static ssize_t
+nxpwifi_verext_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv =
+ (struct nxpwifi_private *)file->private_data;
+ char buf[256];
+ int ret;
+
+ nxpwifi_get_ver_ext(priv, priv->versionstrsel);
+ ret = snprintf(buf, sizeof(buf), "version string: %s\n",
+ priv->version_str);
+
+ return simple_read_from_buffer(ubuf, count, ppos, buf, ret);
+}
+
+/* debugfs "memrw" write handler: read/write firmware memory (addr, value). */
+static ssize_t
+nxpwifi_memrw_write(struct file *file, const char __user *ubuf, size_t count,
+ loff_t *ppos)
+{
+ int ret;
+ char cmd;
+ struct nxpwifi_ds_mem_rw mem_rw;
+ u16 cmd_action;
+ struct nxpwifi_private *priv = (void *)file->private_data;
+ char *buf;
+
+ buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1)));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ ret = sscanf(buf, "%c %x %x", &cmd, &mem_rw.addr, &mem_rw.value);
+ if (ret != 3) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if ((cmd == 'r') || (cmd == 'R')) {
+ cmd_action = HOST_ACT_GEN_GET;
+ mem_rw.value = 0;
+ } else if ((cmd == 'w') || (cmd == 'W')) {
+ cmd_action = HOST_ACT_GEN_SET;
+ } else {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ memcpy(&priv->mem_rw, &mem_rw, sizeof(mem_rw));
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_MEM_ACCESS, cmd_action, 0,
+ &mem_rw, true);
+ if (!ret)
+ ret = count;
+
+done:
+ kfree(buf);
+ return ret;
+}
+
+/* debugfs "memrw" read handler: show last memory access result. */
+static ssize_t
+nxpwifi_memrw_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv = (void *)file->private_data;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ int ret, pos = 0;
+
+ if (!buf)
+ return -ENOMEM;
+
+ pos += snprintf(buf, PAGE_SIZE, "0x%x 0x%x\n", priv->mem_rw.addr,
+ priv->mem_rw.value);
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
+
+ free_page(addr);
+ return ret;
+}
+
+static u32 saved_offset = -1, saved_bytes = -1;
+
+/* debugfs "rdeeprom" write handler: set EEPROM offset/length to read. */
+static ssize_t
+nxpwifi_rdeeprom_write(struct file *file,
+ const char __user *ubuf, size_t count, loff_t *ppos)
+{
+ char *buf;
+ int ret = 0;
+ int offset = -1, bytes = -1;
+ int rv;
+
+ buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1)));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ rv = sscanf(buf, "%d %d", &offset, &bytes);
+
+ if (rv != 2) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (offset == -1 || bytes == -1) {
+ ret = -EINVAL;
+ goto done;
+ } else {
+ saved_offset = offset;
+ saved_bytes = bytes;
+ ret = count;
+ }
+done:
+ kfree(buf);
+ return ret;
+}
+
+/* debugfs "rdeeprom" read handler: dump EEPROM bytes from saved offset/length. */
+static ssize_t
+nxpwifi_rdeeprom_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv =
+ (struct nxpwifi_private *)file->private_data;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ int pos, ret, i;
+ u8 value[MAX_EEPROM_DATA];
+
+ if (!buf)
+ return -ENOMEM;
+
+ if (saved_offset == -1) {
+ /* No command has been given */
+ pos = snprintf(buf, PAGE_SIZE, "0");
+ goto done;
+ }
+
+ /* Get command has been given */
+ ret = nxpwifi_eeprom_read(priv, (u16)saved_offset,
+ (u16)saved_bytes, value);
+ if (ret) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ pos = snprintf(buf, PAGE_SIZE, "%d %d ", saved_offset, saved_bytes);
+
+ for (i = 0; i < saved_bytes; i++)
+ pos += scnprintf(buf + pos, PAGE_SIZE - pos, "%d ", value[i]);
+
+done:
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
+out_free:
+ free_page(addr);
+ return ret;
+}
+
+/*
+ * debugfs "hscfg" write handler: configure host-sleep (conditions/gpio/gap) or
+ * cancel.
+ */
+static ssize_t
+nxpwifi_hscfg_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv = (void *)file->private_data;
+ char *buf;
+ int ret, arg_num;
+ struct nxpwifi_ds_hs_cfg hscfg;
+ int conditions = HS_CFG_COND_DEF;
+ u32 gpio = HS_CFG_GPIO_DEF, gap = HS_CFG_GAP_DEF;
+
+ buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1)));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ arg_num = sscanf(buf, "%d %x %x", &conditions, &gpio, &gap);
+
+ memset(&hscfg, 0, sizeof(struct nxpwifi_ds_hs_cfg));
+
+ if (arg_num > 3) {
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Too many arguments\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (arg_num >= 1 && arg_num < 3)
+ nxpwifi_set_hs_params(priv, HOST_ACT_GEN_GET,
+ NXPWIFI_SYNC_CMD, &hscfg);
+
+ if (arg_num) {
+ if (conditions == HS_CFG_CANCEL) {
+ nxpwifi_cancel_hs(priv, NXPWIFI_ASYNC_CMD);
+ ret = count;
+ goto done;
+ }
+ hscfg.conditions = conditions;
+ }
+ if (arg_num >= 2)
+ hscfg.gpio = gpio;
+ if (arg_num == 3)
+ hscfg.gap = gap;
+
+ hscfg.is_invoke_hostcmd = false;
+ nxpwifi_set_hs_params(priv, HOST_ACT_GEN_SET,
+ NXPWIFI_SYNC_CMD, &hscfg);
+
+ nxpwifi_enable_hs(priv->adapter);
+ clear_bit(NXPWIFI_IS_HS_ENABLING, &priv->adapter->work_flags);
+ ret = count;
+done:
+ kfree(buf);
+ return ret;
+}
+
+/* debugfs "hscfg" read handler: show current host-sleep configuration. */
+static ssize_t
+nxpwifi_hscfg_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv = (void *)file->private_data;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ int pos, ret;
+ struct nxpwifi_ds_hs_cfg hscfg;
+
+ if (!buf)
+ return -ENOMEM;
+
+ nxpwifi_set_hs_params(priv, HOST_ACT_GEN_GET,
+ NXPWIFI_SYNC_CMD, &hscfg);
+
+ pos = snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", hscfg.conditions,
+ hscfg.gpio, hscfg.gap);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
+
+ free_page(addr);
+ return ret;
+}
+
+static ssize_t
+nxpwifi_timeshare_coex_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv = file->private_data;
+ char buf[3];
+ bool timeshare_coex;
+ int ret;
+ unsigned int len;
+
+ if (priv->adapter->fw_api_ver != NXPWIFI_FW_V15)
+ return -EOPNOTSUPP;
+
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_ROBUST_COEX,
+ HOST_ACT_GEN_GET, 0, ×hare_coex, true);
+ if (ret)
+ return ret;
+
+ len = sprintf(buf, "%d\n", timeshare_coex);
+ return simple_read_from_buffer(ubuf, count, ppos, buf, len);
+}
+
+static ssize_t
+nxpwifi_timeshare_coex_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ bool timeshare_coex;
+ struct nxpwifi_private *priv = file->private_data;
+ int ret;
+
+ if (priv->adapter->fw_api_ver != NXPWIFI_FW_V15)
+ return -EOPNOTSUPP;
+
+ ret = kstrtobool_from_user(ubuf, count, ×hare_coex);
+ if (ret)
+ return ret;
+
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_ROBUST_COEX,
+ HOST_ACT_GEN_SET, 0, ×hare_coex, true);
+ if (ret)
+ return ret;
+ else
+ return count;
+}
+
+static ssize_t
+nxpwifi_reset_write(struct file *file,
+ const char __user *ubuf, size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv = file->private_data;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ bool result;
+ int rc;
+
+ rc = kstrtobool_from_user(ubuf, count, &result);
+ if (rc)
+ return rc;
+
+ if (!result)
+ return -EINVAL;
+
+ if (adapter->if_ops.card_reset) {
+ nxpwifi_dbg(adapter, INFO, "Resetting per request\n");
+ adapter->if_ops.card_reset(adapter);
+ }
+
+ return count;
+}
+
+static ssize_t
+nxpwifi_fake_radar_detect_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv = file->private_data;
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ bool result;
+ int rc;
+
+ rc = kstrtobool_from_user(ubuf, count, &result);
+ if (rc)
+ return rc;
+
+ if (!result)
+ return -EINVAL;
+
+ if (priv->wdev.links[0].cac_started) {
+ nxpwifi_dbg(adapter, MSG,
+ "Generate fake radar detected during CAC\n");
+ if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef))
+ nxpwifi_dbg(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(adapter->wiphy, &priv->dfs_chandef,
+ GFP_KERNEL);
+ } else {
+ if (priv->bss_chandef.chan->dfs_cac_ms) {
+ nxpwifi_dbg(adapter, MSG,
+ "Generate fake radar detected\n");
+ cfg80211_radar_event(adapter->wiphy,
+ &priv->dfs_chandef,
+ GFP_KERNEL);
+ }
+ }
+
+ return count;
+}
+
+static ssize_t
+nxpwifi_netmon_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ int ret;
+ struct nxpwifi_802_11_net_monitor netmon_cfg;
+ struct nxpwifi_private *priv = (void *)file->private_data;
+ char *buf;
+
+ buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1)));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+ memset(&netmon_cfg, 0, sizeof(struct nxpwifi_802_11_net_monitor));
+ ret = sscanf(buf, "%u %u %u %u %u",
+ &netmon_cfg.enable_net_mon,
+ &netmon_cfg.filter_flag,
+ &netmon_cfg.band,
+ &netmon_cfg.channel,
+ &netmon_cfg.chan_bandwidth);
+
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_NET_MONITOR,
+ HOST_ACT_GEN_SET, 0, &netmon_cfg, true);
+
+ if (!ret)
+ ret = count;
+
+ kfree(buf);
+ return ret;
+}
+
+static ssize_t
+nxpwifi_twt_setup_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ int ret;
+ struct nxpwifi_twt_cfg twt_cfg;
+ struct nxpwifi_private *priv = (void *)file->private_data;
+ char *buf;
+ u16 twt_mantissa, bcn_miss_threshold;
+
+ buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1)));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ ret = sscanf(buf, "%hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hu %hhu %hu",
+ &twt_cfg.param.twt_setup.implicit,
+ &twt_cfg.param.twt_setup.announced,
+ &twt_cfg.param.twt_setup.trigger_enabled,
+ &twt_cfg.param.twt_setup.twt_info_disabled,
+ &twt_cfg.param.twt_setup.negotiation_type,
+ &twt_cfg.param.twt_setup.twt_wakeup_duration,
+ &twt_cfg.param.twt_setup.flow_identifier,
+ &twt_cfg.param.twt_setup.hard_constraint,
+ &twt_cfg.param.twt_setup.twt_exponent,
+ &twt_mantissa,
+ &twt_cfg.param.twt_setup.twt_request,
+ &bcn_miss_threshold);
+
+ twt_cfg.param.twt_setup.twt_mantissa = cpu_to_le16(twt_mantissa);
+ twt_cfg.param.twt_setup.bcn_miss_threshold = cpu_to_le16(bcn_miss_threshold);
+ twt_cfg.sub_id = NXPWIFI_11AX_TWT_SETUP_SUBID;
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0,
+ &twt_cfg, true);
+ if (!ret)
+ ret = count;
+
+ kfree(buf);
+ return ret;
+}
+
+static ssize_t
+nxpwifi_twt_teardown_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ int ret;
+ struct nxpwifi_twt_cfg twt_cfg;
+ struct nxpwifi_private *priv = (void *)file->private_data;
+ char *buf;
+
+ buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1)));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ ret = sscanf(buf, "%hhu %hhu %hhu",
+ &twt_cfg.param.twt_teardown.flow_identifier,
+ &twt_cfg.param.twt_teardown.negotiation_type,
+ &twt_cfg.param.twt_teardown.teardown_all_twt);
+
+ twt_cfg.sub_id = NXPWIFI_11AX_TWT_TEARDOWN_SUBID;
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0,
+ &twt_cfg, true);
+
+ if (!ret)
+ ret = count;
+
+ kfree(buf);
+ return ret;
+}
+
+static ssize_t
+nxpwifi_twt_report_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct nxpwifi_private *priv =
+ (struct nxpwifi_private *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ ssize_t ret;
+ struct nxpwifi_twt_cfg twt_cfg;
+ u8 num, i, j;
+
+ if (!p)
+ return -ENOMEM;
+
+ twt_cfg.sub_id = NXPWIFI_11AX_TWT_REPORT_SUBID;
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_GET, 0,
+ &twt_cfg, true);
+ if (ret)
+ goto done;
+ num = twt_cfg.param.twt_report.length / NXPWIFI_BTWT_REPORT_LEN;
+ num = num <= NXPWIFI_BTWT_REPORT_MAX_NUM ? num : NXPWIFI_BTWT_REPORT_MAX_NUM;
+ p += sprintf(p, "\ntwt_report len %hhu, num %hhu, twt_report_info:\n",
+ twt_cfg.param.twt_report.length, num);
+ for (i = 0; i < num; i++) {
+ p += sprintf(p, "id[%hu]:\r\n", i);
+ for (j = 0; j < NXPWIFI_BTWT_REPORT_LEN; j++) {
+ p += sprintf(p,
+ " 0x%02x",
+ twt_cfg.param.twt_report.data[i * NXPWIFI_BTWT_REPORT_LEN + j]);
+ }
+ p += sprintf(p, "\r\n");
+ }
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page,
+ (unsigned long)p - page);
+
+done:
+ free_page(page);
+ return ret;
+}
+
+static ssize_t
+nxpwifi_twt_information_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ int ret;
+ struct nxpwifi_twt_cfg twt_cfg;
+ struct nxpwifi_private *priv = (void *)file->private_data;
+ char *buf;
+ u32 suspend_duration;
+
+ buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1)));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ ret = sscanf(buf, "%hhu %u",
+ &twt_cfg.param.twt_information.flow_identifier, &suspend_duration);
+ twt_cfg.param.twt_information.suspend_duration = cpu_to_le32(suspend_duration);
+
+ twt_cfg.sub_id = NXPWIFI_11AX_TWT_INFORMATION_SUBID;
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0,
+ &twt_cfg, true);
+
+ if (!ret)
+ ret = count;
+
+ kfree(buf);
+ return ret;
+}
+
+#define NXPWIFI_DFS_ADD_FILE(name) debugfs_create_file(#name, 0644, \
+ priv->dfs_dev_dir, priv, \
+ &nxpwifi_dfs_##name##_fops)
+
+#define NXPWIFI_DFS_FILE_OPS(name) \
+static const struct file_operations nxpwifi_dfs_##name##_fops = { \
+ .read = nxpwifi_##name##_read, \
+ .write = nxpwifi_##name##_write, \
+ .open = simple_open, \
+}
+
+#define NXPWIFI_DFS_FILE_READ_OPS(name) \
+static const struct file_operations nxpwifi_dfs_##name##_fops = { \
+ .read = nxpwifi_##name##_read, \
+ .open = simple_open, \
+}
+
+#define NXPWIFI_DFS_FILE_WRITE_OPS(name) \
+static const struct file_operations nxpwifi_dfs_##name##_fops = { \
+ .write = nxpwifi_##name##_write, \
+ .open = simple_open, \
+}
+
+NXPWIFI_DFS_FILE_READ_OPS(info);
+NXPWIFI_DFS_FILE_READ_OPS(debug);
+NXPWIFI_DFS_FILE_READ_OPS(getlog);
+NXPWIFI_DFS_FILE_OPS(regrdwr);
+NXPWIFI_DFS_FILE_OPS(rdeeprom);
+NXPWIFI_DFS_FILE_OPS(memrw);
+NXPWIFI_DFS_FILE_OPS(hscfg);
+NXPWIFI_DFS_FILE_OPS(histogram);
+NXPWIFI_DFS_FILE_OPS(debug_mask);
+NXPWIFI_DFS_FILE_OPS(timeshare_coex);
+NXPWIFI_DFS_FILE_WRITE_OPS(reset);
+NXPWIFI_DFS_FILE_WRITE_OPS(fake_radar_detect);
+NXPWIFI_DFS_FILE_OPS(verext);
+NXPWIFI_DFS_FILE_WRITE_OPS(netmon);
+NXPWIFI_DFS_FILE_WRITE_OPS(twt_setup);
+NXPWIFI_DFS_FILE_WRITE_OPS(twt_teardown);
+NXPWIFI_DFS_FILE_READ_OPS(twt_report);
+NXPWIFI_DFS_FILE_WRITE_OPS(twt_information);
+
+/* Create per-netdev debugfs directory and files. */
+void
+nxpwifi_dev_debugfs_init(struct nxpwifi_private *priv)
+{
+ if (!nxpwifi_dfs_dir || !priv)
+ return;
+
+ priv->dfs_dev_dir = debugfs_create_dir(priv->netdev->name,
+ nxpwifi_dfs_dir);
+
+ NXPWIFI_DFS_ADD_FILE(info);
+ NXPWIFI_DFS_ADD_FILE(debug);
+ NXPWIFI_DFS_ADD_FILE(getlog);
+ NXPWIFI_DFS_ADD_FILE(regrdwr);
+ NXPWIFI_DFS_ADD_FILE(rdeeprom);
+
+ NXPWIFI_DFS_ADD_FILE(memrw);
+ NXPWIFI_DFS_ADD_FILE(hscfg);
+ NXPWIFI_DFS_ADD_FILE(histogram);
+ NXPWIFI_DFS_ADD_FILE(debug_mask);
+ NXPWIFI_DFS_ADD_FILE(timeshare_coex);
+ NXPWIFI_DFS_ADD_FILE(reset);
+ NXPWIFI_DFS_ADD_FILE(fake_radar_detect);
+ NXPWIFI_DFS_ADD_FILE(verext);
+ NXPWIFI_DFS_ADD_FILE(netmon);
+ NXPWIFI_DFS_ADD_FILE(twt_setup);
+ NXPWIFI_DFS_ADD_FILE(twt_teardown);
+ NXPWIFI_DFS_ADD_FILE(twt_report);
+ NXPWIFI_DFS_ADD_FILE(twt_information);
+}
+
+/* Remove per-netdev debugfs directory and files. */
+void
+nxpwifi_dev_debugfs_remove(struct nxpwifi_private *priv)
+{
+ if (!priv)
+ return;
+
+ debugfs_remove_recursive(priv->dfs_dev_dir);
+}
+
+/* Create top-level debugfs directory. */
+void
+nxpwifi_debugfs_init(void)
+{
+ if (!nxpwifi_dfs_dir)
+ nxpwifi_dfs_dir = debugfs_create_dir("nxpwifi", NULL);
+}
+
+/* Remove top-level debugfs directory. */
+void
+nxpwifi_debugfs_remove(void)
+{
+ debugfs_remove(nxpwifi_dfs_dir);
+}
--
2.34.1
^ permalink raw reply related
* [PATCH v12 13/22] wifi: nxpwifi: add data path support for STA and AP modes
From: Jeff Chen @ 2026-06-05 16:13 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, briannorris, johannes, francesco, wyatt.hsu,
s.hauer, ulf.hansson, Jeff Chen
In-Reply-To: <20260605161335.2415583-1-jeff.chen_1@nxp.com>
Add the data path implementation for both station and AP roles, including
transmit and receive handling across multiple modules.
Add sta_rx.c for station receive processing, including frame conversion,
RxPD handling, alignment adjustments, WMM priority evaluation, and 11n
reorder support.
Add sta_tx.c for station transmit processing, including TxPD preparation,
NULL data frame handling, and integration with firmware status reporting.
Add uap_txrx.c for AP-mode transmit and receive logic, including bridged
packet forwarding, STA-specific queuing, and AP-side packet dispatch.
Add txrx.c for common TX/RX routines shared by all roles.
Provide support for Ethernet frame reconstruction from 802.11 headers,
TX/RX descriptor handling, WMM prioritization, bridging in AP mode, and
completion callbacks for transmit status reporting.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/sta_rx.c | 242 ++++++++++
drivers/net/wireless/nxp/nxpwifi/sta_tx.c | 190 ++++++++
drivers/net/wireless/nxp/nxpwifi/txrx.c | 352 ++++++++++++++
drivers/net/wireless/nxp/nxpwifi/uap_txrx.c | 478 ++++++++++++++++++++
4 files changed, 1262 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_rx.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_tx.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/txrx.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_txrx.c
diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_rx.c b/drivers/net/wireless/nxp/nxpwifi/sta_rx.c
new file mode 100644
index 000000000000..d951d21eb41c
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/sta_rx.c
@@ -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;
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_tx.c b/drivers/net/wireless/nxp/nxpwifi/sta_tx.c
new file mode 100644
index 000000000000..5ad578223f1c
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/sta_tx.c
@@ -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 last‑packet 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 &&
+ !is_command_pending(adapter)) {
+ adapter->delay_null_pkt = false;
+ ret = true;
+ } else {
+ ret = false;
+ adapter->delay_null_pkt = true;
+ }
+ return ret;
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/txrx.c b/drivers/net/wireless/nxp/nxpwifi/txrx.c
new file mode 100644
index 000000000000..6e8b49138e57
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/txrx.c
@@ -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);
+ }
+ }
+}
diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c
new file mode 100644
index 000000000000..f3d24bf861ca
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c
@@ -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);
+}
--
2.34.1
^ permalink raw reply related
* [PATCH v12 19/22] wifi: nxpwifi: add initial SDIO bus driver support
From: Jeff Chen @ 2026-06-05 16:13 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, briannorris, johannes, francesco, wyatt.hsu,
s.hauer, ulf.hansson, Jeff Chen
In-Reply-To: <20260605161335.2415583-1-jeff.chen_1@nxp.com>
Add SDIO transport support for NXP WiFi devices such as the IW61x series.
Integrate with the core nxpwifi layer to provide station and AP operation
over the SDIO interface.
Implement SDIO probe, suspend/resume, and interrupt handling, and add
firmware download and wakeup/sleep support. Provide multi-port
aggregation (MP-A) for improved TX/RX throughput and include firmware dump
and register dump utilities for diagnostics.
Establish the SDIO transport layer required for nxpwifi functionality.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/sdio.c | 2327 +++++++++++++++++++++++
drivers/net/wireless/nxp/nxpwifi/sdio.h | 340 ++++
2 files changed, 2667 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.h
diff --git a/drivers/net/wireless/nxp/nxpwifi/sdio.c b/drivers/net/wireless/nxp/nxpwifi/sdio.c
new file mode 100644
index 000000000000..d8536354f093
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/sdio.c
@@ -0,0 +1,2327 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * NXP Wireless LAN device driver: SDIO specific handling
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#include <linux/firmware.h>
+#include <linux/completion.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/iopoll.h>
+#include "cfg.h"
+#include "util.h"
+#include "fw.h"
+#include "main.h"
+#include "wmm.h"
+#include "11n.h"
+#include "sdio.h"
+
+#define SDIO_VERSION "1.0"
+
+/* Process deferred SDIO work items. */
+static void nxpwifi_sdio_work(struct work_struct *work);
+
+static struct nxpwifi_if_ops sdio_ops;
+
+static const struct nxpwifi_sdio_card_reg nxpwifi_reg_iw61x = {
+ .start_rd_port = 0,
+ .start_wr_port = 0,
+ .base_0_reg = 0xF8,
+ .base_1_reg = 0xF9,
+ .poll_reg = 0x5C,
+ .host_int_enable = UP_LD_HOST_INT_MASK | DN_LD_HOST_INT_MASK |
+ CMD_PORT_UPLD_INT_MASK | CMD_PORT_DNLD_INT_MASK,
+ .host_int_rsr_reg = 0x4,
+ .host_int_status_reg = 0x0C,
+ .host_int_mask_reg = 0x08,
+ .host_strap_reg = 0xF4,
+ .host_strap_mask = 0x01,
+ .host_strap_value = 0x00,
+ .status_reg_0 = 0xE8,
+ .status_reg_1 = 0xE9,
+ .sdio_int_mask = 0xff,
+ .data_port_mask = 0xffffffff,
+ .io_port_0_reg = 0xE4,
+ .io_port_1_reg = 0xE5,
+ .io_port_2_reg = 0xE6,
+ .max_mp_regs = 196,
+ .rd_bitmap_l = 0x10,
+ .rd_bitmap_u = 0x11,
+ .rd_bitmap_1l = 0x12,
+ .rd_bitmap_1u = 0x13,
+ .wr_bitmap_l = 0x14,
+ .wr_bitmap_u = 0x15,
+ .wr_bitmap_1l = 0x16,
+ .wr_bitmap_1u = 0x17,
+ .rd_len_p0_l = 0x18,
+ .rd_len_p0_u = 0x19,
+ .card_misc_cfg_reg = 0xd8,
+ .card_cfg_2_1_reg = 0xd9,
+ .cmd_rd_len_0 = 0xc0,
+ .cmd_rd_len_1 = 0xc1,
+ .cmd_rd_len_2 = 0xc2,
+ .cmd_rd_len_3 = 0xc3,
+ .cmd_cfg_0 = 0xc4,
+ .cmd_cfg_1 = 0xc5,
+ .cmd_cfg_2 = 0xc6,
+ .cmd_cfg_3 = 0xc7,
+ .fw_dump_host_ready = 0xcc,
+ .fw_dump_ctrl = 0xf9,
+ .fw_dump_start = 0xf1,
+ .fw_dump_end = 0xf8,
+ .func1_dump_reg_start = 0x10,
+ .func1_dump_reg_end = 0x17,
+ .func1_scratch_reg = 0xE8,
+ .func1_spec_reg_num = 13,
+ .func1_spec_reg_table = {0x08, 0x58, 0x5C, 0x5D, 0x60,
+ 0x61, 0x62, 0x64, 0x65, 0x66,
+ 0x68, 0x69, 0x6a},
+};
+
+static const struct nxpwifi_sdio_device nxpwifi_sdio_iw61x = {
+ .firmware = IW61X_SDIO_FW_NAME,
+ .reg = &nxpwifi_reg_iw61x,
+ .max_ports = 32,
+ .mp_agg_pkt_limit = 16,
+ .tx_buf_size = NXPWIFI_TX_DATA_BUF_SIZE_4K,
+ .mp_tx_agg_buf_size = NXPWIFI_MP_AGGR_BSIZE_MAX,
+ .mp_rx_agg_buf_size = NXPWIFI_MP_AGGR_BSIZE_MAX,
+ .can_dump_fw = true,
+ .fw_dump_enh = true,
+ .can_ext_scan = true,
+};
+
+static struct memory_type_mapping generic_mem_type_map[] = {
+ {"DUMP", NULL, 0, 0xDD},
+};
+
+static struct memory_type_mapping mem_type_mapping_tbl[] = {
+ {"ITCM", NULL, 0, 0xF0},
+ {"DTCM", NULL, 0, 0xF1},
+ {"SQRAM", NULL, 0, 0xF2},
+ {"APU", NULL, 0, 0xF3},
+ {"CIU", NULL, 0, 0xF4},
+ {"ICU", NULL, 0, 0xF5},
+ {"MAC", NULL, 0, 0xF6},
+ {"EXT7", NULL, 0, 0xF7},
+ {"EXT8", NULL, 0, 0xF8},
+ {"EXT9", NULL, 0, 0xF9},
+ {"EXT10", NULL, 0, 0xFA},
+ {"EXT11", NULL, 0, 0xFB},
+ {"EXT12", NULL, 0, 0xFC},
+ {"EXT13", NULL, 0, 0xFD},
+ {"EXTLAST", NULL, 0, 0xFE},
+};
+
+/* Bind the SDIO function and start device registration. */
+static int
+nxpwifi_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)
+{
+ int ret;
+ struct sdio_mmc_card *card = NULL;
+
+ card = devm_kzalloc(&func->dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ init_completion(&card->fw_done);
+
+ card->func = func;
+
+ if (id->driver_data) {
+ struct nxpwifi_sdio_device *data = (void *)id->driver_data;
+
+ card->firmware = data->firmware;
+ card->firmware_sdiouart = data->firmware_sdiouart;
+ card->reg = data->reg;
+ card->max_ports = data->max_ports;
+ card->mp_agg_pkt_limit = data->mp_agg_pkt_limit;
+ card->tx_buf_size = data->tx_buf_size;
+ card->mp_tx_agg_buf_size = data->mp_tx_agg_buf_size;
+ card->mp_rx_agg_buf_size = data->mp_rx_agg_buf_size;
+ card->can_dump_fw = data->can_dump_fw;
+ card->fw_dump_enh = data->fw_dump_enh;
+ card->can_ext_scan = data->can_ext_scan;
+ INIT_WORK(&card->work, nxpwifi_sdio_work);
+ }
+
+ sdio_claim_host(func);
+ ret = sdio_enable_func(func);
+ sdio_release_host(func);
+
+ if (ret) {
+ dev_err(&func->dev, "failed to enable function\n");
+ return ret;
+ }
+
+ ret = nxpwifi_add_card(card, &card->fw_done, &sdio_ops,
+ NXPWIFI_SDIO, &func->dev);
+ if (ret) {
+ dev_err(&func->dev, "add card failed\n");
+ goto err_disable;
+ }
+
+ return 0;
+
+err_disable:
+ sdio_claim_host(func);
+ sdio_disable_func(func);
+ sdio_release_host(func);
+
+ return ret;
+}
+
+/* Resume the SDIO function and cancel host sleep. */
+static int nxpwifi_sdio_resume(struct device *dev)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ struct sdio_mmc_card *card;
+ struct nxpwifi_adapter *adapter;
+
+ card = sdio_get_drvdata(func);
+
+ if (unlikely(!card || !card->adapter)) {
+ dev_dbg(dev, "resume: %s not ready\n", !card ? "card" : "adapter");
+ return -ENODEV;
+ }
+
+ adapter = card->adapter;
+
+ if (!test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags))
+ return 0;
+
+ clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags);
+
+ /* Disable Host Sleep */
+ nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA),
+ NXPWIFI_SYNC_CMD);
+
+ return 0;
+}
+
+static int
+nxpwifi_write_reg_locked(struct sdio_func *func, u32 reg, u8 data)
+{
+ int ret;
+
+ sdio_writeb(func, data, reg, &ret);
+ return ret;
+}
+
+static int
+nxpwifi_write_reg(struct nxpwifi_adapter *adapter, u32 reg, u8 data)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ int ret;
+
+ sdio_claim_host(card->func);
+ ret = nxpwifi_write_reg_locked(card->func, reg, data);
+ sdio_release_host(card->func);
+
+ return ret;
+}
+
+static int
+nxpwifi_read_reg(struct nxpwifi_adapter *adapter, u32 reg, u8 *data)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ int ret;
+ u8 val;
+
+ sdio_claim_host(card->func);
+ val = sdio_readb(card->func, reg, &ret);
+ sdio_release_host(card->func);
+
+ *data = val;
+
+ return ret;
+}
+
+static int
+nxpwifi_write_data_sync(struct nxpwifi_adapter *adapter,
+ u8 *buffer, u32 pkt_len, u32 port)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ int ret;
+ u8 blk_mode =
+ (port & NXPWIFI_SDIO_BYTE_MODE_MASK) ? BYTE_MODE : BLOCK_MODE;
+ u32 blk_size = (blk_mode == BLOCK_MODE) ? NXPWIFI_SDIO_BLOCK_SIZE : 1;
+ u32 blk_cnt =
+ (blk_mode ==
+ BLOCK_MODE) ? (pkt_len /
+ NXPWIFI_SDIO_BLOCK_SIZE) : pkt_len;
+ u32 ioport = (port & NXPWIFI_SDIO_IO_PORT_MASK);
+
+ if (test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "%s: not allowed while suspended\n", __func__);
+ return -EPERM;
+ }
+
+ sdio_claim_host(card->func);
+
+ ret = sdio_writesb(card->func, ioport, buffer, blk_cnt * blk_size);
+
+ sdio_release_host(card->func);
+
+ return ret;
+}
+
+static int nxpwifi_read_data_sync(struct nxpwifi_adapter *adapter, u8 *buffer,
+ u32 len, u32 port, u8 claim)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ int ret;
+ u8 blk_mode = (port & NXPWIFI_SDIO_BYTE_MODE_MASK) ? BYTE_MODE
+ : BLOCK_MODE;
+ u32 blk_size = (blk_mode == BLOCK_MODE) ? NXPWIFI_SDIO_BLOCK_SIZE : 1;
+ u32 blk_cnt = (blk_mode == BLOCK_MODE) ? (len / NXPWIFI_SDIO_BLOCK_SIZE)
+ : len;
+ u32 ioport = (port & NXPWIFI_SDIO_IO_PORT_MASK);
+
+ if (claim)
+ sdio_claim_host(card->func);
+
+ ret = sdio_readsb(card->func, buffer, ioport, blk_cnt * blk_size);
+
+ if (claim)
+ sdio_release_host(card->func);
+
+ return ret;
+}
+
+static int
+nxpwifi_sdio_read_fw_status(struct nxpwifi_adapter *adapter, u16 *dat)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ const struct nxpwifi_sdio_card_reg *reg = card->reg;
+ u8 fws0, fws1;
+ int ret;
+
+ ret = nxpwifi_read_reg(adapter, reg->status_reg_0, &fws0);
+ if (ret)
+ return ret;
+
+ ret = nxpwifi_read_reg(adapter, reg->status_reg_1, &fws1);
+ if (ret)
+ return ret;
+
+ *dat = (u16)((fws1 << 8) | fws0);
+ return ret;
+}
+
+static int nxpwifi_check_fw_status(struct nxpwifi_adapter *adapter,
+ u32 poll_num)
+{
+ int ret = 0;
+ u16 firmware_stat = 0;
+
+ unsigned int timeout_us = poll_num * 100000; /* 100 ms * poll_num */
+ /*
+ * Poll every 100 ms until firmware reports FIRMWARE_READY_SDIO.
+ * On timeout, read_poll_timeout() returns -ETIMEDOUT.
+ */
+ ret = read_poll_timeout(nxpwifi_sdio_read_fw_status, ret,
+ (!ret && firmware_stat == FIRMWARE_READY_SDIO),
+ 100000, timeout_us, true, /* sleep */
+ adapter, &firmware_stat);
+
+ /* FW may appear ready; wait a bit to avoid early races. */
+ if (firmware_stat == FIRMWARE_READY_SDIO)
+ msleep(100);
+
+ return ret;
+}
+
+static int nxpwifi_check_winner_status(struct nxpwifi_adapter *adapter)
+{
+ int ret;
+ u8 winner = 0;
+ struct sdio_mmc_card *card = adapter->card;
+
+ ret = nxpwifi_read_reg(adapter, card->reg->status_reg_0, &winner);
+ if (ret)
+ return ret;
+
+ if (winner)
+ adapter->winner = 0;
+ else
+ adapter->winner = 1;
+
+ return ret;
+}
+
+/* Remove the SDIO function and tear down the adapter. */
+static void
+nxpwifi_sdio_remove(struct sdio_func *func)
+{
+ struct sdio_mmc_card *card;
+ struct nxpwifi_adapter *adapter;
+ struct nxpwifi_private *priv;
+ int ret = 0;
+ u16 firmware_stat;
+
+ card = sdio_get_drvdata(func);
+ if (!card)
+ return;
+
+ wait_for_completion(&card->fw_done);
+
+ adapter = card->adapter;
+ if (!adapter || !adapter->priv_num)
+ return;
+
+ ret = nxpwifi_sdio_read_fw_status(adapter, &firmware_stat);
+ if (!ret && firmware_stat == FIRMWARE_READY_SDIO) {
+ nxpwifi_deauthenticate_all(adapter);
+
+ priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY);
+ nxpwifi_disable_auto_ds(priv);
+ nxpwifi_init_shutdown_fw(priv, NXPWIFI_FUNC_SHUTDOWN);
+ }
+
+ nxpwifi_remove_card(adapter);
+}
+
+/* Suspend the SDIO function while keeping SDIO power. */
+static int nxpwifi_sdio_suspend(struct device *dev)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ struct sdio_mmc_card *card;
+ struct nxpwifi_adapter *adapter;
+ mmc_pm_flag_t caps;
+ unsigned long flags = 0;
+ int ret = 0;
+
+ caps = sdio_get_host_pm_caps(func);
+
+ if (!(caps & MMC_PM_KEEP_POWER)) {
+ /* host lacks keep-power capability */
+ dev_warn(dev, "suspend: host does not support MMC_PM_KEEP_POWER\n");
+ return -EOPNOTSUPP;
+ }
+
+ card = sdio_get_drvdata(func);
+
+ if (!card) {
+ dev_warn(dev, "suspend: card not ready\n");
+ return -ENODEV;
+ }
+
+ /* Might still be loading firmware */
+ wait_for_completion(&card->fw_done);
+
+ adapter = card->adapter;
+ if (!adapter) {
+ dev_warn(dev, "suspend: adapter not ready\n");
+ return -ENODEV;
+ }
+
+ /* Enable the Host Sleep */
+ if (!nxpwifi_enable_hs(adapter)) {
+ nxpwifi_dbg(adapter, ERROR, "suspend: enable host sleep failed\n");
+ clear_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags);
+ return -ETIMEDOUT;
+ }
+
+ flags |= MMC_PM_KEEP_POWER;
+
+ if (adapter->wowlan_enabled && (caps & MMC_PM_WAKE_SDIO_IRQ))
+ flags |= MMC_PM_WAKE_SDIO_IRQ;
+
+ ret = sdio_set_host_pm_flags(func, flags);
+
+ /* Indicate device suspended */
+ set_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags);
+ clear_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags);
+
+ return ret;
+}
+
+static void nxpwifi_sdio_coredump(struct device *dev)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ struct sdio_mmc_card *card;
+
+ card = sdio_get_drvdata(func);
+ if (!test_and_set_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP,
+ &card->work_flags))
+ nxpwifi_queue_work(card->adapter, &card->work);
+}
+
+/* WLAN IDs */
+static const struct sdio_device_id nxpwifi_ids[] = {
+ {SDIO_DEVICE(SDIO_VENDOR_ID_NXP, SDIO_DEVICE_ID_NXP_IW61X),
+ .driver_data = (unsigned long)&nxpwifi_sdio_iw61x},
+ {},
+};
+
+MODULE_DEVICE_TABLE(sdio, nxpwifi_ids);
+
+static const struct dev_pm_ops nxpwifi_sdio_pm_ops = {
+ .suspend = nxpwifi_sdio_suspend,
+ .resume = nxpwifi_sdio_resume,
+};
+
+static struct sdio_driver nxpwifi_sdio = {
+ .name = "nxpwifi_sdio",
+ .id_table = nxpwifi_ids,
+ .probe = nxpwifi_sdio_probe,
+ .remove = nxpwifi_sdio_remove,
+ .drv = {
+ .coredump = nxpwifi_sdio_coredump,
+ .pm = &nxpwifi_sdio_pm_ops,
+ }
+};
+
+static int nxpwifi_pm_wakeup_card(struct nxpwifi_adapter *adapter)
+{
+ nxpwifi_dbg(adapter, EVENT, "event: wakeup device...\n");
+
+ return nxpwifi_write_reg(adapter, CONFIGURATION_REG, HOST_POWER_UP);
+}
+
+static int nxpwifi_pm_wakeup_card_complete(struct nxpwifi_adapter *adapter)
+{
+ nxpwifi_dbg(adapter, EVENT, "cmd: wakeup device completed\n");
+
+ return nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0);
+}
+
+/* SDIO wrapper for firmware download (claims host). */
+static int nxpwifi_sdio_dnld_fw(struct nxpwifi_adapter *adapter,
+ struct nxpwifi_fw_image *fw)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ int ret;
+
+ sdio_claim_host(card->func);
+ ret = nxpwifi_dnld_fw(adapter, fw);
+ sdio_release_host(card->func);
+
+ return ret;
+}
+
+static int nxpwifi_init_sdio_new_mode(struct nxpwifi_adapter *adapter)
+{
+ u8 reg;
+ struct sdio_mmc_card *card = adapter->card;
+ int ret;
+
+ adapter->ioport = MEM_PORT;
+
+ /* enable sdio new mode */
+ ret = nxpwifi_read_reg(adapter, card->reg->card_cfg_2_1_reg, ®);
+ if (ret)
+ return ret;
+ ret = nxpwifi_write_reg(adapter, card->reg->card_cfg_2_1_reg,
+ reg | CMD53_NEW_MODE);
+ if (ret)
+ return ret;
+
+ /* Configure cmd port and enable reading rx length from the register */
+ ret = nxpwifi_read_reg(adapter, card->reg->cmd_cfg_0, ®);
+ if (ret)
+ return ret;
+ ret = nxpwifi_write_reg(adapter, card->reg->cmd_cfg_0,
+ reg | CMD_PORT_RD_LEN_EN);
+ if (ret)
+ return ret;
+
+ /*
+ * Enable Dnld/Upld ready auto reset for cmd port after cmd53 is
+ * completed
+ */
+ ret = nxpwifi_read_reg(adapter, card->reg->cmd_cfg_1, ®);
+ if (ret)
+ return ret;
+ ret = nxpwifi_write_reg(adapter, card->reg->cmd_cfg_1,
+ reg | CMD_PORT_AUTO_EN);
+
+ return ret;
+}
+
+/* Initialize SDIO IO ports and host-int behavior. */
+static int nxpwifi_init_sdio_ioport(struct nxpwifi_adapter *adapter)
+{
+ u8 reg;
+ struct sdio_mmc_card *card = adapter->card;
+ int ret;
+
+ ret = nxpwifi_init_sdio_new_mode(adapter);
+ if (ret)
+ return ret;
+
+ /* Set Host interrupt reset to read to clear */
+ ret = nxpwifi_read_reg(adapter, card->reg->host_int_rsr_reg, ®);
+ if (ret)
+ return ret;
+ ret = nxpwifi_write_reg(adapter, card->reg->host_int_rsr_reg,
+ reg | card->reg->sdio_int_mask);
+ if (ret)
+ return ret;
+
+ /* Dnld/Upld ready set to auto reset */
+ ret = nxpwifi_read_reg(adapter, card->reg->card_misc_cfg_reg, ®);
+ if (ret)
+ return ret;
+ ret = nxpwifi_write_reg(adapter, card->reg->card_misc_cfg_reg,
+ reg | AUTO_RE_ENABLE_INT);
+
+ return ret;
+}
+
+static int nxpwifi_write_data_to_card(struct nxpwifi_adapter *adapter,
+ u8 *payload, u32 pkt_len, u32 port)
+{
+ u32 i = 0;
+ int ret;
+
+ do {
+ ret = nxpwifi_write_data_sync(adapter, payload, pkt_len, port);
+ if (ret) {
+ i++;
+ nxpwifi_dbg(adapter, ERROR, "host_to_card, write iomem\t"
+ "(%d) failed: %d\n", i, ret);
+ if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0x04))
+ nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n");
+
+ if (i > MAX_WRITE_IOMEM_RETRY)
+ return ret;
+ }
+ } while (ret);
+
+ return ret;
+}
+
+static int nxpwifi_get_rd_port(struct nxpwifi_adapter *adapter, u8 *port)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ const struct nxpwifi_sdio_card_reg *reg = card->reg;
+ u32 rd_bitmap = card->mp_rd_bitmap;
+
+ if (!(rd_bitmap & reg->data_port_mask))
+ return -EINVAL;
+
+ if (!(card->mp_rd_bitmap & (1 << card->curr_rd_port)))
+ return -EINVAL;
+
+ /* We are now handling the SDIO data ports */
+ card->mp_rd_bitmap &= (u32)(~(1 << card->curr_rd_port));
+ *port = card->curr_rd_port;
+
+ if (++card->curr_rd_port == card->max_ports)
+ card->curr_rd_port = reg->start_rd_port;
+
+ return 0;
+}
+
+static int nxpwifi_get_wr_port_data(struct nxpwifi_adapter *adapter, u32 *port)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ const struct nxpwifi_sdio_card_reg *reg = card->reg;
+ u32 wr_bitmap = card->mp_wr_bitmap;
+
+ if (!(wr_bitmap & card->mp_data_port_mask)) {
+ adapter->data_sent = true;
+ return -EBUSY;
+ }
+
+ if (card->mp_wr_bitmap & (1 << card->curr_wr_port)) {
+ card->mp_wr_bitmap &= (u32)(~(1 << card->curr_wr_port));
+ *port = card->curr_wr_port;
+ if (++card->curr_wr_port == card->mp_end_port)
+ card->curr_wr_port = reg->start_wr_port;
+ } else {
+ adapter->data_sent = true;
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int
+nxpwifi_sdio_poll_card_status(struct nxpwifi_adapter *adapter, u8 bits)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ u32 tries;
+ u8 cs;
+ int ret;
+
+ for (tries = 0; tries < MAX_POLL_TRIES; tries++) {
+ ret = nxpwifi_read_reg(adapter, card->reg->poll_reg, &cs);
+ if (ret)
+ break;
+ else if ((cs & bits) == bits)
+ return 0;
+
+ usleep_range(10, 20);
+ }
+
+ nxpwifi_dbg(adapter, ERROR, "poll card status failed, tries = %d\n", tries);
+
+ return ret;
+}
+
+/* Disable SDIO host interrupt and release IRQ. */
+static void nxpwifi_sdio_disable_host_int(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ struct sdio_func *func = card->func;
+
+ sdio_claim_host(func);
+ nxpwifi_write_reg_locked(func, card->reg->host_int_mask_reg, 0);
+ sdio_release_irq(func);
+ sdio_release_host(func);
+}
+
+static void nxpwifi_interrupt_status(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ u8 sdio_ireg;
+ unsigned long flags;
+
+ if (nxpwifi_read_data_sync(adapter, card->mp_regs,
+ card->reg->max_mp_regs,
+ REG_PORT | NXPWIFI_SDIO_BYTE_MODE_MASK, 0)) {
+ nxpwifi_dbg(adapter, ERROR, "read mp_regs failed\n");
+ return;
+ }
+
+ sdio_ireg = card->mp_regs[card->reg->host_int_status_reg];
+ if (sdio_ireg) {
+ nxpwifi_dbg(adapter, INTR, "intr: sdio_ireg = %#x\n", sdio_ireg);
+ spin_lock_irqsave(&adapter->int_lock, flags);
+ adapter->int_status |= sdio_ireg;
+ spin_unlock_irqrestore(&adapter->int_lock, flags);
+ }
+}
+
+/* SDIO IRQ handler: snapshot status and schedule main work. */
+static void
+nxpwifi_sdio_interrupt(struct sdio_func *func)
+{
+ struct nxpwifi_adapter *adapter;
+ struct sdio_mmc_card *card;
+
+ card = sdio_get_drvdata(func);
+
+ if (!card || !card->adapter) {
+ /* device-scoped error logging (rate-limited to avoid flood) */
+ dev_err_ratelimited(&func->dev, "interrupt: missing card/adapter\n");
+ return;
+ }
+
+ adapter = card->adapter;
+
+ if (!adapter->pps_uapsd_mode && adapter->ps_state == PS_STATE_SLEEP)
+ adapter->ps_state = PS_STATE_AWAKE;
+
+ nxpwifi_interrupt_status(adapter);
+ nxpwifi_queue_work(adapter, &adapter->main_work);
+}
+
+/* Enable SDIO host interrupt and claim IRQ. */
+static int nxpwifi_sdio_enable_host_int(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ struct sdio_func *func = card->func;
+ int ret;
+
+ sdio_claim_host(func);
+
+ /* Request the SDIO IRQ */
+ ret = sdio_claim_irq(func, nxpwifi_sdio_interrupt);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "claim irq failed: ret=%d\n", ret);
+ goto done;
+ }
+
+ /* Simply write the mask to the register */
+ ret = nxpwifi_write_reg_locked(func, card->reg->host_int_mask_reg,
+ card->reg->host_int_enable);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "enable host interrupt failed\n");
+ sdio_release_irq(func);
+ }
+
+done:
+ sdio_release_host(func);
+ return ret;
+}
+
+static int nxpwifi_sdio_card_to_host(struct nxpwifi_adapter *adapter,
+ u32 *type, u8 *buffer,
+ u32 npayload, u32 ioport)
+{
+ int ret;
+ u32 nb;
+
+ if (!buffer)
+ return -EINVAL;
+
+ ret = nxpwifi_read_data_sync(adapter, buffer, npayload, ioport, 1);
+
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR,
+ "read iomem failed (ioport=%#x, len=%u): %d",
+ ioport, npayload, ret);
+
+ return ret;
+ }
+
+ nb = get_unaligned_le16((buffer));
+ if (nb > npayload) {
+ nxpwifi_dbg(adapter, ERROR,
+ "invalid packet len: nb=%u > npayload=%u (ioport=%#x)",
+ nb, npayload, ioport);
+ return -EINVAL;
+ }
+
+ *type = get_unaligned_le16((buffer + 2));
+
+ return ret;
+}
+
+/* Download firmware using the helper protocol. */
+static int nxpwifi_prog_fw_w_helper(struct nxpwifi_adapter *adapter,
+ struct nxpwifi_fw_image *fw)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ const struct nxpwifi_sdio_card_reg *reg = card->reg;
+ int ret;
+ u8 *firmware = fw->fw_buf;
+ u32 firmware_len = fw->fw_len;
+ u32 offset = 0;
+ u8 base0, base1;
+ u8 *fwbuf;
+ u16 len = 0;
+ u32 txlen, tx_blocks = 0, tries;
+ u32 i = 0;
+
+ if (!firmware_len) {
+ nxpwifi_dbg(adapter, ERROR,
+ "firmware image not found! Terminating download\n");
+ return -EINVAL;
+ }
+
+ /* Assume that the allocated buffer is 8-byte aligned */
+ fwbuf = kzalloc(NXPWIFI_UPLD_SIZE, GFP_KERNEL);
+ if (!fwbuf)
+ return -ENOMEM;
+
+ sdio_claim_host(card->func);
+
+ /* Perform firmware data transfer */
+ do {
+ /*
+ * The host polls for the DN_LD_CARD_RDY and CARD_IO_READY
+ * bits
+ */
+ ret = nxpwifi_sdio_poll_card_status(adapter, CARD_IO_READY |
+ DN_LD_CARD_RDY);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR,
+ "FW download with helper:\t"
+ "poll status timeout @ %d\n", offset);
+ goto done;
+ }
+
+ /* More data? */
+ if (offset >= firmware_len)
+ break;
+
+ for (tries = 0; tries < MAX_POLL_TRIES; tries++) {
+ ret = nxpwifi_read_reg(adapter, reg->base_0_reg,
+ &base0);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR,
+ "dev BASE0 register read failed:\t"
+ "base0=%#04X(%d). Terminating dnld\n",
+ base0, base0);
+ goto done;
+ }
+ ret = nxpwifi_read_reg(adapter, reg->base_1_reg,
+ &base1);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR,
+ "dev BASE1 register read failed:\t"
+ "base1=%#04X(%d). Terminating dnld\n",
+ base1, base1);
+ goto done;
+ }
+ len = (u16)(((base1 & 0xff) << 8) | (base0 & 0xff));
+
+ if (len)
+ break;
+
+ usleep_range(10, 20);
+ }
+
+ if (!len) {
+ break;
+ } else if (len > NXPWIFI_UPLD_SIZE) {
+ nxpwifi_dbg(adapter, ERROR,
+ "FW dnld failed @ %d, invalid length %d\n",
+ offset, len);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ txlen = len;
+
+ if (len & BIT(0)) {
+ i++;
+ if (i > MAX_WRITE_IOMEM_RETRY) {
+ nxpwifi_dbg(adapter, ERROR,
+ "FW dnld failed @ %d, over max retry\n",
+ offset);
+ ret = -EIO;
+ goto done;
+ }
+ nxpwifi_dbg(adapter, ERROR,
+ "CRC indicated by the helper:\t"
+ "len = 0x%04X, txlen = %d\n", len, txlen);
+ len &= ~BIT(0);
+ /* Setting this to 0 to resend from same offset */
+ txlen = 0;
+ } else {
+ i = 0;
+
+ /*
+ * Set blocksize to transfer - checking for last
+ * block
+ */
+ if (firmware_len - offset < txlen)
+ txlen = firmware_len - offset;
+
+ tx_blocks = (txlen + NXPWIFI_SDIO_BLOCK_SIZE - 1)
+ / NXPWIFI_SDIO_BLOCK_SIZE;
+
+ /* Copy payload to buffer */
+ memcpy(fwbuf, &firmware[offset], txlen);
+ }
+
+ ret = nxpwifi_write_data_sync(adapter, fwbuf, tx_blocks *
+ NXPWIFI_SDIO_BLOCK_SIZE,
+ adapter->ioport);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR,
+ "FW download, write iomem (%d) failed @ %d\n",
+ i, offset);
+ if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0x04))
+ nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n");
+
+ goto done;
+ }
+
+ offset += txlen;
+ } while (true);
+
+ nxpwifi_dbg(adapter, MSG, "FW download complete (%u bytes)\n", offset);
+
+ ret = 0;
+done:
+ sdio_release_host(card->func);
+ kfree(fwbuf);
+ return ret;
+}
+
+/* Deaggregate an SDIO RX aggregation packet. */
+static void nxpwifi_deaggr_sdio_pkt(struct nxpwifi_adapter *adapter,
+ struct sk_buff *skb)
+{
+ u32 total_pkt_len, pkt_len;
+ struct sk_buff *skb_deaggr;
+ u16 blk_size;
+ u8 blk_num;
+ u8 *data;
+
+ data = skb->data;
+ total_pkt_len = skb->len;
+
+ while (total_pkt_len >= (SDIO_HEADER_OFFSET + adapter->intf_hdr_len)) {
+ if (total_pkt_len < adapter->sdio_rx_block_size)
+ break;
+ blk_num = *(data + BLOCK_NUMBER_OFFSET);
+ blk_size = adapter->sdio_rx_block_size * blk_num;
+ if (blk_size > total_pkt_len) {
+ nxpwifi_dbg(adapter, ERROR,
+ "%s: error in blk_size,\t"
+ "blk_num=%d, blk_size=%d, total_pkt_len=%d\n",
+ __func__, blk_num, blk_size, total_pkt_len);
+ break;
+ }
+ pkt_len = get_unaligned_le16((data +
+ SDIO_HEADER_OFFSET));
+ if ((pkt_len + SDIO_HEADER_OFFSET) > blk_size) {
+ nxpwifi_dbg(adapter, ERROR,
+ "%s: error in pkt_len,\t"
+ "pkt_len=%d, blk_size=%d\n",
+ __func__, pkt_len, blk_size);
+ break;
+ }
+
+ skb_deaggr = nxpwifi_alloc_dma_align_buf(pkt_len, GFP_KERNEL);
+ if (!skb_deaggr)
+ break;
+ skb_put(skb_deaggr, pkt_len);
+ memcpy(skb_deaggr->data, data + SDIO_HEADER_OFFSET, pkt_len);
+ skb_pull(skb_deaggr, adapter->intf_hdr_len);
+
+ nxpwifi_handle_rx_packet(adapter, skb_deaggr);
+ data += blk_size;
+ total_pkt_len -= blk_size;
+ }
+}
+
+static void nxpwifi_decode_rx_packet(struct nxpwifi_adapter *adapter,
+ struct sk_buff *skb, u32 upld_typ)
+{
+ u8 *cmd_buf;
+ u16 pkt_len;
+ struct nxpwifi_rxinfo *rx_info;
+
+ pkt_len = get_unaligned_le16(skb->data);
+
+ if (upld_typ != NXPWIFI_TYPE_AGGR_DATA) {
+ skb_trim(skb, pkt_len);
+ skb_pull(skb, adapter->intf_hdr_len);
+ }
+
+ switch (upld_typ) {
+ case NXPWIFI_TYPE_AGGR_DATA:
+ nxpwifi_dbg(adapter, DATA,
+ "Rx Aggr Data packet\n");
+ rx_info = NXPWIFI_SKB_RXCB(skb);
+ rx_info->buf_type = NXPWIFI_TYPE_AGGR_DATA;
+ if (adapter->rx_work_enabled) {
+ skb_queue_tail(&adapter->rx_data_q, skb);
+ atomic_inc(&adapter->rx_pending);
+ adapter->data_received = true;
+ } else {
+ /* Deaggregate an SDIO RX aggregation packet. */
+ nxpwifi_deaggr_sdio_pkt(adapter, skb);
+ dev_kfree_skb_any(skb);
+ }
+ break;
+
+ case NXPWIFI_TYPE_DATA:
+ nxpwifi_dbg(adapter, DATA, "Rx Data packet\n");
+ if (adapter->rx_work_enabled) {
+ skb_queue_tail(&adapter->rx_data_q, skb);
+ adapter->data_received = true;
+ atomic_inc(&adapter->rx_pending);
+ } else {
+ nxpwifi_handle_rx_packet(adapter, skb);
+ }
+ break;
+
+ case NXPWIFI_TYPE_CMD:
+ nxpwifi_dbg(adapter, CMD, "Rx Cmd Response\n");
+ /* take care of curr_cmd = NULL case */
+ if (!adapter->curr_cmd) {
+ cmd_buf = adapter->upld_buf;
+
+ if (adapter->ps_state == PS_STATE_SLEEP_CFM)
+ nxpwifi_process_sleep_confirm_resp(adapter,
+ skb->data,
+ skb->len);
+
+ memcpy(cmd_buf, skb->data,
+ min_t(u32, NXPWIFI_SIZE_OF_CMD_BUFFER,
+ skb->len));
+
+ dev_kfree_skb_any(skb);
+ } else {
+ adapter->cmd_resp_received = true;
+ adapter->curr_cmd->resp_skb = skb;
+ }
+ break;
+
+ case NXPWIFI_TYPE_EVENT:
+ nxpwifi_dbg(adapter, EVENT, "Rx Event\n");
+ adapter->event_cause = get_unaligned_le32(skb->data);
+
+ if (skb->len > NXPWIFI_EVENT_HEADER_LEN) {
+ u32 body_len = min_t(u32, skb->len - NXPWIFI_EVENT_HEADER_LEN,
+ MAX_EVENT_SIZE);
+ memcpy(adapter->event_body, skb->data + NXPWIFI_EVENT_HEADER_LEN,
+ body_len);
+ }
+
+ /* event cause has been saved to adapter->event_cause */
+ adapter->event_received = true;
+ adapter->event_skb = skb;
+
+ break;
+
+ default:
+ nxpwifi_dbg(adapter, ERROR, "unknown upload type %#x\n", upld_typ);
+ dev_kfree_skb_any(skb);
+ break;
+ }
+}
+
+/* Receive path with SDIO multi-port aggregation. */
+static int nxpwifi_sdio_card_to_host_mp_aggr(struct nxpwifi_adapter *adapter,
+ u16 rx_len, u8 port)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ s32 f_do_rx_aggr = 0;
+ s32 f_do_rx_cur = 0;
+ s32 f_aggr_cur = 0;
+ s32 f_post_aggr_cur = 0;
+ struct sk_buff *skb_deaggr;
+ struct sk_buff *skb = NULL;
+ u32 pkt_len, pkt_type, mport, pind;
+ u8 *curr_ptr;
+ int ret = 0;
+
+ if (!card->mpa_rx.enabled) {
+ nxpwifi_dbg(adapter, WARN, "rx aggregation disabled\n");
+ f_do_rx_cur = 1;
+ goto rx_curr_single;
+ }
+
+ if (card->mp_rd_bitmap & card->reg->data_port_mask) {
+ /* Some more data RX pending */
+
+ if (MP_RX_AGGR_IN_PROGRESS(card)) {
+ if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len)) {
+ f_aggr_cur = 1;
+ } else {
+ /* No room in Aggr buf, do rx aggr now */
+ f_do_rx_aggr = 1;
+ f_post_aggr_cur = 1;
+ }
+ } else {
+ /* Rx aggr not in progress */
+ f_aggr_cur = 1;
+ }
+
+ } else {
+ /* No more data RX pending */
+
+ if (MP_RX_AGGR_IN_PROGRESS(card)) {
+ f_do_rx_aggr = 1;
+ if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len))
+ f_aggr_cur = 1;
+ else
+ /* No room in Aggr buf, do rx aggr now */
+ f_do_rx_cur = 1;
+ } else {
+ f_do_rx_cur = 1;
+ }
+ }
+
+ if (f_aggr_cur) {
+ /* Curr pkt can be aggregated */
+ mp_rx_aggr_setup(card, rx_len, port);
+
+ if (MP_RX_AGGR_PKT_LIMIT_REACHED(card) ||
+ mp_rx_aggr_port_limit_reached(card)) {
+ /* No more pkts allowed in Aggr buf, rx it */
+ f_do_rx_aggr = 1;
+ }
+ }
+
+ if (f_do_rx_aggr) {
+ u32 port_count;
+ int i;
+
+ /* do aggr RX now */
+ for (i = 0, port_count = 0; i < card->max_ports; i++)
+ if (card->mpa_rx.ports & BIT(i))
+ port_count++;
+
+ /*
+ * Reading data from "start_port + 0" to "start_port +
+ * port_count -1", so decrease the count by 1
+ */
+ port_count--;
+ mport = (adapter->ioport | SDIO_MPA_ADDR_BASE |
+ (port_count << 8)) + card->mpa_rx.start_port;
+
+ if (card->mpa_rx.pkt_cnt == 1)
+ mport = adapter->ioport + card->mpa_rx.start_port;
+
+ ret = nxpwifi_read_data_sync(adapter, card->mpa_rx.buf,
+ card->mpa_rx.buf_len, mport, 1);
+ if (ret)
+ goto error;
+
+ curr_ptr = card->mpa_rx.buf;
+
+ for (pind = 0; pind < card->mpa_rx.pkt_cnt; pind++) {
+ u32 *len_arr = card->mpa_rx.len_arr;
+
+ /* get curr PKT len & type */
+ pkt_len = get_unaligned_le16(&curr_ptr[0]);
+ pkt_type = get_unaligned_le16(&curr_ptr[2]);
+
+ /* copy pkt to deaggr buf */
+ skb_deaggr = nxpwifi_alloc_dma_align_buf(len_arr[pind],
+ GFP_KERNEL);
+ if (!skb_deaggr) {
+ nxpwifi_dbg(adapter, ERROR, "skb allocation failure\t"
+ "drop pkt len=%d type=%d\n",
+ pkt_len, pkt_type);
+ curr_ptr += len_arr[pind];
+ continue;
+ }
+
+ skb_put(skb_deaggr, len_arr[pind]);
+
+ if ((pkt_type == NXPWIFI_TYPE_DATA ||
+ (pkt_type == NXPWIFI_TYPE_AGGR_DATA &&
+ adapter->sdio_rx_aggr_enable)) &&
+ pkt_len <= len_arr[pind]) {
+ memcpy(skb_deaggr->data, curr_ptr, pkt_len);
+
+ skb_trim(skb_deaggr, pkt_len);
+
+ nxpwifi_decode_rx_packet(adapter, skb_deaggr,
+ pkt_type);
+ } else {
+ nxpwifi_dbg(adapter, ERROR,
+ "drop wrong aggr pkt:\t"
+ "sdio_single_port_rx_aggr=%d\t"
+ "type=%d len=%d max_len=%d\n",
+ adapter->sdio_rx_aggr_enable,
+ pkt_type, pkt_len, len_arr[pind]);
+ dev_kfree_skb_any(skb_deaggr);
+ }
+ curr_ptr += len_arr[pind];
+ }
+ MP_RX_AGGR_BUF_RESET(card);
+ }
+
+rx_curr_single:
+ if (f_do_rx_cur) {
+ skb = nxpwifi_alloc_dma_align_buf(rx_len, GFP_KERNEL);
+ if (!skb) {
+ nxpwifi_dbg(adapter, ERROR,
+ "single skb allocated fail,\t"
+ "drop pkt port=%d len=%d\n", port, rx_len);
+ ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type,
+ card->mpa_rx.buf,
+ rx_len,
+ adapter->ioport + port);
+ if (ret)
+ goto error;
+ return 0;
+ }
+
+ skb_put(skb, rx_len);
+
+ ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type,
+ skb->data, skb->len,
+ adapter->ioport + port);
+ if (ret)
+ goto error;
+ if (!adapter->sdio_rx_aggr_enable &&
+ pkt_type == NXPWIFI_TYPE_AGGR_DATA) {
+ nxpwifi_dbg(adapter, ERROR, "drop wrong pkt type %d\t"
+ "current SDIO RX Aggr not enabled\n",
+ pkt_type);
+ dev_kfree_skb_any(skb);
+ return 0;
+ }
+
+ nxpwifi_decode_rx_packet(adapter, skb, pkt_type);
+ }
+ if (f_post_aggr_cur) /* Curr pkt can be aggregated */
+ mp_rx_aggr_setup(card, rx_len, port);
+
+ return 0;
+error:
+ if (MP_RX_AGGR_IN_PROGRESS(card))
+ MP_RX_AGGR_BUF_RESET(card);
+
+ if (f_do_rx_cur && skb) /* Single transfer pending. Free curr buff also */
+ dev_kfree_skb_any(skb);
+
+ return ret;
+}
+
+static int nxpwifi_process_int_status(struct nxpwifi_adapter *adapter, u8 sdio_ireg)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ const struct nxpwifi_sdio_card_reg *reg = card->reg;
+ int ret = 0;
+ struct sk_buff *skb;
+ u8 port;
+ u32 len_reg_l, len_reg_u;
+ u32 rx_blocks;
+ u16 rx_len;
+ u32 bitmap;
+ u8 cr;
+
+ if (!sdio_ireg)
+ return ret;
+
+ if (sdio_ireg & DN_LD_CMD_PORT_HOST_INT_STATUS && adapter->cmd_sent)
+ adapter->cmd_sent = false;
+
+ if (sdio_ireg & UP_LD_CMD_PORT_HOST_INT_STATUS) {
+ u32 pkt_type;
+
+ /* read the len of control packet */
+ rx_len = card->mp_regs[reg->cmd_rd_len_1] << 8;
+ rx_len |= (u16)card->mp_regs[reg->cmd_rd_len_0];
+ rx_blocks = DIV_ROUND_UP(rx_len, NXPWIFI_SDIO_BLOCK_SIZE);
+ if (rx_len <= adapter->intf_hdr_len ||
+ (rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE) >
+ NXPWIFI_RX_DATA_BUF_SIZE)
+ return -EINVAL;
+ rx_len = (u16)(rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE);
+
+ skb = nxpwifi_alloc_dma_align_buf(rx_len, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_put(skb, rx_len);
+
+ ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, skb->data,
+ skb->len, adapter->ioport |
+ CMD_PORT_SLCT);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "failed to card_to_host");
+ dev_kfree_skb_any(skb);
+ goto term_cmd;
+ }
+
+ if (pkt_type != NXPWIFI_TYPE_CMD &&
+ pkt_type != NXPWIFI_TYPE_EVENT)
+ nxpwifi_dbg(adapter, ERROR, "Received wrong packet on cmd port");
+
+ nxpwifi_decode_rx_packet(adapter, skb, pkt_type);
+ }
+
+ if (sdio_ireg & DN_LD_HOST_INT_STATUS) {
+ bitmap = (u32)card->mp_regs[reg->wr_bitmap_l];
+ bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_u]) << 8;
+ bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_1l]) << 16;
+ bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_1u]) << 24;
+ card->mp_wr_bitmap = bitmap;
+
+ nxpwifi_dbg(adapter, INTR, "intr: wr_bitmap=0x%x\n", card->mp_wr_bitmap);
+ if (adapter->data_sent &&
+ (card->mp_wr_bitmap & card->mp_data_port_mask)) {
+ nxpwifi_dbg(adapter, INTR, "Tx DONE\n");
+ adapter->data_sent = false;
+ }
+ }
+
+ nxpwifi_dbg(adapter, INTR, "cmd_sent=%d data_sent=%d\n",
+ adapter->cmd_sent, adapter->data_sent);
+ if (sdio_ireg & UP_LD_HOST_INT_STATUS) {
+ bitmap = (u32)card->mp_regs[reg->rd_bitmap_l];
+ bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_u]) << 8;
+ bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_1l]) << 16;
+ bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_1u]) << 24;
+ card->mp_rd_bitmap = bitmap;
+ nxpwifi_dbg(adapter, INTR, "intr: rd_bitmap=0x%x\n", card->mp_rd_bitmap);
+
+ while (true) {
+ ret = nxpwifi_get_rd_port(adapter, &port);
+
+ if (ret)
+ break;
+
+ len_reg_l = reg->rd_len_p0_l + (port << 1);
+ len_reg_u = reg->rd_len_p0_u + (port << 1);
+ rx_len = ((u16)card->mp_regs[len_reg_u]) << 8;
+ rx_len |= (u16)card->mp_regs[len_reg_l];
+ rx_blocks =
+ (rx_len + NXPWIFI_SDIO_BLOCK_SIZE -
+ 1) / NXPWIFI_SDIO_BLOCK_SIZE;
+ if (rx_len <= adapter->intf_hdr_len ||
+ (card->mpa_rx.enabled &&
+ ((rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE) >
+ card->mpa_rx.buf_size))) {
+ nxpwifi_dbg(adapter, ERROR, "invalid rx_len=%d\n", rx_len);
+ return -EINVAL;
+ }
+
+ rx_len = (u16)(rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE);
+
+ ret = nxpwifi_sdio_card_to_host_mp_aggr(adapter, rx_len,
+ port);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR,
+ "card_to_host_mpa failed: int status=%#x\n",
+ sdio_ireg);
+ goto term_cmd;
+ }
+ }
+ }
+
+ return 0;
+
+term_cmd:
+ /* terminate cmd */
+ if (nxpwifi_read_reg(adapter, CONFIGURATION_REG, &cr))
+ nxpwifi_dbg(adapter, ERROR, "read CFG reg failed\n");
+ else
+ nxpwifi_dbg(adapter, INFO, "info: CFG reg val = %d\n", cr);
+
+ if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, (cr | 0x04)))
+ nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n");
+ else
+ nxpwifi_dbg(adapter, INFO, "info: write success\n");
+
+ if (nxpwifi_read_reg(adapter, CONFIGURATION_REG, &cr))
+ nxpwifi_dbg(adapter, ERROR, "read CFG reg failed\n");
+ else
+ nxpwifi_dbg(adapter, INFO, "info: CFG reg val =%x\n", cr);
+
+ return ret;
+}
+
+/* Transmit using SDIO multi-port aggregation. */
+static int nxpwifi_host_to_card_mp_aggr(struct nxpwifi_adapter *adapter,
+ u8 *payload, u32 pkt_len, u32 port,
+ u32 next_pkt_len)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ int ret = 0;
+ s32 f_send_aggr_buf = 0;
+ s32 f_send_cur_buf = 0;
+ s32 f_precopy_cur_buf = 0;
+ s32 f_postcopy_cur_buf = 0;
+ u32 mport;
+ int index;
+
+ if (!card->mpa_tx.enabled || port == CMD_PORT_SLCT) {
+ nxpwifi_dbg(adapter, WARN, "tx aggregation disabled\n");
+ f_send_cur_buf = 1;
+ goto tx_curr_single;
+ }
+
+ if (next_pkt_len) {
+ /* More pkt in TX queue */
+
+ if (MP_TX_AGGR_IN_PROGRESS(card)) {
+ if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) {
+ f_precopy_cur_buf = 1;
+
+ if (!(card->mp_wr_bitmap &
+ (1 << card->curr_wr_port)) ||
+ !MP_TX_AGGR_BUF_HAS_ROOM
+ (card, pkt_len + next_pkt_len))
+ f_send_aggr_buf = 1;
+ } else {
+ /* No room in Aggr buf, send it */
+ f_send_aggr_buf = 1;
+
+ if (!(card->mp_wr_bitmap &
+ (1 << card->curr_wr_port)))
+ f_send_cur_buf = 1;
+ else
+ f_postcopy_cur_buf = 1;
+ }
+ } else {
+ if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len) &&
+ (card->mp_wr_bitmap & (1 << card->curr_wr_port)))
+ f_precopy_cur_buf = 1;
+ else
+ f_send_cur_buf = 1;
+ }
+ } else {
+ /* Last pkt in TX queue */
+
+ if (MP_TX_AGGR_IN_PROGRESS(card)) {
+ /* some packs in Aggr buf already */
+ f_send_aggr_buf = 1;
+
+ if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len))
+ f_precopy_cur_buf = 1;
+ else
+ /* No room in Aggr buf, send it */
+ f_send_cur_buf = 1;
+ } else {
+ f_send_cur_buf = 1;
+ }
+ }
+
+ if (f_precopy_cur_buf) {
+ MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port);
+
+ if (MP_TX_AGGR_PKT_LIMIT_REACHED(card) ||
+ mp_tx_aggr_port_limit_reached(card))
+ /* No more pkts allowed in Aggr buf, send it */
+ f_send_aggr_buf = 1;
+ }
+
+ if (f_send_aggr_buf) {
+ u32 port_count;
+ int i;
+
+ for (i = 0, port_count = 0; i < card->max_ports; i++)
+ if (card->mpa_tx.ports & BIT(i))
+ port_count++;
+
+ /*
+ * Writing data from "start_port + 0" to "start_port +
+ * port_count -1", so decrease the count by 1
+ */
+ port_count--;
+ mport = (adapter->ioport | SDIO_MPA_ADDR_BASE |
+ (port_count << 8)) + card->mpa_tx.start_port;
+
+ if (card->mpa_tx.pkt_cnt == 1)
+ mport = adapter->ioport + card->mpa_tx.start_port;
+
+ ret = nxpwifi_write_data_to_card(adapter, card->mpa_tx.buf,
+ card->mpa_tx.buf_len, mport);
+
+ /* Save the last multi port tx aggregation info to debug log */
+ index = adapter->dbg.last_sdio_mp_index;
+ index = (index + 1) % NXPWIFI_DBG_SDIO_MP_NUM;
+ adapter->dbg.last_sdio_mp_index = index;
+ adapter->dbg.last_mp_wr_ports[index] = mport;
+ adapter->dbg.last_mp_wr_bitmap[index] = card->mp_wr_bitmap;
+ adapter->dbg.last_mp_wr_len[index] = card->mpa_tx.buf_len;
+ adapter->dbg.last_mp_curr_wr_port[index] = card->curr_wr_port;
+
+ MP_TX_AGGR_BUF_RESET(card);
+ }
+
+tx_curr_single:
+ if (f_send_cur_buf)
+ ret = nxpwifi_write_data_to_card(adapter, payload, pkt_len,
+ adapter->ioport + port);
+
+ if (f_postcopy_cur_buf)
+ MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port);
+
+ return ret;
+}
+
+static int nxpwifi_sdio_host_to_card(struct nxpwifi_adapter *adapter,
+ u8 type, struct sk_buff *skb,
+ struct nxpwifi_tx_param *tx_param)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ int ret;
+ u32 buf_block_len;
+ u32 blk_size;
+ u32 port;
+ u8 *payload = (u8 *)skb->data;
+ u32 pkt_len = skb->len;
+
+ /* Allocate buffer and copy payload */
+ blk_size = NXPWIFI_SDIO_BLOCK_SIZE;
+ buf_block_len = (pkt_len + blk_size - 1) / blk_size;
+ put_unaligned_le16((u16)pkt_len, payload + 0);
+ put_unaligned_le16((u16)type, payload + 2);
+
+ /*
+ * This is SDIO specific header
+ * u16 length,
+ * u16 type (NXPWIFI_TYPE_DATA = 0, NXPWIFI_TYPE_CMD = 1,
+ * NXPWIFI_TYPE_EVENT = 3)
+ */
+ if (type == NXPWIFI_TYPE_DATA) {
+ ret = nxpwifi_get_wr_port_data(adapter, &port);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "no wr_port available\n");
+ return ret;
+ }
+ } else {
+ adapter->cmd_sent = true;
+
+ if (pkt_len <= adapter->intf_hdr_len ||
+ pkt_len > NXPWIFI_UPLD_SIZE) {
+ nxpwifi_dbg(adapter, ERROR,
+ "invalid upld pkt_len=%u (hdr_len=%u, max=%u)\n",
+ pkt_len, adapter->intf_hdr_len, NXPWIFI_UPLD_SIZE);
+ return -EINVAL;
+ }
+
+ port = CMD_PORT_SLCT;
+ }
+
+ /* Transfer data to card */
+ pkt_len = buf_block_len * blk_size;
+
+ if (tx_param)
+ ret = nxpwifi_host_to_card_mp_aggr(adapter, payload, pkt_len,
+ port, tx_param->next_pkt_len
+ );
+ else
+ ret = nxpwifi_host_to_card_mp_aggr(adapter, payload, pkt_len,
+ port, 0);
+
+ if (ret) {
+ if (type == NXPWIFI_TYPE_CMD ||
+ type == NXPWIFI_TYPE_VDLL)
+ adapter->cmd_sent = false;
+ if (type == NXPWIFI_TYPE_DATA) {
+ adapter->data_sent = false;
+ /* restore curr_wr_port in error cases */
+ card->curr_wr_port = port;
+ card->mp_wr_bitmap |= (u32)(1 << card->curr_wr_port);
+ }
+ } else {
+ if (type == NXPWIFI_TYPE_DATA) {
+ if (!(card->mp_wr_bitmap & (1 << card->curr_wr_port)))
+ adapter->data_sent = true;
+ else
+ adapter->data_sent = false;
+ }
+ }
+
+ return ret;
+}
+
+static int nxpwifi_alloc_sdio_mpa_buffers(struct nxpwifi_adapter *adapter,
+ u32 mpa_tx_buf_size,
+ u32 mpa_rx_buf_size)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ u32 rx_buf_size;
+ int ret = 0;
+
+ card->mpa_tx.buf = kzalloc(mpa_tx_buf_size, GFP_KERNEL);
+ if (!card->mpa_tx.buf) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ card->mpa_tx.buf_size = mpa_tx_buf_size;
+
+ rx_buf_size = max_t(u32, mpa_rx_buf_size,
+ (u32)SDIO_MAX_AGGR_BUF_SIZE);
+ card->mpa_rx.buf = kzalloc(rx_buf_size, GFP_KERNEL);
+ if (!card->mpa_rx.buf) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ card->mpa_rx.buf_size = rx_buf_size;
+
+error:
+ if (ret) {
+ kfree(card->mpa_tx.buf);
+ kfree(card->mpa_rx.buf);
+ card->mpa_tx.buf_size = 0;
+ card->mpa_rx.buf_size = 0;
+ card->mpa_tx.buf = NULL;
+ card->mpa_rx.buf = NULL;
+ }
+
+ return ret;
+}
+
+static void
+nxpwifi_unregister_dev(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+
+ if (adapter->card) {
+ card->adapter = NULL;
+ sdio_claim_host(card->func);
+ sdio_disable_func(card->func);
+ sdio_release_host(card->func);
+ }
+}
+
+static int nxpwifi_register_dev(struct nxpwifi_adapter *adapter)
+{
+ int ret;
+ struct sdio_mmc_card *card = adapter->card;
+ struct sdio_func *func = card->func;
+ const char *firmware = card->firmware;
+
+ /* save adapter pointer in card */
+ card->adapter = adapter;
+ adapter->tx_buf_size = card->tx_buf_size;
+
+ sdio_claim_host(func);
+
+ /* Set block size */
+ ret = sdio_set_block_size(card->func, NXPWIFI_SDIO_BLOCK_SIZE);
+ sdio_release_host(func);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "cannot set SDIO block size\n");
+ return ret;
+ }
+
+ /*
+ * Select correct firmware (sdsd or sdiouart) firmware based on the strapping
+ * option
+ */
+ if (card->firmware_sdiouart) {
+ u8 val;
+
+ nxpwifi_read_reg(adapter, card->reg->host_strap_reg, &val);
+ if ((val & card->reg->host_strap_mask) == card->reg->host_strap_value)
+ firmware = card->firmware_sdiouart;
+ }
+ strscpy(adapter->fw_name, firmware, sizeof(adapter->fw_name));
+
+ if (card->fw_dump_enh) {
+ adapter->mem_type_mapping_tbl = generic_mem_type_map;
+ adapter->num_mem_types = 1;
+ } else {
+ adapter->mem_type_mapping_tbl = mem_type_mapping_tbl;
+ adapter->num_mem_types = ARRAY_SIZE(mem_type_mapping_tbl);
+ }
+
+ return 0;
+}
+
+static int nxpwifi_init_sdio(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ const struct nxpwifi_sdio_card_reg *reg = card->reg;
+ int ret;
+ u8 sdio_ireg;
+
+ sdio_set_drvdata(card->func, card);
+
+ /*
+ * Read the host_int_status_reg for ACK the first interrupt got
+ * from the bootloader. If we don't do this we get a interrupt
+ * as soon as we register the irq.
+ */
+ nxpwifi_read_reg(adapter, card->reg->host_int_status_reg, &sdio_ireg);
+
+ /* Get SDIO ioport */
+ if (nxpwifi_init_sdio_ioport(adapter))
+ return -EIO;
+
+ /* Initialize SDIO variables in card */
+ card->mp_rd_bitmap = 0;
+ card->mp_wr_bitmap = 0;
+ card->curr_rd_port = reg->start_rd_port;
+ card->curr_wr_port = reg->start_wr_port;
+
+ card->mp_data_port_mask = reg->data_port_mask;
+
+ card->mpa_tx.buf_len = 0;
+ card->mpa_tx.pkt_cnt = 0;
+ card->mpa_tx.start_port = 0;
+
+ card->mpa_tx.enabled = 1;
+ card->mpa_tx.pkt_aggr_limit = card->mp_agg_pkt_limit;
+
+ card->mpa_rx.buf_len = 0;
+ card->mpa_rx.pkt_cnt = 0;
+ card->mpa_rx.start_port = 0;
+
+ card->mpa_rx.enabled = 1;
+ card->mpa_rx.pkt_aggr_limit = card->mp_agg_pkt_limit;
+
+ /* Allocate buffers for SDIO MP-A */
+ card->mp_regs = devm_kzalloc(&card->func->dev, reg->max_mp_regs, GFP_KERNEL);
+
+ if (!card->mp_regs)
+ return -ENOMEM;
+
+ card->mpa_rx.len_arr =
+ devm_kcalloc(&card->func->dev, card->mp_agg_pkt_limit,
+ sizeof(*card->mpa_rx.len_arr), GFP_KERNEL);
+
+ if (!card->mpa_rx.len_arr)
+ return -ENOMEM;
+
+ ret = nxpwifi_alloc_sdio_mpa_buffers(adapter,
+ card->mp_tx_agg_buf_size,
+ card->mp_rx_agg_buf_size);
+
+ /* Allocate 32k MPA Tx/Rx buffers if 64k memory allocation fails */
+ if (ret && (card->mp_tx_agg_buf_size == NXPWIFI_MP_AGGR_BSIZE_MAX ||
+ card->mp_rx_agg_buf_size == NXPWIFI_MP_AGGR_BSIZE_MAX)) {
+ /* Disable rx single port aggregation */
+ adapter->host_disable_sdio_rx_aggr = true;
+
+ ret = nxpwifi_alloc_sdio_mpa_buffers(adapter,
+ NXPWIFI_MP_AGGR_BSIZE_32K,
+ NXPWIFI_MP_AGGR_BSIZE_32K);
+ if (ret) {
+ /* Disable multi port aggregation */
+ card->mpa_tx.enabled = 0;
+ card->mpa_rx.enabled = 0;
+ }
+ }
+
+ adapter->ext_scan = card->can_ext_scan;
+ return ret;
+}
+
+static void nxpwifi_cleanup_mpa_buf(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+
+ MP_TX_AGGR_BUF_RESET(card);
+ MP_RX_AGGR_BUF_RESET(card);
+}
+
+static void nxpwifi_cleanup_sdio(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+
+ cancel_work_sync(&card->work);
+
+ kfree(card->mpa_tx.buf);
+ kfree(card->mpa_rx.buf);
+}
+
+static void
+nxpwifi_update_mp_end_port(struct nxpwifi_adapter *adapter, u16 port)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ const struct nxpwifi_sdio_card_reg *reg = card->reg;
+ int i;
+
+ card->mp_end_port = port;
+
+ card->mp_data_port_mask = reg->data_port_mask;
+
+ if (reg->start_wr_port) {
+ for (i = 1; i <= card->max_ports - card->mp_end_port; i++)
+ card->mp_data_port_mask &=
+ ~(1 << (card->max_ports - i));
+ }
+
+ card->curr_wr_port = reg->start_wr_port;
+}
+
+/* Perform an SDIO card reset in workqueue context. */
+static void nxpwifi_sdio_card_reset_work(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ struct sdio_func *func = card->func;
+ int ret;
+
+ /* Prepare the adapter for the reset. */
+ nxpwifi_shutdown_sw(adapter);
+ clear_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, &card->work_flags);
+ clear_bit(NXPWIFI_IFACE_WORK_CARD_RESET, &card->work_flags);
+
+ /* Run a HW reset of the SDIO interface. */
+ sdio_claim_host(func);
+ ret = mmc_hw_reset(func->card);
+ sdio_release_host(func);
+
+ switch (ret) {
+ case 1:
+ nxpwifi_dbg(adapter, MSG, "SDIO HW reset asynchronous\n");
+ complete_all(adapter->fw_done);
+ break;
+ case 0:
+ ret = nxpwifi_reinit_sw(adapter);
+ if (ret)
+ dev_err(&func->dev, "reinit failed: %d\n", ret);
+ break;
+ default:
+ dev_err(&func->dev, "SDIO HW reset failed: %d\n", ret);
+ break;
+ }
+}
+
+static enum
+rdwr_status nxpwifi_sdio_rdwr_firmware(struct nxpwifi_adapter *adapter,
+ u8 doneflag)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ int ret, tries;
+ u8 ctrl_data = 0;
+
+ sdio_writeb(card->func, card->reg->fw_dump_host_ready,
+ card->reg->fw_dump_ctrl, &ret);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "SDIO Write ERR\n");
+ return RDWR_STATUS_FAILURE;
+ }
+ for (tries = 0; tries < MAX_POLL_TRIES; tries++) {
+ ctrl_data = sdio_readb(card->func, card->reg->fw_dump_ctrl,
+ &ret);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "SDIO read err\n");
+ return RDWR_STATUS_FAILURE;
+ }
+ if (ctrl_data == FW_DUMP_DONE)
+ break;
+ if (doneflag && ctrl_data == doneflag)
+ return RDWR_STATUS_DONE;
+ if (ctrl_data != card->reg->fw_dump_host_ready) {
+ nxpwifi_dbg(adapter, WARN,
+ "The ctrl reg was changed, re-try again\n");
+ sdio_writeb(card->func, card->reg->fw_dump_host_ready,
+ card->reg->fw_dump_ctrl, &ret);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "SDIO write err\n");
+ return RDWR_STATUS_FAILURE;
+ }
+ }
+ usleep_range(100, 200);
+ }
+ if (ctrl_data == card->reg->fw_dump_host_ready) {
+ nxpwifi_dbg(adapter, ERROR, "Fail to pull ctrl_data\n");
+ return RDWR_STATUS_FAILURE;
+ }
+
+ return RDWR_STATUS_SUCCESS;
+}
+
+/* Dump firmware memories for post-mortem analysis. */
+static void nxpwifi_sdio_fw_dump(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ int ret = 0;
+ unsigned int reg, reg_start, reg_end;
+ u8 *dbg_ptr, *end_ptr, dump_num, idx, i, read_reg, doneflag = 0;
+ enum rdwr_status stat;
+ u32 memory_size;
+
+ if (!card->can_dump_fw)
+ return;
+
+ for (idx = 0; idx < ARRAY_SIZE(mem_type_mapping_tbl); idx++) {
+ struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx];
+
+ if (entry->mem_ptr) {
+ vfree(entry->mem_ptr);
+ entry->mem_ptr = NULL;
+ }
+ entry->mem_size = 0;
+ }
+
+ nxpwifi_pm_wakeup_card(adapter);
+ sdio_claim_host(card->func);
+
+ nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump start ==\n");
+
+ stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag);
+ if (stat == RDWR_STATUS_FAILURE)
+ goto done;
+
+ reg = card->reg->fw_dump_start;
+ /* Read the number of the memories which will dump */
+ dump_num = sdio_readb(card->func, reg, &ret);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "SDIO read memory length err\n");
+ goto done;
+ }
+
+ /* Read the length of every memory which will dump */
+ for (idx = 0; idx < dump_num; idx++) {
+ struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx];
+
+ stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag);
+ if (stat == RDWR_STATUS_FAILURE)
+ goto done;
+
+ memory_size = 0;
+ reg = card->reg->fw_dump_start;
+ for (i = 0; i < 4; i++) {
+ read_reg = sdio_readb(card->func, reg, &ret);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "SDIO read err\n");
+ goto done;
+ }
+ memory_size |= (read_reg << i * 8);
+ reg++;
+ }
+
+ if (memory_size == 0) {
+ nxpwifi_dbg(adapter, DUMP, "Firmware dump Finished!\n");
+ ret = nxpwifi_write_reg(adapter,
+ card->reg->fw_dump_ctrl,
+ FW_DUMP_READ_DONE);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "SDIO write err\n");
+ return;
+ }
+ break;
+ }
+
+ nxpwifi_dbg(adapter, DUMP,
+ "%s_SIZE=0x%x\n", entry->mem_name, memory_size);
+ entry->mem_ptr = vmalloc(memory_size + 1);
+ entry->mem_size = memory_size;
+ if (!entry->mem_ptr)
+ goto done;
+ dbg_ptr = entry->mem_ptr;
+ end_ptr = dbg_ptr + memory_size;
+
+ doneflag = entry->done_flag;
+ nxpwifi_dbg(adapter, DUMP, "Start %s output, please wait...\n",
+ entry->mem_name);
+
+ do {
+ stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag);
+ if (stat == RDWR_STATUS_FAILURE)
+ goto done;
+
+ reg_start = card->reg->fw_dump_start;
+ reg_end = card->reg->fw_dump_end;
+ for (reg = reg_start; reg <= reg_end; reg++) {
+ *dbg_ptr = sdio_readb(card->func, reg, &ret);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "SDIO read err\n");
+ goto done;
+ }
+ if (dbg_ptr < end_ptr)
+ dbg_ptr++;
+ else
+ nxpwifi_dbg(adapter, ERROR, "Allocated buf not enough\n");
+ }
+
+ if (stat != RDWR_STATUS_DONE)
+ continue;
+
+ nxpwifi_dbg(adapter, DUMP, "%s done: size=0x%tx\n",
+ entry->mem_name, dbg_ptr - entry->mem_ptr);
+ break;
+ } while (1);
+ }
+ nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump end ==\n");
+
+done:
+ sdio_release_host(card->func);
+}
+
+/* Generic firmware dump flow for enhanced devices. */
+static void nxpwifi_sdio_generic_fw_dump(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ struct memory_type_mapping *entry = &generic_mem_type_map[0];
+ unsigned int reg, reg_start, reg_end;
+ u8 start_flag = 0, done_flag = 0;
+ u8 *dbg_ptr, *end_ptr;
+ enum rdwr_status stat;
+ int ret = -EPERM, tries;
+
+ if (!card->fw_dump_enh)
+ return;
+
+ if (entry->mem_ptr) {
+ vfree(entry->mem_ptr);
+ entry->mem_ptr = NULL;
+ }
+ entry->mem_size = 0;
+
+ nxpwifi_pm_wakeup_card(adapter);
+ sdio_claim_host(card->func);
+
+ nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump start ==\n");
+
+ stat = nxpwifi_sdio_rdwr_firmware(adapter, done_flag);
+ if (stat == RDWR_STATUS_FAILURE)
+ goto done;
+
+ reg_start = card->reg->fw_dump_start;
+ reg_end = card->reg->fw_dump_end;
+ for (reg = reg_start; reg <= reg_end; reg++) {
+ for (tries = 0; tries < MAX_POLL_TRIES; tries++) {
+ start_flag = sdio_readb(card->func, reg, &ret);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "SDIO read err\n");
+ goto done;
+ }
+ if (start_flag == 0)
+ break;
+ if (tries == MAX_POLL_TRIES) {
+ nxpwifi_dbg(adapter, ERROR, "FW not ready to dump\n");
+ ret = -EPERM;
+ goto done;
+ }
+ }
+ usleep_range(100, 200);
+ }
+
+ entry->mem_ptr = vmalloc(0xf0000 + 1);
+ if (!entry->mem_ptr) {
+ ret = -ENOMEM;
+ goto done;
+ }
+ dbg_ptr = entry->mem_ptr;
+ entry->mem_size = 0xf0000;
+ end_ptr = dbg_ptr + entry->mem_size;
+
+ done_flag = entry->done_flag;
+ nxpwifi_dbg(adapter, DUMP,
+ "Start %s output, please wait...\n", entry->mem_name);
+
+ while (true) {
+ stat = nxpwifi_sdio_rdwr_firmware(adapter, done_flag);
+ if (stat == RDWR_STATUS_FAILURE)
+ goto done;
+ for (reg = reg_start; reg <= reg_end; reg++) {
+ *dbg_ptr = sdio_readb(card->func, reg, &ret);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "SDIO read err\n");
+ goto done;
+ }
+ dbg_ptr++;
+ if (dbg_ptr >= end_ptr) {
+ u8 *tmp_ptr;
+
+ tmp_ptr = vmalloc(entry->mem_size + 0x4000 + 1);
+ if (!tmp_ptr)
+ goto done;
+
+ memcpy(tmp_ptr, entry->mem_ptr,
+ entry->mem_size);
+ vfree(entry->mem_ptr);
+ entry->mem_ptr = tmp_ptr;
+ tmp_ptr = NULL;
+ dbg_ptr = entry->mem_ptr + entry->mem_size;
+ entry->mem_size += 0x4000;
+ end_ptr = entry->mem_ptr + entry->mem_size;
+ }
+ }
+ if (stat == RDWR_STATUS_DONE) {
+ entry->mem_size = dbg_ptr - entry->mem_ptr;
+ nxpwifi_dbg(adapter, DUMP, "dump %s done size=0x%x\n",
+ entry->mem_name, entry->mem_size);
+ ret = 0;
+ break;
+ }
+ }
+ nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump end ==\n");
+
+done:
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR, "firmware dump failed\n");
+ if (entry->mem_ptr) {
+ vfree(entry->mem_ptr);
+ entry->mem_ptr = NULL;
+ }
+ entry->mem_size = 0;
+ }
+ sdio_release_host(card->func);
+}
+
+/* Build and upload consolidated device dump. */
+static void nxpwifi_sdio_device_dump_work(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+
+ adapter->devdump_data = vzalloc(NXPWIFI_FW_DUMP_SIZE);
+ if (!adapter->devdump_data)
+ return;
+
+ nxpwifi_drv_info_dump(adapter);
+
+ /* Generic firmware dump flow for enhanced devices. */
+ if (card->fw_dump_enh)
+ nxpwifi_sdio_generic_fw_dump(adapter);
+ /* Dump firmware memories for post-mortem analysis. */
+ else
+ nxpwifi_sdio_fw_dump(adapter);
+
+ nxpwifi_prepare_fw_dump_info(adapter);
+ nxpwifi_upload_device_dump(adapter);
+}
+
+/* Process deferred SDIO work items. */
+static void nxpwifi_sdio_work(struct work_struct *work)
+{
+ struct sdio_mmc_card *card =
+ container_of(work, struct sdio_mmc_card, work);
+
+ /* Build and upload consolidated device dump. */
+ if (test_and_clear_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP,
+ &card->work_flags))
+ nxpwifi_sdio_device_dump_work(card->adapter);
+
+ /* Perform an SDIO card reset in workqueue context. */
+ if (test_and_clear_bit(NXPWIFI_IFACE_WORK_CARD_RESET,
+ &card->work_flags))
+ nxpwifi_sdio_card_reset_work(card->adapter);
+}
+
+/* Schedule SDIO card reset. */
+static void nxpwifi_sdio_card_reset(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+
+ if (!test_and_set_bit(NXPWIFI_IFACE_WORK_CARD_RESET, &card->work_flags))
+ nxpwifi_queue_work(adapter, &card->work);
+}
+
+static void nxpwifi_sdio_device_dump(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+
+ if (!test_and_set_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP,
+ &card->work_flags))
+ nxpwifi_queue_work(adapter, &card->work);
+}
+
+/* Dump SDIO function and scratch registers into drv_buf. */
+static int
+nxpwifi_sdio_reg_dump(struct nxpwifi_adapter *adapter, char *drv_buf)
+{
+ char *p = drv_buf;
+ struct sdio_mmc_card *cardp = adapter->card;
+ int ret = 0;
+ u8 count, func, data, index = 0, size = 0;
+ u8 reg, reg_start, reg_end;
+ char buf[256], *ptr;
+
+ if (!p)
+ return 0;
+
+ nxpwifi_dbg(adapter, MSG, "SDIO register dump start\n");
+
+ nxpwifi_pm_wakeup_card(adapter);
+
+ sdio_claim_host(cardp->func);
+
+ for (count = 0; count < 5; count++) {
+ memset(buf, 0, sizeof(buf));
+ ptr = buf;
+
+ switch (count) {
+ case 0:
+ /* Read the registers of SDIO function0 */
+ func = count;
+ reg_start = 0;
+ reg_end = 9;
+ break;
+ case 1:
+ /* Read the registers of SDIO function1 */
+ func = count;
+ reg_start = cardp->reg->func1_dump_reg_start;
+ reg_end = cardp->reg->func1_dump_reg_end;
+ break;
+ case 2:
+ index = 0;
+ func = 1;
+ reg_start = cardp->reg->func1_spec_reg_table[index++];
+ size = cardp->reg->func1_spec_reg_num;
+ reg_end = cardp->reg->func1_spec_reg_table[size - 1];
+ break;
+ default:
+ /* Read the scratch registers of SDIO function1 */
+ if (count == 4)
+ msleep(100);
+ func = 1;
+ reg_start = cardp->reg->func1_scratch_reg;
+ reg_end = reg_start + NXPWIFI_SDIO_SCRATCH_SIZE;
+ }
+
+ if (count != 2)
+ ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf),
+ "SDIO Func%d (%#x-%#x): ", func, reg_start,
+ reg_end);
+ else
+ ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf),
+ "SDIO Func%d: ", func);
+
+ for (reg = reg_start; reg <= reg_end;) {
+ if (func == 0)
+ data = sdio_f0_readb(cardp->func, reg, &ret);
+ else
+ data = sdio_readb(cardp->func, reg, &ret);
+
+ if (count == 2)
+ ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "(%#x) ", reg);
+ if (!ret) {
+ ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "%02x ", data);
+ } else {
+ ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "ERR");
+ break;
+ }
+
+ if (count == 2 && reg < reg_end)
+ reg = cardp->reg->func1_spec_reg_table[index++];
+ else
+ reg++;
+ }
+
+ nxpwifi_dbg(adapter, MSG, "%s\n", buf);
+ p += sprintf(p, "%s\n", buf);
+ }
+
+ sdio_release_host(cardp->func);
+
+ nxpwifi_dbg(adapter, MSG, "SDIO register dump end\n");
+
+ return p - drv_buf;
+}
+
+static void nxpwifi_sdio_up_dev(struct nxpwifi_adapter *adapter)
+{
+ struct sdio_mmc_card *card = adapter->card;
+ u8 sdio_ireg;
+ int ret = 0;
+
+ sdio_claim_host(card->func);
+ ret = sdio_enable_func(card->func);
+
+ if (ret)
+ nxpwifi_dbg(adapter, ERROR, "sdio_enable_func failed: %d\n", ret);
+
+ ret = sdio_set_block_size(card->func, NXPWIFI_SDIO_BLOCK_SIZE);
+
+ if (ret)
+ nxpwifi_dbg(adapter, ERROR, "sdio_set_block_size failed: %d\n", ret);
+
+ sdio_release_host(card->func);
+
+ /*
+ * tx_buf_size might be changed to 3584 by firmware during
+ * data transfer, we will reset to default size.
+ */
+ adapter->tx_buf_size = card->tx_buf_size;
+
+ /*
+ * Read the host_int_status_reg for ACK the first interrupt got
+ * from the bootloader. If we don't do this we get a interrupt
+ * as soon as we register the irq.
+ */
+ nxpwifi_read_reg(adapter, card->reg->host_int_status_reg, &sdio_ireg);
+
+ if (nxpwifi_init_sdio_ioport(adapter))
+ nxpwifi_dbg(adapter, ERROR, "error enabling SDIO port\n");
+}
+
+static struct nxpwifi_if_ops sdio_ops = {
+ .init_if = nxpwifi_init_sdio,
+ .cleanup_if = nxpwifi_cleanup_sdio,
+ .check_fw_status = nxpwifi_check_fw_status,
+ .check_winner_status = nxpwifi_check_winner_status,
+ .prog_fw = nxpwifi_prog_fw_w_helper,
+ .register_dev = nxpwifi_register_dev,
+ .unregister_dev = nxpwifi_unregister_dev,
+ .enable_int = nxpwifi_sdio_enable_host_int,
+ .disable_int = nxpwifi_sdio_disable_host_int,
+ .process_int_status = nxpwifi_process_int_status,
+ .host_to_card = nxpwifi_sdio_host_to_card,
+ .wakeup = nxpwifi_pm_wakeup_card,
+ .wakeup_complete = nxpwifi_pm_wakeup_card_complete,
+
+ /* SDIO specific */
+ .update_mp_end_port = nxpwifi_update_mp_end_port,
+ .cleanup_mpa_buf = nxpwifi_cleanup_mpa_buf,
+ .cmdrsp_complete = nxpwifi_sdio_cmdrsp_complete,
+ .event_complete = nxpwifi_sdio_event_complete,
+ .dnld_fw = nxpwifi_sdio_dnld_fw,
+ .card_reset = nxpwifi_sdio_card_reset,
+ .reg_dump = nxpwifi_sdio_reg_dump,
+ .device_dump = nxpwifi_sdio_device_dump,
+ .deaggr_pkt = nxpwifi_deaggr_sdio_pkt,
+ .up_dev = nxpwifi_sdio_up_dev,
+};
+
+module_sdio_driver(nxpwifi_sdio);
+
+MODULE_AUTHOR("NXP International Ltd.");
+MODULE_DESCRIPTION("NXP WiFi SDIO Driver version " SDIO_VERSION);
+MODULE_VERSION(SDIO_VERSION);
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(IW61X_SDIO_FW_NAME);
diff --git a/drivers/net/wireless/nxp/nxpwifi/sdio.h b/drivers/net/wireless/nxp/nxpwifi/sdio.h
new file mode 100644
index 000000000000..de5c884a5b14
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/sdio.h
@@ -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 */
--
2.34.1
^ permalink raw reply related
* [PATCH v12 18/22] wifi: nxpwifi: add core driver implementation
From: Jeff Chen @ 2026-06-05 16:13 UTC (permalink / raw)
To: linux-wireless
Cc: linux-kernel, briannorris, johannes, francesco, wyatt.hsu,
s.hauer, ulf.hansson, Jeff Chen
In-Reply-To: <20260605161335.2415583-1-jeff.chen_1@nxp.com>
Add the core layer of the nxpwifi driver, providing the primary logic and
infrastructure for managing the wireless adapter.
Implement adapter registration, initialization, and teardown; define the
main driver execution paths for TX/RX, command processing, and event
handling; and add firmware download and wakeup support. Introduce power
management integration, workqueue handling, and interrupt processing, and
provide debug and dump facilities for diagnostics.
Define the core data structures and interfaces in main.h, and establish
the nxpwifi_if_ops abstraction for use by bus-specific drivers.
Signed-off-by: Jeff Chen <jeff.chen_1@nxp.com>
---
drivers/net/wireless/nxp/nxpwifi/main.c | 1673 +++++++++++++++++++++++
drivers/net/wireless/nxp/nxpwifi/main.h | 1427 +++++++++++++++++++
2 files changed, 3100 insertions(+)
create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.c
create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.h
diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c
new file mode 100644
index 000000000000..a7c6d592e2ff
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/main.c
@@ -0,0 +1,1673 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * NXP Wireless LAN device driver: major functions
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#include <linux/suspend.h>
+
+#include "main.h"
+#include "cmdevt.h"
+#include "wmm.h"
+#include "cfg80211.h"
+#include "11n.h"
+
+#define VERSION "1.0"
+
+static unsigned int debug_mask = NXPWIFI_DEFAULT_DEBUG_MASK;
+
+char driver_version[] = "nxpwifi " VERSION " (%s) ";
+
+const u16 nxpwifi_1d_to_wmm_queue[8] = { 1, 0, 0, 1, 2, 2, 3, 3 };
+
+/* Optional RF calibration data file */
+static const char *cal_data_name = "nxp/cal_data.conf";
+
+/* Register device; init adapter/privs/if_ops/locks; cleanup on fail. */
+static struct nxpwifi_adapter *nxpwifi_register(void *card, struct device *dev,
+ struct nxpwifi_if_ops *if_ops)
+{
+ struct nxpwifi_adapter *adapter;
+ int ret = 0;
+ int i;
+
+ adapter = kzalloc_obj(*adapter, GFP_KERNEL);
+ if (!adapter)
+ return ERR_PTR(-ENOMEM);
+
+ adapter->dev = dev;
+ adapter->card = card;
+
+ /* Save interface specific operations in adapter */
+ memmove(&adapter->if_ops, if_ops, sizeof(struct nxpwifi_if_ops));
+ adapter->debug_mask = debug_mask;
+
+ /* card specific initialization has been deferred until now .. */
+ if (adapter->if_ops.init_if) {
+ ret = adapter->if_ops.init_if(adapter);
+ if (ret)
+ goto error;
+ }
+
+ adapter->priv_num = 0;
+
+ for (i = 0; i < NXPWIFI_MAX_BSS_NUM; i++) {
+ /* Allocate memory for private structure */
+ adapter->priv[i] =
+ kzalloc_obj(struct nxpwifi_private, GFP_KERNEL);
+ if (!adapter->priv[i]) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ adapter->priv[i]->adapter = adapter;
+ adapter->priv_num++;
+ }
+ nxpwifi_init_lock_list(adapter);
+
+ timer_setup(&adapter->cmd_timer, nxpwifi_cmd_timeout_func, 0);
+
+ if (ret)
+ return ERR_PTR(ret);
+ else
+ return adapter;
+
+error:
+ nxpwifi_dbg(adapter, ERROR,
+ "info: leave %s with error\n", __func__);
+
+ for (i = 0; i < adapter->priv_num; i++)
+ kfree(adapter->priv[i]);
+
+ kfree(adapter);
+
+ return ERR_PTR(ret);
+}
+
+/* Unregister device; free timers, beacons, privs, nd_info, adapter. */
+static void nxpwifi_unregister(struct nxpwifi_adapter *adapter)
+{
+ s32 i;
+
+ if (adapter->if_ops.cleanup_if)
+ adapter->if_ops.cleanup_if(adapter);
+
+ timer_delete_sync(&adapter->cmd_timer);
+
+ /* Free private structures */
+ for (i = 0; i < adapter->priv_num; i++) {
+ nxpwifi_free_curr_bcn(adapter->priv[i]);
+ kfree(adapter->priv[i]);
+ }
+
+ if (adapter->nd_info) {
+ for (i = 0 ; i < adapter->nd_info->n_matches ; i++)
+ kfree(adapter->nd_info->matches[i]);
+ kfree(adapter->nd_info);
+ adapter->nd_info = NULL;
+ }
+
+ kfree(adapter->regd);
+
+ kfree(adapter);
+}
+
+static void nxpwifi_queue_rx_work(struct nxpwifi_adapter *adapter)
+{
+ queue_work(adapter->rx_workqueue, &adapter->rx_work);
+}
+
+static void nxpwifi_process_rx(struct nxpwifi_adapter *adapter)
+{
+ struct sk_buff *skb;
+ struct nxpwifi_rxinfo *rx_info;
+
+ if (atomic_read(&adapter->iface_changing) ||
+ atomic_read(&adapter->rx_ba_teardown_pending))
+ return;
+
+ /* Check for Rx data */
+ while ((skb = skb_dequeue(&adapter->rx_data_q))) {
+ atomic_dec(&adapter->rx_pending);
+ if (adapter->delay_main_work &&
+ (atomic_read(&adapter->rx_pending) < LOW_RX_PENDING)) {
+ adapter->delay_main_work = false;
+ nxpwifi_queue_work(adapter, &adapter->main_work);
+ }
+ rx_info = NXPWIFI_SKB_RXCB(skb);
+ if (rx_info->buf_type == NXPWIFI_TYPE_AGGR_DATA) {
+ if (adapter->if_ops.deaggr_pkt)
+ adapter->if_ops.deaggr_pkt(adapter, skb);
+ dev_kfree_skb_any(skb);
+ } else {
+ nxpwifi_handle_rx_packet(adapter, skb);
+ }
+ }
+}
+
+static void maybe_quirk_fw_disable_ds(struct nxpwifi_adapter *adapter)
+{
+ struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA);
+ struct nxpwifi_ver_ext ver_ext;
+
+ if (test_and_set_bit(NXPWIFI_IS_REQUESTING_FW_VEREXT, &adapter->work_flags))
+ return;
+
+ memset(&ver_ext, 0, sizeof(ver_ext));
+ ver_ext.version_str_sel = 1;
+ if (nxpwifi_send_cmd(priv, HOST_CMD_VERSION_EXT,
+ HOST_ACT_GEN_GET, 0, &ver_ext, false)) {
+ nxpwifi_dbg(priv->adapter, MSG,
+ "Checking hardware revision failed.\n");
+ }
+}
+
+static void nxpwifi_handle_irq_status(struct nxpwifi_adapter *adapter, u8 istat)
+{
+ if (adapter->hs_activated)
+ nxpwifi_process_hs_config(adapter);
+ if (adapter->if_ops.process_int_status)
+ adapter->if_ops.process_int_status(adapter, istat);
+}
+
+static bool nxpwifi_drain_tx(struct nxpwifi_adapter *adapter)
+{
+ bool ret = false;
+
+ if ((adapter->scan_chan_gap_enabled || !adapter->scan_processing) &&
+ !adapter->data_sent && !skb_queue_empty(&adapter->tx_data_q)) {
+ if (adapter->hs_activated_manually) {
+ nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY),
+ NXPWIFI_ASYNC_CMD);
+ adapter->hs_activated_manually = false;
+ }
+
+ nxpwifi_process_tx_queue(adapter);
+ if (adapter->hs_activated) {
+ clear_bit(NXPWIFI_IS_HS_CONFIGURED,
+ &adapter->work_flags);
+ nxpwifi_hs_activated_event
+ (nxpwifi_get_priv
+ (adapter, NXPWIFI_BSS_ROLE_ANY),
+ false);
+ }
+ ret = true;
+ }
+
+ if ((adapter->scan_chan_gap_enabled ||
+ !adapter->scan_processing) &&
+ !adapter->data_sent &&
+ !nxpwifi_bypass_txlist_empty(adapter)) {
+ if (adapter->hs_activated_manually) {
+ nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY),
+ NXPWIFI_ASYNC_CMD);
+ adapter->hs_activated_manually = false;
+ }
+ nxpwifi_process_bypass_tx(adapter);
+ if (adapter->hs_activated) {
+ clear_bit(NXPWIFI_IS_HS_CONFIGURED,
+ &adapter->work_flags);
+ nxpwifi_hs_activated_event
+ (nxpwifi_get_priv
+ (adapter, NXPWIFI_BSS_ROLE_ANY),
+ false);
+ }
+ ret = true;
+ }
+
+ if ((adapter->scan_chan_gap_enabled ||
+ !adapter->scan_processing) &&
+ !adapter->data_sent && !nxpwifi_wmm_lists_empty(adapter)) {
+ if (adapter->hs_activated_manually) {
+ nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY),
+ NXPWIFI_ASYNC_CMD);
+ adapter->hs_activated_manually = false;
+ }
+
+ nxpwifi_wmm_process_tx(adapter);
+ if (adapter->hs_activated) {
+ clear_bit(NXPWIFI_IS_HS_CONFIGURED,
+ &adapter->work_flags);
+ nxpwifi_hs_activated_event
+ (nxpwifi_get_priv
+ (adapter, NXPWIFI_BSS_ROLE_ANY),
+ false);
+ }
+ ret = true;
+ }
+
+ return ret;
+}
+
+static bool nxpwifi_handle_rx(struct nxpwifi_adapter *adapter)
+{
+ if (adapter->rx_work_enabled && adapter->data_received) {
+ nxpwifi_queue_rx_work(adapter);
+ return true;
+ }
+
+ return false;
+}
+
+static bool nxpwifi_handle_cmd_response(struct nxpwifi_adapter *adapter)
+{
+ /* Check for Cmd Resp */
+ if (adapter->cmd_resp_received) {
+ adapter->cmd_resp_received = false;
+ nxpwifi_process_cmdresp(adapter);
+ return true;
+ }
+
+ return false;
+}
+
+static bool nxpwifi_handle_events(struct nxpwifi_adapter *adapter)
+{
+ if (adapter->event_received) {
+ adapter->event_received = false;
+ nxpwifi_process_event(adapter);
+ return true;
+ }
+
+ return false;
+}
+
+static bool nxpwifi_tx_has_pending(struct nxpwifi_adapter *adapter)
+{
+ return !skb_queue_empty(&adapter->tx_data_q) ||
+ !nxpwifi_bypass_txlist_empty(adapter) ||
+ !nxpwifi_wmm_lists_empty(adapter);
+}
+
+static bool nxpwifi_cmd_has_pending(struct nxpwifi_adapter *adapter)
+{
+ return !list_empty(&adapter->cmd_pending_q);
+}
+
+static bool nxpwifi_events_has_pending(struct nxpwifi_adapter *adapter)
+{
+ return adapter->event_received;
+}
+
+static bool nxpwifi_should_wakeup_card(struct nxpwifi_adapter *adapter)
+{
+ if (adapter->ps_state != PS_STATE_SLEEP)
+ return false;
+
+ if (!adapter->pm_wakeup_card_req || adapter->pm_wakeup_fw_try)
+ return false;
+
+ return is_command_pending(adapter) || nxpwifi_tx_has_pending(adapter);
+}
+
+static bool nxpwifi_should_exit_main_loop(struct nxpwifi_adapter *adapter)
+{
+ if (adapter->pm_wakeup_fw_try)
+ return true;
+
+ if (adapter->ps_state == PS_STATE_PRE_SLEEP)
+ nxpwifi_check_ps_cond(adapter);
+
+ if (adapter->ps_state != PS_STATE_AWAKE)
+ return true;
+
+ if (adapter->tx_lock_flag)
+ return true;
+
+ if ((!adapter->scan_chan_gap_enabled && adapter->scan_processing) ||
+ adapter->data_sent || !nxpwifi_tx_has_pending(adapter)) {
+ if (adapter->cmd_sent || adapter->curr_cmd ||
+ !is_command_pending(adapter))
+ return true;
+ }
+
+ return false;
+}
+
+static void nxpwifi_wakeup_card(struct nxpwifi_adapter *adapter)
+{
+ adapter->pm_wakeup_fw_try = true;
+ mod_timer(&adapter->wakeup_timer, jiffies + (HZ * 3));
+ adapter->if_ops.wakeup(adapter);
+}
+
+static void nxpwifi_handle_vdll_download(struct nxpwifi_adapter *adapter)
+{
+ if (!adapter->cmd_sent && adapter->vdll_ctrl.pending_block) {
+ struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl;
+
+ nxpwifi_download_vdll_block(adapter, ctrl->pending_block,
+ ctrl->pending_block_len);
+ ctrl->pending_block = NULL;
+ }
+}
+
+static void nxpwifi_finish_delayed_null_pkt(struct nxpwifi_adapter *adapter)
+{
+ if (!adapter->delay_null_pkt)
+ return;
+
+ if (adapter->cmd_sent || adapter->curr_cmd || is_command_pending(adapter))
+ return;
+
+ if (nxpwifi_tx_has_pending(adapter))
+ return;
+
+ if (!nxpwifi_send_null_packet(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA),
+ NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET |
+ NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) {
+ adapter->delay_null_pkt = false;
+ adapter->ps_state = PS_STATE_SLEEP;
+ }
+}
+
+static bool nxpwifi_pump_command(struct nxpwifi_adapter *adapter)
+{
+ if (!adapter->cmd_sent && !adapter->curr_cmd) {
+ if (!nxpwifi_exec_next_cmd(adapter))
+ return true;
+ }
+
+ return false;
+}
+
+/* Main loop: IRQ/RX/CMD/EVENT; wake card; TX; PS null; exit if idle. */
+void nxpwifi_main_process(struct nxpwifi_adapter *adapter)
+{
+ unsigned long flags;
+
+ /* Check if virtual interface changing */
+ if (atomic_read(&adapter->iface_changing)) {
+ nxpwifi_dbg(adapter,
+ INFO, "main_process skipped due to iface_changing");
+ return;
+ }
+
+ for (;;) {
+ bool did_work = false;
+ u8 istat = 0;
+
+ if (adapter->hw_status == NXPWIFI_HW_STATUS_NOT_READY)
+ break;
+
+ /*
+ * For non-USB interfaces, If we process interrupts first, it
+ * would increase RX pending even further. Avoid this by
+ * checking if rx_pending has crossed high threshold and
+ * schedule rx work queue and then process interrupts.
+ * For USB interface, there are no interrupts. We already have
+ * HIGH_RX_PENDING check in usb.c
+ */
+ if (atomic_read(&adapter->rx_pending) >= HIGH_RX_PENDING) {
+ adapter->delay_main_work = true;
+ nxpwifi_queue_rx_work(adapter);
+ break;
+ }
+
+ /*
+ * Snapshot-and-clear the interrupt status.
+ *
+ * Take the same lock as the producer (nxpwifi_sdio_interrupt()) uses
+ * when OR-ing new bits into adapter->int_status. We atomically grab
+ * what has accumulated and clear it, so this consumer owns this batch.
+ */
+ spin_lock_irqsave(&adapter->int_lock, flags);
+ istat = adapter->int_status;
+ adapter->int_status = 0;
+ spin_unlock_irqrestore(&adapter->int_lock, flags);
+
+ /* Handle pending interrupt if any */
+ if (istat) {
+ nxpwifi_handle_irq_status(adapter, istat);
+ did_work = true;
+ }
+
+ did_work |= nxpwifi_handle_rx(adapter);
+
+ if (nxpwifi_should_wakeup_card(adapter)) {
+ nxpwifi_wakeup_card(adapter);
+ continue;
+ }
+
+ if (IS_CARD_RX_RCVD(adapter)) {
+ /* Card has responded, clear wakeup state and update power state */
+ adapter->data_received = false;
+ adapter->pm_wakeup_fw_try = false;
+ timer_delete(&adapter->wakeup_timer);
+ if (adapter->ps_state == PS_STATE_SLEEP)
+ adapter->ps_state = PS_STATE_AWAKE;
+ } else {
+ if (nxpwifi_should_exit_main_loop(adapter))
+ break;
+ }
+
+ did_work |= nxpwifi_handle_events(adapter);
+
+ did_work |= nxpwifi_handle_cmd_response(adapter);
+
+ /* Check if we need to confirm Sleep Request received previously */
+ if (adapter->ps_state == PS_STATE_PRE_SLEEP)
+ nxpwifi_check_ps_cond(adapter);
+
+ /*
+ * The ps_state may have been changed during processing of
+ * Sleep Request event.
+ */
+ if (adapter->ps_state != PS_STATE_AWAKE)
+ continue;
+
+ if (adapter->tx_lock_flag)
+ continue;
+
+ nxpwifi_handle_vdll_download(adapter);
+
+ did_work |= nxpwifi_pump_command(adapter);
+
+ did_work |= nxpwifi_drain_tx(adapter);
+
+ /*
+ * Attempt to send delayed null packet.
+ * If successful, firmware will enter sleep and ps_state will be updated.
+ * We check ps_state here to determine if main loop can safely exit.
+ */
+ nxpwifi_finish_delayed_null_pkt(adapter);
+
+ if (adapter->ps_state == PS_STATE_SLEEP)
+ break;
+ /*
+ * Step 3) Cooperative preemption point.
+ * cond_resched() yields ONLY if need_resched() is set. Placing it
+ * BEFORE the final check improves fairness: it lets ksdioirqd (RT/FIFO)
+ * or other producers run and set new int_status bits. Immediately
+ * after we return here, we perform the final "net cast" (Step 4) to
+ * decide if we should continue or return.
+ */
+
+ cond_resched();
+
+ /*
+ * Step 4) Exit decision with lost-kick closure.
+ *
+ * We consider exiting ONLY when this round did no real work.
+ * Rationale:
+ * - If did_work == true: we will loop anyway; at Step 1 we will
+ * re-snapshot int_status, so there's no need to re-check now.
+ * - If did_work == false: we appear idle and may return. But during
+ * our execution window, a producer may have just set int_status and
+ * queue_work(); since this work is still running, queue_work()
+ * returns false (no second instance queued). If we return now,
+ * we'd leave unprocessed status with no pending work => lost-kick.
+ *
+ * Therefore, perform a single final check: if *anything* is pending,
+ * continue looping; otherwise, break and return.
+ */
+ if (!did_work) {
+ bool more = false;
+ unsigned long flags;
+ /* 4a) New IRQ bits raced in while we were running? */
+ spin_lock_irqsave(&adapter->int_lock, flags);
+ more |= adapter->int_status != 0;
+ spin_unlock_irqrestore(&adapter->int_lock, flags);
+ /* 4b) Any other sources still pending? (driver-specific) */
+ more |= nxpwifi_tx_has_pending(adapter);
+ more |= nxpwifi_cmd_has_pending(adapter);
+ more |= nxpwifi_events_has_pending(adapter);
+
+ if (!more)
+ break; /* Truly quiescent now: safe to return. */
+ /* else: loop back to Step 1 to consume what just arrived. */
+ }
+ /* If did_work == true, we loop unconditionally and re-snapshot. */
+ };
+}
+
+/* Free adapter via nxpwifi_unregister(). */
+static void nxpwifi_free_adapter(struct nxpwifi_adapter *adapter)
+{
+ if (!adapter) {
+ pr_err("%s: adapter is NULL\n", __func__);
+ return;
+ }
+
+ nxpwifi_unregister(adapter);
+ pr_debug("info: %s: free adapter\n", __func__);
+}
+
+/* Destroy main and RX workqueues. */
+static void nxpwifi_terminate_workqueue(struct nxpwifi_adapter *adapter)
+{
+ if (adapter->workqueue) {
+ destroy_workqueue(adapter->workqueue);
+ adapter->workqueue = NULL;
+ }
+
+ if (adapter->rx_workqueue) {
+ destroy_workqueue(adapter->rx_workqueue);
+ adapter->rx_workqueue = NULL;
+ }
+}
+
+/* FW bring-up: download, enable IRQ, init FW; cfg80211+ifaces; cleanup. */
+static int _nxpwifi_fw_dpc(const struct firmware *firmware, void *context)
+{
+ int ret = 0;
+ char fmt[64];
+ struct nxpwifi_adapter *adapter = context;
+ struct nxpwifi_fw_image fw;
+ bool init_failed = false;
+ struct wireless_dev *wdev;
+ struct completion *fw_done = adapter->fw_done;
+
+ if (!firmware) {
+ nxpwifi_dbg(adapter, ERROR,
+ "Failed to get firmware %s\n", adapter->fw_name);
+ ret = -EINVAL;
+ goto err_dnld_fw;
+ }
+
+ memset(&fw, 0, sizeof(struct nxpwifi_fw_image));
+ adapter->firmware = firmware;
+ fw.fw_buf = (u8 *)adapter->firmware->data;
+ fw.fw_len = adapter->firmware->size;
+
+ if (adapter->if_ops.dnld_fw)
+ ret = adapter->if_ops.dnld_fw(adapter, &fw);
+ else
+ ret = nxpwifi_dnld_fw(adapter, &fw);
+
+ if (ret)
+ goto err_dnld_fw;
+
+ nxpwifi_dbg(adapter, MSG, "WLAN FW is active\n");
+
+ /* Load optional calibration data */
+ ret = request_firmware(&adapter->cal_data, cal_data_name, adapter->dev);
+ if (ret) {
+ nxpwifi_dbg(adapter, INFO, "no %s, using default cal\n",
+ cal_data_name);
+ adapter->cal_data = NULL;
+ }
+
+ /* enable host interrupt after fw dnld is successful */
+ if (adapter->if_ops.enable_int) {
+ ret = adapter->if_ops.enable_int(adapter);
+ if (ret)
+ goto err_dnld_fw;
+ }
+
+ ret = nxpwifi_init_fw(adapter);
+ if (ret)
+ goto err_init_fw;
+
+ maybe_quirk_fw_disable_ds(adapter);
+
+ if (!adapter->wiphy) {
+ if (nxpwifi_register_cfg80211(adapter)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "cannot register with cfg80211\n");
+ goto err_init_fw;
+ }
+ }
+
+ if (nxpwifi_init_channel_scan_gap(adapter)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "could not init channel stats table\n");
+ goto err_init_chan_scan;
+ }
+
+ rtnl_lock();
+ /* Create station interface by default */
+ wdev = nxpwifi_add_virtual_intf(adapter->wiphy, "mlan%d", NET_NAME_ENUM,
+ NL80211_IFTYPE_STATION, NULL);
+ if (IS_ERR(wdev)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "cannot create default STA interface\n");
+ rtnl_unlock();
+ goto err_add_intf;
+ }
+
+ wdev = nxpwifi_add_virtual_intf(adapter->wiphy, "uap%d", NET_NAME_ENUM,
+ NL80211_IFTYPE_AP, NULL);
+ if (IS_ERR(wdev)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "cannot create AP interface\n");
+ rtnl_unlock();
+ goto err_add_intf;
+ }
+
+ rtnl_unlock();
+
+ nxpwifi_drv_get_driver_version(adapter, fmt, sizeof(fmt) - 1);
+ nxpwifi_dbg(adapter, MSG, "driver_version = %s\n", fmt);
+ adapter->is_up = true;
+ goto done;
+
+err_add_intf:
+ vfree(adapter->chan_stats);
+err_init_chan_scan:
+ wiphy_unregister(adapter->wiphy);
+ wiphy_free(adapter->wiphy);
+err_init_fw:
+ if (adapter->if_ops.disable_int)
+ adapter->if_ops.disable_int(adapter);
+err_dnld_fw:
+ nxpwifi_dbg(adapter, ERROR,
+ "info: %s: unregister device\n", __func__);
+ if (adapter->if_ops.unregister_dev)
+ adapter->if_ops.unregister_dev(adapter);
+
+ set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags);
+ nxpwifi_terminate_workqueue(adapter);
+
+ if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) {
+ pr_debug("info: %s: shutdown nxpwifi\n", __func__);
+ nxpwifi_shutdown_drv(adapter);
+ nxpwifi_free_cmd_buffers(adapter);
+ }
+
+ init_failed = true;
+done:
+ if (adapter->cal_data) {
+ release_firmware(adapter->cal_data);
+ adapter->cal_data = NULL;
+ }
+ if (adapter->firmware) {
+ release_firmware(adapter->firmware);
+ adapter->firmware = NULL;
+ }
+ if (init_failed)
+ nxpwifi_free_adapter(adapter);
+
+ /* Tell all current and future waiters we're finished */
+ complete_all(fw_done);
+
+ return ret;
+}
+
+static void nxpwifi_fw_dpc(const struct firmware *firmware, void *context)
+{
+ _nxpwifi_fw_dpc(firmware, context);
+}
+
+/* Request firmware (sync/async) and start HW init. */
+static int nxpwifi_init_hw_fw(struct nxpwifi_adapter *adapter,
+ bool req_fw_nowait)
+{
+ int ret;
+
+ if (req_fw_nowait) {
+ ret = request_firmware_nowait(THIS_MODULE, 1, adapter->fw_name,
+ adapter->dev, GFP_KERNEL, adapter,
+ nxpwifi_fw_dpc);
+ } else {
+ ret = request_firmware(&adapter->firmware,
+ adapter->fw_name,
+ adapter->dev);
+ }
+
+ if (ret < 0)
+ nxpwifi_dbg(adapter, ERROR, "request_firmware%s error %d\n",
+ req_fw_nowait ? "_nowait" : "", ret);
+ return ret;
+}
+
+/* ndo_open: bring carrier down. */
+static int
+nxpwifi_open(struct net_device *dev)
+{
+ netif_carrier_off(dev);
+
+ return 0;
+}
+
+/* ndo_stop: abort scan/sched-scan if running. */
+static int
+nxpwifi_close(struct net_device *dev)
+{
+ struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev);
+
+ if (priv->scan_request) {
+ struct cfg80211_scan_info info = {
+ .aborted = true,
+ };
+
+ nxpwifi_dbg(priv->adapter, INFO,
+ "aborting scan on ndo_stop\n");
+ cfg80211_scan_done(priv->scan_request, &info);
+ priv->scan_request = NULL;
+ priv->scan_aborting = true;
+ }
+
+ if (priv->sched_scanning) {
+ nxpwifi_dbg(priv->adapter, INFO,
+ "aborting bgscan on ndo_stop\n");
+ nxpwifi_stop_bg_scan(priv);
+ cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0);
+ }
+
+ return 0;
+}
+
+static bool
+nxpwifi_bypass_tx_queue(struct nxpwifi_private *priv,
+ struct sk_buff *skb)
+{
+ struct ethhdr *eth_hdr = (struct ethhdr *)skb->data;
+
+ if (eth_hdr->h_proto == htons(ETH_P_PAE) ||
+ nxpwifi_is_skb_mgmt_frame(skb)) {
+ nxpwifi_dbg(priv->adapter, DATA,
+ "bypass txqueue; eth type %#x, mgmt %d\n",
+ ntohs(eth_hdr->h_proto),
+ nxpwifi_is_skb_mgmt_frame(skb));
+ if (eth_hdr->h_proto == htons(ETH_P_PAE))
+ nxpwifi_dbg(priv->adapter, MSG,
+ "key: send EAPOL to %pM\n",
+ eth_hdr->h_dest);
+ return true;
+ }
+
+ return false;
+}
+
+/* Queue SKB (WMM or bypass) and schedule main work. */
+void nxpwifi_queue_tx_pkt(struct nxpwifi_private *priv, struct sk_buff *skb)
+{
+ struct nxpwifi_adapter *adapter = priv->adapter;
+ struct netdev_queue *txq;
+ int index = nxpwifi_1d_to_wmm_queue[skb->priority];
+
+ if (atomic_inc_return(&priv->wmm_tx_pending[index]) >= MAX_TX_PENDING) {
+ txq = netdev_get_tx_queue(priv->netdev, index);
+ if (!netif_tx_queue_stopped(txq)) {
+ netif_tx_stop_queue(txq);
+ nxpwifi_dbg(adapter, DATA,
+ "stop queue: %d\n", index);
+ }
+ }
+
+ if (nxpwifi_bypass_tx_queue(priv, skb)) {
+ atomic_inc(&adapter->tx_pending);
+ atomic_inc(&adapter->bypass_tx_pending);
+ nxpwifi_wmm_add_buf_bypass_txqueue(priv, skb);
+ } else {
+ atomic_inc(&adapter->tx_pending);
+ nxpwifi_wmm_add_buf_txqueue(priv, skb);
+ }
+
+ nxpwifi_queue_work(adapter, &adapter->main_work);
+}
+
+struct sk_buff *
+nxpwifi_clone_skb_for_tx_status(struct nxpwifi_private *priv,
+ struct sk_buff *skb, u8 flag, u64 *cookie)
+{
+ struct sk_buff *orig_skb = skb;
+ struct nxpwifi_txinfo *tx_info, *orig_tx_info;
+ u32 id32 = 0;
+ int ret;
+
+ skb = skb_clone(skb, GFP_ATOMIC);
+ if (skb) {
+ spin_lock_bh(&priv->ack_status_lock);
+ /*
+ * Use XArray to allocate IDs in the range 1..0x0F.
+ * Limit ensures the allocated token ID is always within this
+ * range.
+ */
+ ret = xa_alloc(&priv->ack_status_frames, &id32, orig_skb,
+ XA_LIMIT(1, 0x0f), GFP_ATOMIC);
+ spin_unlock_bh(&priv->ack_status_lock);
+
+ if (ret == 0) {
+ tx_info = NXPWIFI_SKB_TXCB(skb);
+ tx_info->ack_frame_id = id32;
+ tx_info->flags |= flag;
+ orig_tx_info = NXPWIFI_SKB_TXCB(orig_skb);
+ orig_tx_info->ack_frame_id = id32;
+ orig_tx_info->flags |= flag;
+
+ if (flag == NXPWIFI_BUF_FLAG_ACTION_TX_STATUS && cookie)
+ orig_tx_info->cookie = *cookie;
+
+ } else if (skb_shared(skb)) {
+ kfree_skb(orig_skb);
+ } else {
+ kfree_skb(skb);
+ skb = orig_skb;
+ }
+ } else {
+ /* couldn't clone -- lose tx status ... */
+ skb = orig_skb;
+ }
+
+ return skb;
+}
+
+/* ndo_start_xmit: fix headroom, fill TXCB, timestamp, enqueue. */
+static netdev_tx_t
+nxpwifi_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev);
+ struct sk_buff *new_skb;
+ struct nxpwifi_txinfo *tx_info;
+ bool multicast;
+
+ nxpwifi_dbg(priv->adapter, DATA,
+ "data: %lu BSS(%d-%d): Data <= kernel\n",
+ jiffies, priv->bss_type, priv->bss_num);
+
+ if (test_bit(NXPWIFI_SURPRISE_REMOVED, &priv->adapter->work_flags)) {
+ kfree_skb(skb);
+ priv->stats.tx_dropped++;
+ return 0;
+ }
+ if (!skb->len || skb->len > ETH_FRAME_LEN) {
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "Tx: bad skb len %d\n", skb->len);
+ kfree_skb(skb);
+ priv->stats.tx_dropped++;
+ return 0;
+ }
+ if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) {
+ nxpwifi_dbg(priv->adapter, DATA,
+ "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(priv->adapter, ERROR,
+ "Tx: cannot alloca new_skb\n");
+ kfree_skb(skb);
+ priv->stats.tx_dropped++;
+ return 0;
+ }
+ kfree_skb(skb);
+ skb = new_skb;
+ nxpwifi_dbg(priv->adapter, INFO,
+ "info: new skb headroomd %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->pkt_len = skb->len;
+
+ multicast = is_multicast_ether_addr(skb->data);
+
+ if (unlikely(!multicast && sk_requests_wifi_status(skb->sk) &&
+ priv->adapter->fw_api_ver == NXPWIFI_FW_V15))
+ skb = nxpwifi_clone_skb_for_tx_status(priv,
+ skb,
+ NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS, NULL);
+
+ /*
+ * Record the current time the packet was queued; used to
+ * determine the amount of time the packet was queued in
+ * the driver before it was sent to the firmware.
+ * The delay is then sent along with the packet to the
+ * firmware for aggregate delay calculation for stats and
+ * MSDU lifetime expiry.
+ */
+ __net_timestamp(skb);
+
+ nxpwifi_queue_tx_pkt(priv, skb);
+
+ return 0;
+}
+
+int nxpwifi_set_mac_address(struct nxpwifi_private *priv,
+ struct net_device *dev, bool external,
+ u8 *new_mac)
+{
+ int ret;
+ u64 mac_addr, old_mac_addr;
+
+ old_mac_addr = ether_addr_to_u64(priv->curr_addr);
+
+ if (external) {
+ mac_addr = ether_addr_to_u64(new_mac);
+ } else {
+ /* Internal mac address change */
+ if (priv->bss_type == NXPWIFI_BSS_TYPE_ANY)
+ return -EOPNOTSUPP;
+
+ mac_addr = old_mac_addr;
+
+ if (priv->adapter->priv[0] != priv) {
+ /* Set mac address based on bss_type/bss_num */
+ mac_addr ^= BIT_ULL(priv->bss_type + 8);
+ mac_addr += priv->bss_num;
+ }
+ }
+
+ u64_to_ether_addr(mac_addr, priv->curr_addr);
+
+ /* Send request to firmware */
+ ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_MAC_ADDRESS,
+ HOST_ACT_GEN_SET, 0, NULL, true);
+
+ if (ret) {
+ u64_to_ether_addr(old_mac_addr, priv->curr_addr);
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "set mac address failed: ret=%d\n", ret);
+ return ret;
+ }
+
+ eth_hw_addr_set(dev, priv->curr_addr);
+ return 0;
+}
+
+/* ndo_set_mac_address: set MAC via firmware. */
+static int
+nxpwifi_ndo_set_mac_address(struct net_device *dev, void *addr)
+{
+ struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev);
+ struct sockaddr *hw_addr = addr;
+
+ return nxpwifi_set_mac_address(priv, dev, true, hw_addr->sa_data);
+}
+
+/* ndo_set_rx_mode: promisc/allmulti or program multicast. */
+static void nxpwifi_set_multicast_list(struct net_device *dev)
+{
+ struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev);
+ struct nxpwifi_multicast_list mcast_list;
+
+ if (dev->flags & IFF_PROMISC) {
+ mcast_list.mode = NXPWIFI_PROMISC_MODE;
+ } else if (dev->flags & IFF_ALLMULTI ||
+ netdev_mc_count(dev) > NXPWIFI_MAX_MULTICAST_LIST_SIZE) {
+ mcast_list.mode = NXPWIFI_ALL_MULTI_MODE;
+ } else {
+ mcast_list.mode = NXPWIFI_MULTICAST_MODE;
+ mcast_list.num_multicast_addr =
+ nxpwifi_copy_mcast_addr(&mcast_list, dev);
+ }
+ nxpwifi_request_set_multicast_list(priv, &mcast_list);
+}
+
+/* ndo_tx_timeout: account; reset card on threshold. */
+static void
+nxpwifi_tx_timeout(struct net_device *dev, unsigned int txqueue)
+{
+ struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev);
+
+ priv->num_tx_timeout++;
+ priv->tx_timeout_cnt++;
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "%lu : Tx timeout(#%d), bss_type-num = %d-%d\n",
+ jiffies, priv->tx_timeout_cnt, priv->bss_type,
+ priv->bss_num);
+ nxpwifi_set_trans_start(dev);
+
+ if (priv->tx_timeout_cnt > TX_TIMEOUT_THRESHOLD &&
+ priv->adapter->if_ops.card_reset) {
+ nxpwifi_dbg(priv->adapter, ERROR,
+ "tx_timeout_cnt exceeds threshold.\t"
+ "Triggering card reset!\n");
+ priv->adapter->if_ops.card_reset(priv->adapter);
+ }
+}
+
+void nxpwifi_upload_device_dump(struct nxpwifi_adapter *adapter)
+{
+ /*
+ * Dump all the memory data into single file, a userspace script will
+ * be used to split all the memory data to multiple files
+ */
+ nxpwifi_dbg(adapter, MSG,
+ "== nxpwifi dump information to /sys/class/devcoredump start\n");
+ dev_coredumpv(adapter->dev, adapter->devdump_data, adapter->devdump_len,
+ GFP_KERNEL);
+ nxpwifi_dbg(adapter, MSG,
+ "== nxpwifi dump information to /sys/class/devcoredump end\n");
+
+ /*
+ * Device dump data will be freed in device coredump release function
+ * after 5 min. Here reset adapter->devdump_data and ->devdump_len
+ * to avoid it been accidentally reused.
+ */
+ adapter->devdump_data = NULL;
+ adapter->devdump_len = 0;
+}
+EXPORT_SYMBOL_GPL(nxpwifi_upload_device_dump);
+
+void nxpwifi_drv_info_dump(struct nxpwifi_adapter *adapter)
+{
+ char *p;
+ char drv_version[64];
+ struct sdio_mmc_card *sdio_card;
+ struct nxpwifi_private *priv;
+ int i, idx;
+ struct netdev_queue *txq;
+ struct nxpwifi_debug_info *debug_info;
+
+ nxpwifi_dbg(adapter, MSG, "===nxpwifi driverinfo dump start===\n");
+
+ p = adapter->devdump_data;
+ strscpy(p, "========Start dump driverinfo========\n", NXPWIFI_FW_DUMP_SIZE);
+ p += strlen("========Start dump driverinfo========\n");
+ p += sprintf(p, "driver_name = ");
+ p += sprintf(p, "\"nxpwifi\"\n");
+
+ nxpwifi_drv_get_driver_version(adapter, drv_version,
+ sizeof(drv_version) - 1);
+ p += sprintf(p, "driver_version = %s\n", drv_version);
+
+ p += sprintf(p, "tx_pending = %d\n",
+ atomic_read(&adapter->tx_pending));
+ p += sprintf(p, "rx_pending = %d\n",
+ atomic_read(&adapter->rx_pending));
+
+ if (adapter->iface_type == NXPWIFI_SDIO) {
+ sdio_card = (struct sdio_mmc_card *)adapter->card;
+ p += sprintf(p, "\nmp_rd_bitmap=0x%x curr_rd_port=0x%x\n",
+ sdio_card->mp_rd_bitmap, sdio_card->curr_rd_port);
+ p += sprintf(p, "mp_wr_bitmap=0x%x curr_wr_port=0x%x\n",
+ sdio_card->mp_wr_bitmap, sdio_card->curr_wr_port);
+ }
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ if (!adapter->priv[i]->netdev)
+ continue;
+ priv = adapter->priv[i];
+ p += sprintf(p, "\n[interface : \"%s\"]\n",
+ priv->netdev->name);
+ p += sprintf(p, "wmm_tx_pending[0] = %d\n",
+ atomic_read(&priv->wmm_tx_pending[0]));
+ p += sprintf(p, "wmm_tx_pending[1] = %d\n",
+ atomic_read(&priv->wmm_tx_pending[1]));
+ p += sprintf(p, "wmm_tx_pending[2] = %d\n",
+ atomic_read(&priv->wmm_tx_pending[2]));
+ p += sprintf(p, "wmm_tx_pending[3] = %d\n",
+ atomic_read(&priv->wmm_tx_pending[3]));
+ p += sprintf(p, "media_state=\"%s\"\n", !priv->media_connected ?
+ "Disconnected" : "Connected");
+ p += sprintf(p, "carrier %s\n", (netif_carrier_ok(priv->netdev)
+ ? "on" : "off"));
+ for (idx = 0; idx < priv->netdev->num_tx_queues; idx++) {
+ txq = netdev_get_tx_queue(priv->netdev, idx);
+ p += sprintf(p, "tx queue %d:%s ", idx,
+ netif_tx_queue_stopped(txq) ?
+ "stopped" : "started");
+ }
+ p += sprintf(p, "\n%s: num_tx_timeout = %d\n",
+ priv->netdev->name, priv->num_tx_timeout);
+ }
+
+ if (adapter->iface_type == NXPWIFI_SDIO) {
+ p += sprintf(p, "\n=== %s register dump===\n", "SDIO");
+ if (adapter->if_ops.reg_dump)
+ p += adapter->if_ops.reg_dump(adapter, p);
+ }
+ p += sprintf(p, "\n=== more debug information\n");
+ debug_info = kzalloc_obj(*debug_info, GFP_KERNEL);
+ if (debug_info) {
+ for (i = 0; i < adapter->priv_num; i++) {
+ if (!adapter->priv[i]->netdev)
+ continue;
+ priv = adapter->priv[i];
+ nxpwifi_get_debug_info(priv, debug_info);
+ p += nxpwifi_debug_info_to_buffer(priv, p, debug_info);
+ break;
+ }
+ kfree(debug_info);
+ }
+
+ p += sprintf(p, "\n========End dump========\n");
+ nxpwifi_dbg(adapter, MSG, "===nxpwifi driverinfo dump end===\n");
+ adapter->devdump_len = p - (char *)adapter->devdump_data;
+}
+EXPORT_SYMBOL_GPL(nxpwifi_drv_info_dump);
+
+void nxpwifi_prepare_fw_dump_info(struct nxpwifi_adapter *adapter)
+{
+ u8 idx;
+ char *fw_dump_ptr;
+ u32 dump_len = 0;
+
+ for (idx = 0; idx < adapter->num_mem_types; idx++) {
+ struct memory_type_mapping *entry =
+ &adapter->mem_type_mapping_tbl[idx];
+
+ if (entry->mem_ptr) {
+ dump_len += (strlen("========Start dump ") +
+ strlen(entry->mem_name) +
+ strlen("========\n") +
+ (entry->mem_size + 1) +
+ strlen("\n========End dump========\n"));
+ }
+ }
+
+ if (dump_len + 1 + adapter->devdump_len > NXPWIFI_FW_DUMP_SIZE) {
+ /* Realloc in case buffer overflow */
+ fw_dump_ptr = vzalloc(dump_len + 1 + adapter->devdump_len);
+ nxpwifi_dbg(adapter, MSG, "Realloc device dump data.\n");
+ if (!fw_dump_ptr) {
+ vfree(adapter->devdump_data);
+ nxpwifi_dbg(adapter, ERROR,
+ "vzalloc devdump data failure!\n");
+ return;
+ }
+
+ memmove(fw_dump_ptr, adapter->devdump_data,
+ adapter->devdump_len);
+ vfree(adapter->devdump_data);
+ adapter->devdump_data = fw_dump_ptr;
+ }
+
+ fw_dump_ptr = (char *)adapter->devdump_data + adapter->devdump_len;
+
+ for (idx = 0; idx < adapter->num_mem_types; idx++) {
+ struct memory_type_mapping *entry =
+ &adapter->mem_type_mapping_tbl[idx];
+
+ if (entry->mem_ptr) {
+ fw_dump_ptr += sprintf(fw_dump_ptr, "========Start dump ");
+ fw_dump_ptr += sprintf(fw_dump_ptr, "%s", entry->mem_name);
+ fw_dump_ptr += sprintf(fw_dump_ptr, "========\n");
+ memcpy(fw_dump_ptr, entry->mem_ptr, entry->mem_size);
+ fw_dump_ptr += entry->mem_size;
+ fw_dump_ptr += sprintf(fw_dump_ptr, "\n========End dump========\n");
+ }
+ }
+
+ adapter->devdump_len = fw_dump_ptr - (char *)adapter->devdump_data;
+
+ for (idx = 0; idx < adapter->num_mem_types; idx++) {
+ struct memory_type_mapping *entry =
+ &adapter->mem_type_mapping_tbl[idx];
+
+ vfree(entry->mem_ptr);
+ entry->mem_ptr = NULL;
+ entry->mem_size = 0;
+ }
+}
+EXPORT_SYMBOL_GPL(nxpwifi_prepare_fw_dump_info);
+
+/* ndo_get_stats: return netdev stats. */
+static struct net_device_stats *nxpwifi_get_stats(struct net_device *dev)
+{
+ struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev);
+
+ return &priv->stats;
+}
+
+static u16
+nxpwifi_netdev_select_wmm_queue(struct net_device *dev, struct sk_buff *skb,
+ struct net_device *sb_dev)
+{
+ skb->priority = cfg80211_classify8021d(skb, NULL);
+ return nxpwifi_1d_to_wmm_queue[skb->priority];
+}
+
+/* Network device handlers */
+static const struct net_device_ops nxpwifi_netdev_ops = {
+ .ndo_open = nxpwifi_open,
+ .ndo_stop = nxpwifi_close,
+ .ndo_start_xmit = nxpwifi_hard_start_xmit,
+ .ndo_set_mac_address = nxpwifi_ndo_set_mac_address,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_tx_timeout = nxpwifi_tx_timeout,
+ .ndo_get_stats = nxpwifi_get_stats,
+ .ndo_set_rx_mode = nxpwifi_set_multicast_list,
+ .ndo_select_queue = nxpwifi_netdev_select_wmm_queue,
+};
+
+/* Init per-interface defaults: ops, addrs, mgmt IEs, stats. */
+void nxpwifi_init_priv_params(struct nxpwifi_private *priv,
+ struct net_device *dev)
+{
+ dev->netdev_ops = &nxpwifi_netdev_ops;
+ dev->needs_free_netdev = true;
+ /* Initialize private structure */
+ priv->current_key_index = 0;
+ priv->media_connected = false;
+ memset(priv->mgmt_ie, 0,
+ sizeof(struct nxpwifi_ie) * MAX_MGMT_IE_INDEX);
+ priv->beacon_idx = NXPWIFI_AUTO_IDX_MASK;
+ priv->proberesp_idx = NXPWIFI_AUTO_IDX_MASK;
+ priv->assocresp_idx = NXPWIFI_AUTO_IDX_MASK;
+ priv->gen_idx = NXPWIFI_AUTO_IDX_MASK;
+ priv->num_tx_timeout = 0;
+ if (is_valid_ether_addr(dev->dev_addr))
+ ether_addr_copy(priv->curr_addr, dev->dev_addr);
+ else
+ ether_addr_copy(priv->curr_addr, priv->adapter->perm_addr);
+
+ if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA ||
+ GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) {
+ priv->hist_data = kmalloc_obj(*priv->hist_data, GFP_KERNEL);
+ if (priv->hist_data)
+ nxpwifi_hist_data_reset(priv);
+ }
+}
+
+/* Return true if any command is pending. */
+int is_command_pending(struct nxpwifi_adapter *adapter)
+{
+ int is_cmd_pend_q_empty;
+
+ spin_lock_bh(&adapter->cmd_pending_q_lock);
+ is_cmd_pend_q_empty = list_empty(&adapter->cmd_pending_q);
+ spin_unlock_bh(&adapter->cmd_pending_q_lock);
+
+ return !is_cmd_pend_q_empty;
+}
+
+/* Host MLME work: deliver RX; handle assoc/link-loss. */
+static void nxpwifi_host_mlme_work(struct wiphy *wiphy, struct wiphy_work *work)
+{
+ struct nxpwifi_adapter *adapter =
+ container_of(work, struct nxpwifi_adapter, host_mlme_work);
+ struct sk_buff *skb;
+ struct nxpwifi_rxinfo *rx_info;
+ struct nxpwifi_private *priv;
+
+ if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags))
+ return;
+
+ while ((skb = skb_dequeue(&adapter->rx_mlme_q))) {
+ rx_info = NXPWIFI_SKB_RXCB(skb);
+ priv = adapter->priv[rx_info->bss_num];
+ cfg80211_rx_mlme_mgmt(priv->netdev,
+ skb->data,
+ rx_info->pkt_len);
+ }
+
+ /* Check for host mlme disconnection */
+ if (adapter->host_mlme_link_lost) {
+ if (adapter->priv_link_lost) {
+ nxpwifi_reset_connect_state(adapter->priv_link_lost,
+ WLAN_REASON_DEAUTH_LEAVING,
+ true);
+ adapter->priv_link_lost = NULL;
+ }
+ adapter->host_mlme_link_lost = false;
+ }
+
+ /* Check for host mlme Assoc Resp */
+ if (adapter->assoc_resp_received) {
+ nxpwifi_process_assoc_resp(adapter);
+ adapter->assoc_resp_received = false;
+ }
+}
+
+/* RX work: process RX queue. */
+static void nxpwifi_rx_work(struct work_struct *work)
+{
+ struct nxpwifi_adapter *adapter =
+ container_of(work, struct nxpwifi_adapter, rx_work);
+
+ if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags))
+ return;
+ nxpwifi_process_rx(adapter);
+}
+
+/* Main work: run nxpwifi_main_process(). */
+static void nxpwifi_main_work(struct work_struct *work)
+{
+ struct nxpwifi_adapter *adapter =
+ container_of(work, struct nxpwifi_adapter, main_work);
+
+ if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags))
+ return;
+ nxpwifi_main_process(adapter);
+}
+
+/* Teardown: disable IRQs, stop queues, shutdown, remove ifaces, unreg. */
+static void nxpwifi_uninit_sw(struct nxpwifi_adapter *adapter)
+{
+ struct nxpwifi_private *priv;
+ int i;
+
+ /*
+ * We can no longer handle interrupts once we start doing the teardown
+ * below.
+ */
+ if (adapter->if_ops.disable_int)
+ adapter->if_ops.disable_int(adapter);
+
+ set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags);
+ nxpwifi_terminate_workqueue(adapter);
+ adapter->int_status = 0;
+
+ /* Stop data */
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+ if (priv->netdev) {
+ nxpwifi_stop_net_dev_queue(priv->netdev, adapter);
+ netif_carrier_off(priv->netdev);
+ netif_device_detach(priv->netdev);
+ }
+ }
+
+ nxpwifi_dbg(adapter, CMD, "cmd: calling nxpwifi_shutdown_drv...\n");
+ nxpwifi_shutdown_drv(adapter);
+ nxpwifi_dbg(adapter, CMD, "cmd: nxpwifi_shutdown_drv done\n");
+
+ if (atomic_read(&adapter->rx_pending) ||
+ atomic_read(&adapter->tx_pending) ||
+ atomic_read(&adapter->cmd_pending)) {
+ nxpwifi_dbg(adapter, ERROR,
+ "rx_pending=%d, tx_pending=%d,\t"
+ "cmd_pending=%d\n",
+ atomic_read(&adapter->rx_pending),
+ atomic_read(&adapter->tx_pending),
+ atomic_read(&adapter->cmd_pending));
+ }
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ priv = adapter->priv[i];
+ rtnl_lock();
+ if (priv->netdev &&
+ priv->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED) {
+ /*
+ * Close the netdev now, because if we do it later, the
+ * netdev notifiers will need to acquire the wiphy lock
+ * again --> deadlock.
+ */
+ dev_close(priv->wdev.netdev);
+ wiphy_lock(adapter->wiphy);
+ nxpwifi_del_virtual_intf(adapter->wiphy, &priv->wdev);
+ wiphy_unlock(adapter->wiphy);
+ }
+ rtnl_unlock();
+ }
+
+ wiphy_unregister(adapter->wiphy);
+ wiphy_free(adapter->wiphy);
+ adapter->wiphy = NULL;
+
+ vfree(adapter->chan_stats);
+ nxpwifi_free_cmd_buffers(adapter);
+}
+
+/* Shut down SW/FW and mark device down. */
+void nxpwifi_shutdown_sw(struct nxpwifi_adapter *adapter)
+{
+ struct nxpwifi_private *priv;
+
+ if (!adapter)
+ return;
+
+ wait_for_completion(adapter->fw_done);
+ /* Caller should ensure we aren't suspending while this happens */
+ reinit_completion(adapter->fw_done);
+
+ priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY);
+ nxpwifi_deauthenticate(priv, NULL);
+
+ nxpwifi_init_shutdown_fw(priv, NXPWIFI_FUNC_SHUTDOWN);
+
+ nxpwifi_uninit_sw(adapter);
+ adapter->is_up = false;
+}
+EXPORT_SYMBOL_GPL(nxpwifi_shutdown_sw);
+
+/* Re-init adapter SW and bring device up. */
+int
+nxpwifi_reinit_sw(struct nxpwifi_adapter *adapter)
+{
+ int ret = 0;
+
+ nxpwifi_init_lock_list(adapter);
+ if (adapter->if_ops.up_dev)
+ adapter->if_ops.up_dev(adapter);
+
+ adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING;
+ clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags);
+ clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags);
+ adapter->hs_activated = false;
+ clear_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags);
+ init_waitqueue_head(&adapter->hs_activate_wait_q);
+ init_waitqueue_head(&adapter->cmd_wait_q.wait);
+ adapter->cmd_wait_q.status = 0;
+ adapter->scan_wait_q_woken = false;
+
+ if (num_possible_cpus() > 1)
+ adapter->rx_work_enabled = true;
+
+ adapter->workqueue =
+ alloc_workqueue("NXPWIFI_WORK_QUEUE",
+ WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0);
+ if (!adapter->workqueue) {
+ ret = -ENOMEM;
+ goto err_kmalloc;
+ }
+
+ INIT_WORK(&adapter->main_work, nxpwifi_main_work);
+
+ if (adapter->rx_work_enabled) {
+ adapter->rx_workqueue = alloc_workqueue("NXPWIFI_RX_WORK_QUEUE",
+ WQ_HIGHPRI |
+ WQ_MEM_RECLAIM |
+ WQ_UNBOUND, 0);
+ if (!adapter->rx_workqueue) {
+ ret = -ENOMEM;
+ goto err_kmalloc;
+ }
+ INIT_WORK(&adapter->rx_work, nxpwifi_rx_work);
+ }
+
+ wiphy_work_init(&adapter->host_mlme_work, nxpwifi_host_mlme_work);
+
+ /*
+ * Register the device. Fill up the private data structure with
+ * relevant information from the card. Some code extracted from
+ * nxpwifi_register_dev()
+ */
+ nxpwifi_dbg(adapter, INFO, "%s, nxpwifi_init_hw_fw()...\n", __func__);
+
+ ret = nxpwifi_init_hw_fw(adapter, false);
+ if (ret) {
+ nxpwifi_dbg(adapter, ERROR,
+ "%s: firmware init failed\n", __func__);
+ goto err_init_fw;
+ }
+
+ /* _nxpwifi_fw_dpc() does its own cleanup */
+ ret = _nxpwifi_fw_dpc(adapter->firmware, adapter);
+ if (ret) {
+ pr_err("Failed to bring up adapter: %d\n", ret);
+ return ret;
+ }
+ nxpwifi_dbg(adapter, INFO, "%s, successful\n", __func__);
+
+ return ret;
+
+err_init_fw:
+ nxpwifi_dbg(adapter, ERROR, "info: %s: unregister device\n", __func__);
+ if (adapter->if_ops.unregister_dev)
+ adapter->if_ops.unregister_dev(adapter);
+
+err_kmalloc:
+ set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags);
+ nxpwifi_terminate_workqueue(adapter);
+ if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) {
+ nxpwifi_dbg(adapter, ERROR,
+ "info: %s: shutdown nxpwifi\n", __func__);
+ nxpwifi_shutdown_drv(adapter);
+ nxpwifi_free_cmd_buffers(adapter);
+ }
+
+ complete_all(adapter->fw_done);
+ nxpwifi_dbg(adapter, INFO, "%s, error\n", __func__);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(nxpwifi_reinit_sw);
+
+/* Add card: register adapter, workqueues, device; request FW (async). */
+int
+nxpwifi_add_card(void *card, struct completion *fw_done,
+ struct nxpwifi_if_ops *if_ops, u8 iface_type,
+ struct device *dev)
+{
+ struct nxpwifi_adapter *adapter;
+ int ret = 0;
+
+ adapter = nxpwifi_register(card, dev, if_ops);
+ if (IS_ERR(adapter)) {
+ ret = PTR_ERR(adapter);
+ pr_err("%s: adapter register failed %d\n", __func__, ret);
+ goto err_init_sw;
+ }
+
+ adapter->iface_type = iface_type;
+ adapter->fw_done = fw_done;
+
+ adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING;
+ clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags);
+ clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags);
+ adapter->hs_activated = false;
+ init_waitqueue_head(&adapter->hs_activate_wait_q);
+ init_waitqueue_head(&adapter->cmd_wait_q.wait);
+ adapter->cmd_wait_q.status = 0;
+ adapter->scan_wait_q_woken = false;
+
+ if (num_possible_cpus() > 1)
+ adapter->rx_work_enabled = true;
+
+ adapter->workqueue =
+ alloc_workqueue("NXPWIFI_WORK_QUEUE",
+ WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0);
+ if (!adapter->workqueue) {
+ ret = -ENOMEM;
+ goto err_kmalloc;
+ }
+
+ INIT_WORK(&adapter->main_work, nxpwifi_main_work);
+
+ if (adapter->rx_work_enabled) {
+ adapter->rx_workqueue = alloc_workqueue("NXPWIFI_RX_WORK_QUEUE",
+ WQ_HIGHPRI |
+ WQ_MEM_RECLAIM |
+ WQ_UNBOUND, 0);
+ if (!adapter->rx_workqueue) {
+ ret = -ENOMEM;
+ goto err_kmalloc;
+ }
+
+ INIT_WORK(&adapter->rx_work, nxpwifi_rx_work);
+ }
+
+ wiphy_work_init(&adapter->host_mlme_work, nxpwifi_host_mlme_work);
+
+ /*
+ * Register the device. Fill up the private data structure with relevant
+ * information from the card.
+ */
+ ret = adapter->if_ops.register_dev(adapter);
+ if (ret) {
+ pr_err("%s: failed to register nxpwifi device\n", __func__);
+ goto err_registerdev;
+ }
+
+ ret = nxpwifi_init_hw_fw(adapter, true);
+ if (ret) {
+ pr_err("%s: firmware init failed\n", __func__);
+ goto err_init_fw;
+ }
+
+ return ret;
+
+err_init_fw:
+ pr_debug("info: %s: unregister device\n", __func__);
+ if (adapter->if_ops.unregister_dev)
+ adapter->if_ops.unregister_dev(adapter);
+err_registerdev:
+ set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags);
+
+err_kmalloc:
+ nxpwifi_terminate_workqueue(adapter);
+
+ if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) {
+ pr_debug("info: %s: shutdown nxpwifi\n", __func__);
+ nxpwifi_shutdown_drv(adapter);
+ nxpwifi_free_cmd_buffers(adapter);
+ }
+
+ nxpwifi_free_adapter(adapter);
+
+err_init_sw:
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(nxpwifi_add_card);
+
+/* Remove card: teardown SW, unregister device, free adapter. */
+void nxpwifi_remove_card(struct nxpwifi_adapter *adapter)
+{
+ if (!adapter)
+ return;
+
+ if (adapter->is_up)
+ nxpwifi_uninit_sw(adapter);
+
+ /* Unregister device */
+ nxpwifi_dbg(adapter, INFO,
+ "info: unregister device\n");
+ if (adapter->if_ops.unregister_dev)
+ adapter->if_ops.unregister_dev(adapter);
+ /* Free adapter structure */
+ nxpwifi_dbg(adapter, INFO,
+ "info: free adapter\n");
+ nxpwifi_free_adapter(adapter);
+}
+EXPORT_SYMBOL_GPL(nxpwifi_remove_card);
+
+void _nxpwifi_dbg(const struct nxpwifi_adapter *adapter, int mask,
+ const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ if (!(adapter->debug_mask & mask))
+ return;
+
+ va_start(args, fmt);
+
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ if (adapter->dev)
+ dev_info(adapter->dev, "%pV", &vaf);
+ else
+ pr_info("%pV", &vaf);
+
+ va_end(args);
+}
+EXPORT_SYMBOL_GPL(_nxpwifi_dbg);
+
+/* Module init: init debugfs if enabled. */
+static int
+nxpwifi_init_module(void)
+{
+#ifdef CONFIG_DEBUG_FS
+ nxpwifi_debugfs_init();
+#endif
+ return 0;
+}
+
+/* Module exit: remove debugfs if enabled. */
+static void
+nxpwifi_cleanup_module(void)
+{
+#ifdef CONFIG_DEBUG_FS
+ nxpwifi_debugfs_remove();
+#endif
+}
+
+module_init(nxpwifi_init_module);
+module_exit(nxpwifi_cleanup_module);
+
+MODULE_AUTHOR("NXP International Ltd.");
+MODULE_DESCRIPTION("NXP WiFi Driver version " VERSION);
+MODULE_VERSION(VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/nxp/nxpwifi/main.h b/drivers/net/wireless/nxp/nxpwifi/main.h
new file mode 100644
index 000000000000..565e5528a614
--- /dev/null
+++ b/drivers/net/wireless/nxp/nxpwifi/main.h
@@ -0,0 +1,1427 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * nxpwifi: main data structures and prototypes
+ *
+ * Copyright 2011-2024 NXP
+ */
+
+#ifndef _NXPWIFI_MAIN_H_
+#define _NXPWIFI_MAIN_H_
+
+#include <linux/completion.h>
+#include <linux/kernel.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/etherdevice.h>
+#include <net/sock.h>
+#include <linux/vmalloc.h>
+#include <linux/firmware.h>
+#include <linux/ctype.h>
+#include <linux/of.h>
+#include <linux/xarray.h>
+#include <linux/inetdevice.h>
+#include <linux/devcoredump.h>
+#include <linux/err.h>
+#include <linux/gfp.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/of_irq.h>
+#include <linux/workqueue.h>
+#include <net/ieee80211_radiotap.h>
+
+#include "cfg.h"
+#include "util.h"
+#include "fw.h"
+#include "sdio.h"
+
+extern char driver_version[];
+
+struct nxpwifi_adapter;
+struct nxpwifi_private;
+
+/* command type */
+enum {
+ NXPWIFI_ASYNC_CMD,
+ NXPWIFI_SYNC_CMD
+};
+
+#define NXPWIFI_MAX_AP 64
+
+#define NXPWIFI_MAX_PKTS_TXQ 16
+
+#define NXPWIFI_DEFAULT_WATCHDOG_TIMEOUT (5 * HZ)
+
+#define NXPWIFI_TIMER_10S 10000
+#define NXPWIFI_TIMER_1S 1000
+
+#define MAX_TX_PENDING 400
+#define LOW_TX_PENDING 380
+
+#define HIGH_RX_PENDING 50
+#define LOW_RX_PENDING 20
+
+#define NXPWIFI_UPLD_SIZE (2312)
+
+#define MAX_EVENT_SIZE 2048
+
+#define NXPWIFI_FW_DUMP_SIZE (2 * 1024 * 1024)
+
+#define ARP_FILTER_MAX_BUF_SIZE 68
+
+#define NXPWIFI_KEY_BUFFER_SIZE 16
+#define NXPWIFI_DEFAULT_LISTEN_INTERVAL 10
+#define NXPWIFI_MAX_REGION_CODE 9
+
+#define DEFAULT_BCN_AVG_FACTOR 8
+#define DEFAULT_DATA_AVG_FACTOR 8
+
+#define FIRST_VALID_CHANNEL 0xff
+
+#define DEFAULT_BCN_MISS_TIMEOUT 5
+
+#define MAX_SCAN_BEACON_BUFFER 8000
+
+#define SCAN_BEACON_ENTRY_PAD 6
+
+#define NXPWIFI_PASSIVE_SCAN_CHAN_TIME 110
+#define NXPWIFI_ACTIVE_SCAN_CHAN_TIME 40
+#define NXPWIFI_SPECIFIC_SCAN_CHAN_TIME 40
+#define NXPWIFI_DEF_SCAN_CHAN_GAP_TIME 50
+
+#define SCAN_RSSI(RSSI) (0x100 - ((u8)(RSSI)))
+
+#define NXPWIFI_MAX_TOTAL_SCAN_TIME (NXPWIFI_TIMER_10S - NXPWIFI_TIMER_1S)
+
+#define WPA_GTK_OUI_OFFSET 2
+#define RSN_GTK_OUI_OFFSET 2
+
+#define NXPWIFI_OUI_NOT_PRESENT 0
+#define NXPWIFI_OUI_PRESENT 1
+
+#define PKT_TYPE_MGMT 0xE5
+#define PKT_TYPE_802DOT11 0x05
+/* check if any data / resp / event is received from card */
+#define IS_CARD_RX_RCVD(adapter) ({ \
+ typeof(adapter) (_adapter) = adapter; \
+ ((_adapter)->cmd_resp_received || \
+ (_adapter)->event_received || \
+ (_adapter)->data_received); \
+ })
+
+#define NXPWIFI_TYPE_DATA 0
+#define NXPWIFI_TYPE_CMD 1
+#define NXPWIFI_TYPE_EVENT 3
+#define NXPWIFI_TYPE_VDLL 4
+#define NXPWIFI_TYPE_AGGR_DATA 10
+
+#define MAX_BITMAP_RATES_SIZE 18
+
+#define MAX_CHANNEL_BAND_BG 14
+#define MAX_CHANNEL_BAND_A 165
+
+#define MAX_FREQUENCY_BAND_BG 2484
+
+#define NXPWIFI_EVENT_HEADER_LEN 4
+#define NXPWIFI_UAP_EVENT_EXTRA_HEADER 2
+
+#define NXPWIFI_TYPE_LEN 4
+#define NXPWIFI_USB_TYPE_CMD 0xF00DFACE
+#define NXPWIFI_USB_TYPE_DATA 0xBEADC0DE
+#define NXPWIFI_USB_TYPE_EVENT 0xBEEFFACE
+
+/* tx_timeout threshold to trigger card reset */
+#define TX_TIMEOUT_THRESHOLD 6
+
+#define NXPWIFI_DRV_INFO_SIZE_MAX 0x40000
+
+/* address alignment helper */
+#define NXPWIFI_ALIGN_ADDR(p, a) ({ \
+ typeof(a) (_a) = a; \
+ (((long)(p) + (_a) - 1) & ~((_a) - 1)); \
+ })
+
+#define NXPWIFI_MAC_LOCAL_ADMIN_BIT 41
+
+/* bit helper */
+#define MBIT(x) (((u32)1) << (x))
+
+/* enum nxpwifi_debug_level - nxp wifi debug level */
+enum NXPWIFI_DEBUG_LEVEL {
+ NXPWIFI_DBG_MSG = 0x00000001,
+ NXPWIFI_DBG_FATAL = 0x00000002,
+ NXPWIFI_DBG_ERROR = 0x00000004,
+ NXPWIFI_DBG_DATA = 0x00000008,
+ NXPWIFI_DBG_CMD = 0x00000010,
+ NXPWIFI_DBG_EVENT = 0x00000020,
+ NXPWIFI_DBG_INTR = 0x00000040,
+ NXPWIFI_DBG_IOCTL = 0x00000080,
+ NXPWIFI_DBG_MPA_D = 0x00008000,
+ NXPWIFI_DBG_DAT_D = 0x00010000,
+ NXPWIFI_DBG_CMD_D = 0x00020000,
+ NXPWIFI_DBG_EVT_D = 0x00040000,
+ NXPWIFI_DBG_FW_D = 0x00080000,
+ NXPWIFI_DBG_IF_D = 0x00100000,
+ NXPWIFI_DBG_ENTRY = 0x10000000,
+ NXPWIFI_DBG_WARN = 0x20000000,
+ NXPWIFI_DBG_INFO = 0x40000000,
+ NXPWIFI_DBG_DUMP = 0x80000000,
+ NXPWIFI_DBG_ANY = 0xffffffff
+};
+
+#define NXPWIFI_DEFAULT_DEBUG_MASK (NXPWIFI_DBG_MSG | \
+ NXPWIFI_DBG_FATAL | \
+ NXPWIFI_DBG_ERROR)
+
+__printf(3, 4)
+void _nxpwifi_dbg(const struct nxpwifi_adapter *adapter, int mask,
+ const char *fmt, ...);
+#define nxpwifi_dbg(adapter, mask, fmt, ...) \
+ _nxpwifi_dbg(adapter, NXPWIFI_DBG_##mask, fmt, ##__VA_ARGS__)
+
+#define DEBUG_DUMP_DATA_MAX_LEN 128
+#define nxpwifi_dbg_dump(adapter, dbg_mask, str, buf, len) \
+do { \
+ if ((adapter)->debug_mask & NXPWIFI_DBG_##dbg_mask) \
+ print_hex_dump(KERN_DEBUG, str, \
+ DUMP_PREFIX_OFFSET, 16, 1, \
+ buf, len, false); \
+} while (0)
+
+/* Min BGSCAN interval 15 second */
+#define NXPWIFI_BGSCAN_INTERVAL 15000
+/* bgscan interval (ms) and default repeat count */
+#define NXPWIFI_BGSCAN_REPEAT_COUNT 6
+
+struct nxpwifi_dbg {
+ u32 num_cmd_host_to_card_failure;
+ u32 num_cmd_sleep_cfm_host_to_card_failure;
+ u32 num_tx_host_to_card_failure;
+ u32 num_event_deauth;
+ u32 num_event_disassoc;
+ u32 num_event_link_lost;
+ u32 num_cmd_deauth;
+ u32 num_cmd_assoc_success;
+ u32 num_cmd_assoc_failure;
+ u32 num_tx_timeout;
+ u16 timeout_cmd_id;
+ u16 timeout_cmd_act;
+ u16 last_cmd_id[DBG_CMD_NUM];
+ u16 last_cmd_act[DBG_CMD_NUM];
+ u16 last_cmd_index;
+ u16 last_cmd_resp_id[DBG_CMD_NUM];
+ u16 last_cmd_resp_index;
+ u16 last_event[DBG_CMD_NUM];
+ u16 last_event_index;
+ u32 last_mp_wr_bitmap[NXPWIFI_DBG_SDIO_MP_NUM];
+ u32 last_mp_wr_ports[NXPWIFI_DBG_SDIO_MP_NUM];
+ u32 last_mp_wr_len[NXPWIFI_DBG_SDIO_MP_NUM];
+ u32 last_mp_curr_wr_port[NXPWIFI_DBG_SDIO_MP_NUM];
+ u8 last_sdio_mp_index;
+};
+
+enum NXPWIFI_HARDWARE_STATUS {
+ NXPWIFI_HW_STATUS_READY,
+ NXPWIFI_HW_STATUS_INITIALIZING,
+ NXPWIFI_HW_STATUS_RESET,
+ NXPWIFI_HW_STATUS_NOT_READY
+};
+
+enum NXPWIFI_802_11_POWER_MODE {
+ NXPWIFI_802_11_POWER_MODE_CAM,
+ NXPWIFI_802_11_POWER_MODE_PSP
+};
+
+struct nxpwifi_tx_param {
+ u32 next_pkt_len;
+};
+
+enum NXPWIFI_PS_STATE {
+ PS_STATE_AWAKE,
+ PS_STATE_PRE_SLEEP,
+ PS_STATE_SLEEP_CFM,
+ PS_STATE_SLEEP
+};
+
+enum nxpwifi_iface_type {
+ NXPWIFI_SDIO
+};
+
+struct nxpwifi_add_ba_param {
+ u32 tx_win_size;
+ u32 rx_win_size;
+ u32 timeout;
+ u8 tx_amsdu;
+ u8 rx_amsdu;
+};
+
+struct nxpwifi_tx_aggr {
+ u8 ampdu_user;
+ u8 ampdu_ap;
+ u8 amsdu;
+};
+
+enum nxpwifi_ba_status {
+ BA_SETUP_NONE = 0,
+ BA_SETUP_INPROGRESS,
+ BA_SETUP_COMPLETE
+};
+
+struct nxpwifi_ra_list_tbl {
+ struct list_head list;
+ struct sk_buff_head skb_head;
+ u8 ra[ETH_ALEN];
+ u32 is_11n_enabled;
+ u16 max_amsdu;
+ u16 ba_pkt_count;
+ u8 ba_packet_thr;
+ enum nxpwifi_ba_status ba_status;
+ u8 amsdu_in_ampdu;
+ u16 total_pkt_count;
+ bool tx_paused;
+};
+
+struct nxpwifi_tid_tbl {
+ struct list_head ra_list;
+};
+
+#define WMM_HIGHEST_PRIORITY 7
+#define HIGH_PRIO_TID 7
+#define LOW_PRIO_TID 0
+#define NO_PKT_PRIO_TID -1
+#define NXPWIFI_WMM_DRV_DELAY_MAX 510
+
+struct nxpwifi_wmm_desc {
+ struct nxpwifi_tid_tbl tid_tbl_ptr[MAX_NUM_TID];
+ u32 packets_out[MAX_NUM_TID];
+ u32 pkts_paused[MAX_NUM_TID];
+ /* protects ra_list */
+ spinlock_t ra_list_spinlock;
+ struct nxpwifi_wmm_ac_status ac_status[IEEE80211_NUM_ACS];
+ enum nxpwifi_wmm_ac_e ac_down_graded_vals[IEEE80211_NUM_ACS];
+ u32 drv_pkt_delay_max;
+ u8 queue_priority[IEEE80211_NUM_ACS];
+ u32 user_pri_pkt_tx_ctrl[WMM_HIGHEST_PRIORITY + 1]; /* UP: 0 to 7 */
+ /* number of queued TX packets */
+ atomic_t tx_pkts_queued;
+ /* highest priority currently queued */
+ atomic_t highest_queued_prio;
+};
+
+struct nxpwifi_802_11_security {
+ u8 wpa_enabled;
+ u8 wpa2_enabled;
+ u8 wep_enabled;
+ u32 authentication_mode;
+ u8 is_authtype_auto;
+ u32 encryption_mode;
+};
+
+struct ieee_types_vendor_specific {
+ struct ieee80211_vendor_ie vend_hdr;
+ u8 data[IEEE_MAX_IE_SIZE - sizeof(struct ieee80211_vendor_ie)];
+} __packed;
+
+struct nxpwifi_bssdescriptor {
+ u8 mac_address[ETH_ALEN];
+ struct cfg80211_ssid ssid;
+ u32 privacy;
+ s32 rssi;
+ u32 channel;
+ u32 freq;
+ u16 beacon_period;
+ u8 erp_flags;
+ u32 bss_mode;
+ u8 supported_rates[NXPWIFI_SUPPORTED_RATES];
+ u8 data_rates[NXPWIFI_SUPPORTED_RATES];
+ u16 bss_band;
+ u64 fw_tsf;
+ u64 timestamp;
+ union ieee_types_phy_param_set phy_param_set;
+ struct ieee_types_cf_param_set cf_param_set;
+ u16 cap_info_bitmap;
+ struct ieee80211_wmm_param_ie wmm_ie;
+ u8 disable_11n;
+ struct ieee80211_ht_cap *bcn_ht_cap;
+ u16 ht_cap_offset;
+ struct ieee80211_ht_operation *bcn_ht_oper;
+ u16 ht_info_offset;
+ u8 *bcn_bss_co_2040;
+ u16 bss_co_2040_offset;
+ u8 *bcn_ext_cap;
+ u16 ext_cap_offset;
+ struct ieee80211_vht_cap *bcn_vht_cap;
+ u16 vht_cap_offset;
+ struct ieee80211_vht_operation *bcn_vht_oper;
+ u16 vht_info_offset;
+ struct ieee_types_oper_mode_ntf *oper_mode;
+ u16 oper_mode_offset;
+ u8 disable_11ac;
+ struct ieee80211_he_cap_elem *bcn_he_cap;
+ u16 he_cap_offset;
+ struct ieee80211_he_operation *bcn_he_oper;
+ u16 he_info_offset;
+ u8 disable_11ax;
+ struct ieee_types_vendor_specific *bcn_wpa_ie;
+ u16 wpa_offset;
+ struct element *bcn_rsn_ie;
+ u16 rsn_offset;
+ struct element *bcn_rsnx_ie;
+ u16 rsnx_offset;
+ u8 *beacon_buf;
+ u32 beacon_buf_size;
+ u8 sensed_11h;
+ u8 local_constraint;
+ u8 chan_sw_ie_present;
+};
+
+struct nxpwifi_current_bss_params {
+ struct nxpwifi_bssdescriptor bss_descriptor;
+ bool wmm_enabled;
+ bool wmm_uapsd_enabled;
+ u8 band;
+ u32 num_of_rates;
+ u8 data_rates[NXPWIFI_SUPPORTED_RATES];
+};
+
+struct nxpwifi_sleep_period {
+ u16 period;
+ u16 reserved;
+};
+
+struct nxpwifi_wep_key {
+ u32 length;
+ u32 key_index;
+ u32 key_length;
+ u8 key_material[NXPWIFI_KEY_BUFFER_SIZE];
+};
+
+#define MAX_REGION_CHANNEL_NUM 2
+
+struct nxpwifi_chan_freq_power {
+ u16 channel;
+ u32 freq;
+ u16 max_tx_power;
+ u8 unsupported;
+};
+
+enum state_11d_t {
+ DISABLE_11D = 0,
+ ENABLE_11D = 1,
+};
+
+#define NXPWIFI_MAX_TRIPLET_802_11D 83
+
+struct nxpwifi_802_11d_domain_reg {
+ u8 dfs_region;
+ u8 country_code[IEEE80211_COUNTRY_STRING_LEN];
+ u8 no_of_triplet;
+ struct ieee80211_country_ie_triplet
+ triplet[NXPWIFI_MAX_TRIPLET_802_11D];
+};
+
+struct nxpwifi_vendor_spec_cfg_ie {
+ u16 mask;
+ u16 flag;
+ u8 ie[NXPWIFI_MAX_VSIE_LEN];
+};
+
+struct wps {
+ u8 session_enable;
+};
+
+struct nxpwifi_roc_cfg {
+ u64 cookie;
+ struct ieee80211_channel chan;
+};
+
+enum nxpwifi_iface_work_flags {
+ NXPWIFI_IFACE_WORK_DEVICE_DUMP,
+ NXPWIFI_IFACE_WORK_CARD_RESET,
+};
+
+enum nxpwifi_adapter_work_flags {
+ NXPWIFI_SURPRISE_REMOVED,
+ NXPWIFI_IS_CMD_TIMEDOUT,
+ NXPWIFI_IS_SUSPENDED,
+ NXPWIFI_IS_HS_CONFIGURED,
+ NXPWIFI_IS_HS_ENABLING,
+ NXPWIFI_IS_REQUESTING_FW_VEREXT,
+};
+
+struct nxpwifi_band_config {
+ u8 chan_band:2;
+ u8 chan_width:2;
+ u8 chan2_offset:2;
+ u8 scan_mode:2;
+} __packed;
+
+struct nxpwifi_channel_band {
+ struct nxpwifi_band_config band_config;
+ u8 channel;
+};
+
+struct nxpwifi_private {
+ struct nxpwifi_adapter *adapter;
+ u8 bss_type;
+ u8 bss_role;
+ u8 bss_priority;
+ u8 bss_num;
+ u8 bss_started;
+ u8 auth_flag;
+ u16 auth_alg;
+ u8 frame_type;
+ u8 curr_addr[ETH_ALEN];
+ u8 media_connected;
+ u8 port_open;
+ u8 usb_port;
+ u32 num_tx_timeout;
+ /* track consecutive timeout */
+ u8 tx_timeout_cnt;
+ struct net_device *netdev;
+ struct net_device_stats stats;
+ u32 curr_pkt_filter;
+ u32 bss_mode;
+ u32 pkt_tx_ctrl;
+ u16 tx_power_level;
+ u8 max_tx_power_level;
+ u8 min_tx_power_level;
+ u32 tx_ant;
+ u32 rx_ant;
+ u8 tx_rate;
+ u8 tx_htinfo;
+ u8 rxpd_htinfo;
+ u8 rxpd_rate;
+ u16 rate_bitmap;
+ u16 bitmap_rates[MAX_BITMAP_RATES_SIZE];
+ u32 data_rate;
+ u8 is_data_rate_auto;
+ u16 bcn_avg_factor;
+ u16 data_avg_factor;
+ s16 data_rssi_last;
+ s16 data_nf_last;
+ s16 data_rssi_avg;
+ s16 data_nf_avg;
+ s16 bcn_rssi_last;
+ s16 bcn_nf_last;
+ s16 bcn_rssi_avg;
+ s16 bcn_nf_avg;
+ struct nxpwifi_bssdescriptor *attempted_bss_desc;
+ struct cfg80211_ssid prev_ssid;
+ u8 prev_bssid[ETH_ALEN];
+ struct nxpwifi_current_bss_params curr_bss_params;
+ u16 beacon_period;
+ u8 dtim_period;
+ u16 listen_interval;
+ u16 atim_window;
+ struct nxpwifi_802_11_security sec_info;
+ struct nxpwifi_wep_key wep_key[NUM_WEP_KEYS];
+ u16 wep_key_curr_index;
+ u8 wpa_ie[256];
+ u16 wpa_ie_len;
+ u8 wpa_is_gtk_set;
+ struct host_cmd_ds_802_11_key_material aes_key;
+ u8 *wps_ie;
+ u16 wps_ie_len;
+ u8 wmm_required;
+ bool wmm_enabled;
+ u8 wmm_qosinfo;
+ struct nxpwifi_wmm_desc wmm;
+ atomic_t wmm_tx_pending[IEEE80211_NUM_ACS];
+ struct list_head sta_list;
+ /* spin lock for associated station list */
+ spinlock_t sta_list_spinlock;
+ struct list_head tx_ba_stream_tbl_ptr[MAX_NUM_TID];
+ /* spin lock for tx_ba_stream_tbl_ptr queue */
+ struct spinlock tx_ba_stream_tbl_lock[MAX_NUM_TID];
+ struct nxpwifi_tx_aggr aggr_prio_tbl[MAX_NUM_TID];
+ struct nxpwifi_add_ba_param add_ba_param;
+ u16 rx_seq[MAX_NUM_TID];
+ u8 tos_to_tid_inv[MAX_NUM_TID];
+ struct list_head rx_reorder_tbl_ptr[MAX_NUM_TID];
+ /* spin lock for rx_reorder_tbl_ptr queue */
+ struct spinlock rx_reorder_tbl_lock[MAX_NUM_TID];
+#define NXPWIFI_ASSOC_RSP_BUF_SIZE 500
+ u8 assoc_rsp_buf[NXPWIFI_ASSOC_RSP_BUF_SIZE];
+ u32 assoc_rsp_size;
+ struct cfg80211_bss *req_bss;
+
+#define NXPWIFI_GENIE_BUF_SIZE 256
+ u8 gen_ie_buf[NXPWIFI_GENIE_BUF_SIZE];
+ u8 gen_ie_buf_len;
+
+ struct nxpwifi_vendor_spec_cfg_ie vs_ie[NXPWIFI_MAX_VSIE_NUM];
+
+#define NXPWIFI_ASSOC_TLV_BUF_SIZE 256
+ u8 assoc_tlv_buf[NXPWIFI_ASSOC_TLV_BUF_SIZE];
+ u8 assoc_tlv_buf_len;
+
+ u8 *curr_bcn_buf;
+ u32 curr_bcn_size;
+ /* spin lock for beacon buffer */
+ spinlock_t curr_bcn_buf_lock;
+ struct wireless_dev wdev;
+ struct nxpwifi_chan_freq_power cfp;
+ u32 versionstrsel;
+ char version_str[NXPWIFI_VERSION_STR_LENGTH];
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dfs_dev_dir;
+#endif
+ u16 current_key_index;
+ struct cfg80211_scan_request *scan_request;
+ u8 cfg_bssid[6];
+ struct wps wps;
+ u8 scan_block;
+ s32 cqm_rssi_thold;
+ u32 cqm_rssi_hyst;
+ u8 subsc_evt_rssi_state;
+ struct nxpwifi_ds_misc_subsc_evt async_subsc_evt_storage;
+ struct nxpwifi_ie mgmt_ie[MAX_MGMT_IE_INDEX];
+ u16 beacon_idx;
+ u16 proberesp_idx;
+ u16 assocresp_idx;
+ u16 gen_idx;
+ u8 ap_11n_enabled;
+ u8 ap_11ac_enabled;
+ u8 ap_11ax_enabled;
+ u16 config_bands;
+ /* 11AX */
+ u8 user_he_cap_len;
+ u8 user_he_cap[HE_CAP_MAX_SIZE];
+ u8 user_2g_he_cap_len;
+ u8 user_2g_he_cap[HE_CAP_MAX_SIZE];
+ bool host_mlme_reg;
+ u32 mgmt_frame_mask;
+ struct nxpwifi_roc_cfg roc_cfg;
+ bool scan_aborting;
+ u8 sched_scanning;
+ u8 csa_chan;
+ unsigned long csa_expire_time;
+ u8 del_list_idx;
+ bool hs2_enabled;
+ struct nxpwifi_uap_bss_param bss_cfg;
+ struct cfg80211_chan_def bss_chandef;
+ struct station_parameters *sta_params;
+ struct xarray ack_status_frames;
+ /* spin lock for ack status */
+ spinlock_t ack_status_lock;
+ /** rx histogram data */
+ struct nxpwifi_histogram_data *hist_data;
+ struct cfg80211_chan_def dfs_chandef;
+ struct wiphy_work reset_conn_state_work;
+ struct wiphy_delayed_work dfs_cac_work;
+ struct wiphy_delayed_work dfs_chan_sw_work;
+ bool uap_stop_tx;
+ struct cfg80211_ap_update ap_update_info;
+ struct nxpwifi_11h_intf_state state_11h;
+ struct nxpwifi_ds_mem_rw mem_rw;
+ struct sk_buff_head bypass_txq;
+ struct nxpwifi_user_scan_chan hidden_chan[NXPWIFI_USER_SCAN_CHAN_MAX];
+ u8 assoc_resp_ht_param;
+ bool ht_param_present;
+ u16 last_deauth_reason;
+};
+
+struct nxpwifi_tx_ba_stream_tbl {
+ struct list_head list;
+ struct rcu_head rcu;
+ int tid;
+ u8 ra[ETH_ALEN];
+ enum nxpwifi_ba_status ba_status;
+ u8 amsdu;
+};
+
+struct nxpwifi_rx_reorder_tbl;
+
+struct reorder_tmr_cnxt {
+ struct timer_list timer;
+ struct nxpwifi_rx_reorder_tbl *ptr;
+ struct nxpwifi_private *priv;
+ u8 timer_is_set;
+};
+
+struct nxpwifi_rx_reorder_tbl {
+ struct list_head list;
+ struct list_head tmp_list;
+ struct rcu_head rcu;
+ int tid;
+ u8 ta[ETH_ALEN];
+ int init_win;
+ int start_win;
+ int win_size;
+ void **rx_reorder_ptr;
+ struct reorder_tmr_cnxt timer_context;
+ u8 amsdu;
+ u8 flags;
+};
+
+struct nxpwifi_bss_prio_node {
+ struct list_head list;
+ struct nxpwifi_private *priv;
+};
+
+struct nxpwifi_bss_prio_tbl {
+ struct list_head bss_prio_head;
+ spinlock_t bss_prio_lock; /* protects BSS priority */
+ struct nxpwifi_bss_prio_node *bss_prio_cur;
+};
+
+struct cmd_ctrl_node {
+ struct list_head list;
+ struct nxpwifi_private *priv;
+ u32 cmd_no;
+ u32 cmd_flag;
+ struct sk_buff *cmd_skb;
+ struct sk_buff *resp_skb;
+ void *data_buf;
+ u32 wait_q_enabled;
+ struct sk_buff *skb;
+ u8 *condition;
+ u8 cmd_wait_q_woken;
+ int (*cmd_resp)(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp,
+ u16 cmdresp_no,
+ void *data_buf);
+};
+
+struct nxpwifi_bss_priv {
+ u16 band;
+ u64 fw_tsf;
+};
+
+struct nxpwifi_station_stats {
+ u64 last_rx;
+ s8 rssi;
+ u64 rx_bytes;
+ u64 tx_bytes;
+ u32 rx_packets;
+ u32 tx_packets;
+ u32 tx_failed;
+ u8 last_tx_rate;
+ u8 last_tx_htinfo;
+};
+
+/*AP - side structure tracking associated STA info */
+struct nxpwifi_sta_node {
+ struct list_head list;
+ struct rcu_head rcu;
+ u8 mac_addr[ETH_ALEN];
+ u8 is_wmm_enabled;
+ u8 is_11n_enabled;
+ u8 is_11ac_enabled;
+ u8 is_11ax_enabled;
+ u8 ampdu_sta[MAX_NUM_TID];
+ u16 rx_seq[MAX_NUM_TID];
+ u16 max_amsdu;
+ struct nxpwifi_station_stats stats;
+ u8 tx_pause;
+};
+
+#define NXPWIFI_TYPE_AGGR_DATA_V2 11
+#define NXPWIFI_BUS_AGGR_MODE_LEN_V2 (2)
+#define NXPWIFI_BUS_AGGR_MAX_LEN 16000
+#define NXPWIFI_BUS_AGGR_MAX_NUM 10
+struct bus_aggr_params {
+ u16 enable;
+ u16 mode;
+ u16 tx_aggr_max_size;
+ u16 tx_aggr_max_num;
+ u16 tx_aggr_align;
+};
+
+struct vdll_dnld_ctrl {
+ u8 *pending_block;
+ u16 pending_block_len;
+ u8 *vdll_mem;
+ u32 vdll_len;
+ struct sk_buff *skb;
+};
+
+struct nxpwifi_if_ops {
+ int (*init_if)(struct nxpwifi_adapter *adapter);
+ void (*cleanup_if)(struct nxpwifi_adapter *adapter);
+ int (*check_fw_status)(struct nxpwifi_adapter *adapter, u32 poll_num);
+ int (*check_winner_status)(struct nxpwifi_adapter *adapter);
+ int (*prog_fw)(struct nxpwifi_adapter *adapter,
+ struct nxpwifi_fw_image *fw);
+ int (*register_dev)(struct nxpwifi_adapter *adapter);
+ void (*unregister_dev)(struct nxpwifi_adapter *adapter);
+ int (*enable_int)(struct nxpwifi_adapter *adapter);
+ void (*disable_int)(struct nxpwifi_adapter *adapter);
+ int (*process_int_status)(struct nxpwifi_adapter *adapter, u8 istat);
+ int (*host_to_card)(struct nxpwifi_adapter *adapter, u8 type,
+ struct sk_buff *skb,
+ struct nxpwifi_tx_param *tx_param);
+ int (*wakeup)(struct nxpwifi_adapter *adapter);
+ int (*wakeup_complete)(struct nxpwifi_adapter *adapter);
+
+ /* interface-specific operations */
+ void (*update_mp_end_port)(struct nxpwifi_adapter *adapter, u16 port);
+ void (*cleanup_mpa_buf)(struct nxpwifi_adapter *adapter);
+ int (*cmdrsp_complete)(struct nxpwifi_adapter *adapter,
+ struct sk_buff *skb);
+ int (*event_complete)(struct nxpwifi_adapter *adapter,
+ struct sk_buff *skb);
+ int (*dnld_fw)(struct nxpwifi_adapter *adapter,
+ struct nxpwifi_fw_image *fw);
+ void (*card_reset)(struct nxpwifi_adapter *adapter);
+ int (*reg_dump)(struct nxpwifi_adapter *adapter, char *drv_buf);
+ void (*device_dump)(struct nxpwifi_adapter *adapter);
+ void (*deaggr_pkt)(struct nxpwifi_adapter *adapter,
+ struct sk_buff *skb);
+ void (*up_dev)(struct nxpwifi_adapter *adapter);
+};
+
+struct nxpwifi_adapter {
+ u8 iface_type;
+ unsigned int debug_mask;
+ struct nxpwifi_iface_comb iface_limit;
+ struct nxpwifi_iface_comb curr_iface_comb;
+ struct nxpwifi_private *priv[NXPWIFI_MAX_BSS_NUM];
+ u8 priv_num;
+ const struct firmware *firmware;
+ char fw_name[32];
+ int winner;
+ struct device *dev;
+ struct wiphy *wiphy;
+ u8 perm_addr[ETH_ALEN];
+ unsigned long work_flags;
+ u32 fw_release_number;
+ u8 intf_hdr_len;
+ void *card;
+ struct nxpwifi_if_ops if_ops;
+ atomic_t bypass_tx_pending;
+ atomic_t rx_pending;
+ atomic_t tx_pending;
+ atomic_t cmd_pending;
+ atomic_t tx_hw_pending;
+ struct workqueue_struct *workqueue;
+ struct work_struct main_work;
+ struct workqueue_struct *rx_workqueue;
+ struct work_struct rx_work;
+ struct wiphy_work host_mlme_work;
+ bool rx_work_enabled;
+ bool rx_processing;
+ bool delay_main_work;
+ atomic_t rx_ba_teardown_pending;
+ atomic_t iface_changing;
+ struct nxpwifi_bss_prio_tbl bss_prio_tbl[NXPWIFI_MAX_BSS_NUM];
+ u32 nxpwifi_processing;
+ u16 tx_buf_size;
+ u16 curr_tx_buf_size;
+ /* SDIO single port rx aggregation capability */
+ bool host_disable_sdio_rx_aggr;
+ bool sdio_rx_aggr_enable;
+ u16 sdio_rx_block_size;
+ u32 ioport;
+ enum NXPWIFI_HARDWARE_STATUS hw_status;
+ u16 number_of_antenna;
+ u32 fw_cap_info;
+ u32 fw_cap_ext;
+ u16 user_htstream;
+ u64 uuid_lo;
+ u64 uuid_hi;
+ /* interrupt lock */
+ spinlock_t int_lock;
+ u8 int_status;
+ u32 event_cause;
+ struct sk_buff *event_skb;
+ u8 upld_buf[NXPWIFI_UPLD_SIZE];
+ u8 data_sent;
+ u8 cmd_sent;
+ u8 cmd_resp_received;
+ bool event_received;
+ u8 data_received;
+ u8 assoc_resp_received;
+ struct nxpwifi_private *priv_link_lost;
+ u8 host_mlme_link_lost;
+ u16 seq_num;
+ struct cmd_ctrl_node *cmd_pool;
+ struct cmd_ctrl_node *curr_cmd;
+ /* spin lock for command */
+ spinlock_t nxpwifi_cmd_lock;
+ struct timer_list cmd_timer;
+ struct list_head cmd_free_q;
+ spinlock_t cmd_free_q_lock; /* protects cmd_free_q */
+ struct list_head cmd_pending_q;
+ spinlock_t cmd_pending_q_lock; /* protects cmd_pending_q */
+ struct list_head scan_pending_q;
+ spinlock_t scan_pending_q_lock; /* protects scan_pending_q */
+ struct sk_buff_head tx_data_q;
+ atomic_t tx_queued;
+ u32 scan_processing;
+ u16 region_code;
+ struct nxpwifi_802_11d_domain_reg domain_reg;
+ u16 scan_probes;
+ u32 scan_mode;
+ u16 specific_scan_time;
+ u16 active_scan_time;
+ u16 passive_scan_time;
+ u16 scan_chan_gap_time;
+ u16 fw_bands;
+ u8 tx_lock_flag;
+ struct nxpwifi_sleep_period sleep_period;
+ u16 ps_mode;
+ u32 ps_state;
+ u8 need_to_wakeup;
+ u16 multiple_dtim;
+ u16 local_listen_interval;
+ u16 null_pkt_interval;
+ struct sk_buff *sleep_cfm;
+ u16 bcn_miss_time_out;
+ u8 is_deep_sleep;
+ u8 delay_null_pkt;
+ u16 delay_to_ps;
+ u16 enhanced_ps_mode;
+ u8 pm_wakeup_card_req;
+ u16 gen_null_pkt;
+ u16 pps_uapsd_mode;
+ u32 pm_wakeup_fw_try;
+ struct timer_list wakeup_timer;
+ struct nxpwifi_hs_config_param hs_cfg;
+ u8 hs_activated;
+ u8 hs_activated_manually;
+ u16 hs_activate_wait_q_woken;
+ wait_queue_head_t hs_activate_wait_q;
+ u8 event_body[MAX_EVENT_SIZE];
+ u32 hw_dot_11n_dev_cap;
+ u8 hw_dev_mcs_support;
+ u8 hw_mpdu_density;
+ u8 user_dev_mcs_support;
+ u8 sec_chan_offset;
+ struct nxpwifi_dbg dbg;
+ u8 arp_filter[ARP_FILTER_MAX_BUF_SIZE];
+ u32 arp_filter_size;
+ struct nxpwifi_wait_queue cmd_wait_q;
+ u8 scan_wait_q_woken;
+ spinlock_t queue_lock; /* protects TX queues */
+ u8 dfs_region;
+ u8 country_code[IEEE80211_COUNTRY_STRING_LEN];
+ u16 max_mgmt_ie_index;
+ const struct firmware *cal_data;
+ /* 11AC capability fields */
+ u32 is_hw_11ac_capable;
+ u32 hw_dot_11ac_dev_cap;
+ u32 hw_dot_11ac_mcs_support;
+ u32 usr_dot_11ac_dev_cap_bg;
+ u32 usr_dot_11ac_dev_cap_a;
+ u32 usr_dot_11ac_mcs_support;
+ /* 11AX capability fields */
+ u8 is_hw_11ax_capable;
+ u8 hw_he_cap_len;
+ u8 hw_he_cap[HE_CAP_MAX_SIZE];
+ u8 hw_2g_he_cap_len;
+ u8 hw_2g_he_cap[HE_CAP_MAX_SIZE];
+ atomic_t pending_bridged_pkts;
+ struct completion *fw_done; /* FW init completion */
+ bool is_up;
+ bool ext_scan;
+ u8 fw_api_ver;
+ u8 fw_hotfix_ver;
+ u8 key_api_major_ver, key_api_minor_ver;
+ u8 max_sta_conn;
+ struct memory_type_mapping *mem_type_mapping_tbl;
+ u8 num_mem_types;
+ bool scan_chan_gap_enabled;
+ struct sk_buff_head rx_mlme_q;
+ struct sk_buff_head rx_data_q;
+ struct nxpwifi_chan_stats *chan_stats;
+ u32 num_in_chan_stats;
+ int survey_idx;
+ u8 coex_scan;
+ u8 coex_min_scan_time;
+ u8 coex_max_scan_time;
+ u8 coex_win_size;
+ u8 coex_tx_win_size;
+ u8 coex_rx_win_size;
+ u8 active_scan_triggered;
+ bool usb_mc_status;
+ bool usb_mc_setup;
+ struct cfg80211_wowlan_nd_info *nd_info;
+ struct ieee80211_regdomain *regd;
+ /* Aggregation parameters*/
+ struct bus_aggr_params bus_aggr;
+ void *devdump_data; /* device dump storage */
+ int devdump_len; /* device dump length */
+ bool ignore_btcoex_events;
+ struct vdll_dnld_ctrl vdll_ctrl;
+ u64 roc_cookie_counter;
+ u32 enable_net_mon;
+ bool wowlan_enabled;
+ bool chandef_valid;
+ struct cfg80211_chan_def chandef;
+ atomic_t uap_count;
+};
+
+void nxpwifi_process_tx_queue(struct nxpwifi_adapter *adapter);
+
+void nxpwifi_init_lock_list(struct nxpwifi_adapter *adapter);
+
+void nxpwifi_set_trans_start(struct net_device *dev);
+
+void nxpwifi_stop_net_dev_queue(struct net_device *netdev,
+ struct nxpwifi_adapter *adapter);
+
+void nxpwifi_wake_up_net_dev_queue(struct net_device *netdev,
+ struct nxpwifi_adapter *adapter);
+
+int nxpwifi_init_priv(struct nxpwifi_private *priv);
+void nxpwifi_free_priv(struct nxpwifi_private *priv);
+
+int nxpwifi_init_fw(struct nxpwifi_adapter *adapter);
+
+void nxpwifi_shutdown_drv(struct nxpwifi_adapter *adapter);
+
+int nxpwifi_dnld_fw(struct nxpwifi_adapter *adapter,
+ struct nxpwifi_fw_image *fw);
+
+int nxpwifi_recv_packet(struct nxpwifi_private *priv, struct sk_buff *skb);
+int nxpwifi_uap_recv_packet(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+
+void nxpwifi_host_mlme_disconnect(struct nxpwifi_private *priv,
+ u16 reason_code, u8 *sa);
+
+int nxpwifi_process_mgmt_packet(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+int nxpwifi_recv_packet_to_monif(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+int nxpwifi_complete_cmd(struct nxpwifi_adapter *adapter,
+ struct cmd_ctrl_node *cmd_node);
+
+void nxpwifi_cmd_timeout_func(struct timer_list *t);
+
+int nxpwifi_get_debug_info(struct nxpwifi_private *priv,
+ struct nxpwifi_debug_info *info);
+
+int nxpwifi_alloc_cmd_buffer(struct nxpwifi_adapter *adapter);
+void nxpwifi_free_cmd_buffer(struct nxpwifi_adapter *adapter);
+void nxpwifi_free_cmd_buffers(struct nxpwifi_adapter *adapter);
+void nxpwifi_cancel_all_pending_cmd(struct nxpwifi_adapter *adapter);
+void nxpwifi_cancel_pending_scan_cmd(struct nxpwifi_adapter *adapter);
+void nxpwifi_cancel_scan(struct nxpwifi_adapter *adapter);
+
+void nxpwifi_recycle_cmd_node(struct nxpwifi_adapter *adapter,
+ struct cmd_ctrl_node *cmd_node);
+
+void nxpwifi_insert_cmd_to_pending_q(struct nxpwifi_adapter *adapter,
+ struct cmd_ctrl_node *cmd_node);
+
+int nxpwifi_exec_next_cmd(struct nxpwifi_adapter *adapter);
+int nxpwifi_process_cmdresp(struct nxpwifi_adapter *adapter);
+void nxpwifi_process_assoc_resp(struct nxpwifi_adapter *adapter);
+int nxpwifi_handle_rx_packet(struct nxpwifi_adapter *adapter,
+ struct sk_buff *skb);
+int nxpwifi_process_tx(struct nxpwifi_private *priv, struct sk_buff *skb,
+ struct nxpwifi_tx_param *tx_param);
+int nxpwifi_send_null_packet(struct nxpwifi_private *priv, u8 flags);
+int nxpwifi_write_data_complete(struct nxpwifi_adapter *adapter,
+ struct sk_buff *skb, int aggr, int status);
+void nxpwifi_clean_txrx(struct nxpwifi_private *priv);
+u8 nxpwifi_check_last_packet_indication(struct nxpwifi_private *priv);
+void nxpwifi_check_ps_cond(struct nxpwifi_adapter *adapter);
+void nxpwifi_process_sleep_confirm_resp(struct nxpwifi_adapter *adapter,
+ u8 *pbuf, u32 upld_len);
+void nxpwifi_process_hs_config(struct nxpwifi_adapter *adapter);
+void nxpwifi_hs_activated_event(struct nxpwifi_private *priv,
+ u8 activated);
+int nxpwifi_set_hs_params(struct nxpwifi_private *priv, u16 action,
+ int cmd_type, struct nxpwifi_ds_hs_cfg *hs_cfg);
+int nxpwifi_ret_802_11_hs_cfg(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp);
+int nxpwifi_process_rx_packet(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+int nxpwifi_process_sta_rx_packet(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+int nxpwifi_process_uap_rx_packet(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+int nxpwifi_handle_uap_rx_forward(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+void nxpwifi_delete_all_station_list(struct nxpwifi_private *priv);
+void nxpwifi_wmm_del_peer_ra_list(struct nxpwifi_private *priv,
+ const u8 *ra_addr);
+void nxpwifi_process_sta_txpd(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+void nxpwifi_process_uap_txpd(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+int nxpwifi_cmd_802_11_scan(struct host_cmd_ds_command *cmd,
+ struct nxpwifi_scan_cmd_config *scan_cfg);
+void nxpwifi_queue_scan_cmd(struct nxpwifi_private *priv,
+ struct cmd_ctrl_node *cmd_node);
+int nxpwifi_ret_802_11_scan(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp);
+int nxpwifi_associate(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc);
+int nxpwifi_cmd_802_11_associate(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ struct nxpwifi_bssdescriptor *bss_desc);
+int nxpwifi_ret_802_11_associate(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp);
+u8 nxpwifi_band_to_radio_type(u16 config_bands);
+int nxpwifi_deauthenticate(struct nxpwifi_private *priv, u8 *mac);
+void nxpwifi_deauthenticate_all(struct nxpwifi_adapter *adapter);
+int nxpwifi_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd);
+struct nxpwifi_chan_freq_power *nxpwifi_get_cfp(struct nxpwifi_private *priv,
+ u8 band, u16 channel, u32 freq);
+u32 nxpwifi_index_to_data_rate(struct nxpwifi_private *priv,
+ u8 index, u8 ht_info);
+u32 nxpwifi_index_to_acs_data_rate(struct nxpwifi_private *priv,
+ u8 index, u8 ht_info);
+int nxpwifi_cmd_append_vsie_tlv(struct nxpwifi_private *priv, u16 vsie_mask,
+ u8 **buffer);
+u32 nxpwifi_get_active_data_rates(struct nxpwifi_private *priv,
+ u8 *rates);
+u32 nxpwifi_get_supported_rates(struct nxpwifi_private *priv, u8 *rates);
+u32 nxpwifi_get_rates_from_cfg80211(struct nxpwifi_private *priv,
+ u8 *rates, u8 radio_type);
+u8 nxpwifi_is_rate_auto(struct nxpwifi_private *priv);
+extern u16 region_code_index[NXPWIFI_MAX_REGION_CODE];
+void nxpwifi_save_curr_bcn(struct nxpwifi_private *priv);
+void nxpwifi_free_curr_bcn(struct nxpwifi_private *priv);
+int is_command_pending(struct nxpwifi_adapter *adapter);
+void nxpwifi_init_priv_params(struct nxpwifi_private *priv,
+ struct net_device *dev);
+void nxpwifi_set_ba_params(struct nxpwifi_private *priv);
+void nxpwifi_update_ampdu_txwinsize(struct nxpwifi_adapter *pmadapter);
+void nxpwifi_set_11ac_ba_params(struct nxpwifi_private *priv);
+int nxpwifi_cmd_802_11_scan_ext(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ void *data_buf);
+int nxpwifi_ret_802_11_scan_ext(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *resp);
+int nxpwifi_handle_event_ext_scan_report(struct nxpwifi_private *priv,
+ void *buf);
+int nxpwifi_cmd_802_11_bg_scan_config(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ void *data_buf);
+int nxpwifi_stop_bg_scan(struct nxpwifi_private *priv);
+
+/* check if RA-based queuing */
+static inline u8
+nxpwifi_queuing_ra_based(struct nxpwifi_private *priv)
+{
+ /* In STA mode DA==RA; subject to future revision */
+ if (priv->bss_mode == NL80211_IFTYPE_STATION &&
+ (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA))
+ return false;
+
+ return true;
+}
+
+/* copy rates from src to dest */
+static inline u32
+nxpwifi_copy_rates(u8 *dest, u32 pos, u8 *src, int len)
+{
+ int i;
+
+ for (i = 0; i < len && src[i]; i++, pos++) {
+ if (pos >= NXPWIFI_SUPPORTED_RATES)
+ break;
+ dest[pos] = src[i];
+ }
+
+ return pos;
+}
+
+/* return priv matching the given BSS type and number */
+static inline struct nxpwifi_private *
+nxpwifi_get_priv_by_id(struct nxpwifi_adapter *adapter,
+ u8 bss_num, u8 bss_type)
+{
+ int i;
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ if (adapter->priv[i]->bss_mode ==
+ NL80211_IFTYPE_UNSPECIFIED)
+ continue;
+ if (adapter->priv[i]->bss_num == bss_num &&
+ adapter->priv[i]->bss_type == bss_type)
+ break;
+ }
+ return ((i < adapter->priv_num) ? adapter->priv[i] : NULL);
+}
+
+/* return first priv matching BSS role */
+static inline struct nxpwifi_private *
+nxpwifi_get_priv(struct nxpwifi_adapter *adapter,
+ enum nxpwifi_bss_role bss_role)
+{
+ int i;
+
+ for (i = 0; i < adapter->priv_num; i++) {
+ if (bss_role == NXPWIFI_BSS_ROLE_ANY ||
+ GET_BSS_ROLE(adapter->priv[i]) == bss_role)
+ break;
+ }
+
+ return ((i < adapter->priv_num) ? adapter->priv[i] : NULL);
+}
+
+/* find unused BSS number for new interface */
+static inline u8
+nxpwifi_get_unused_bss_num(struct nxpwifi_adapter *adapter, u8 bss_type)
+{
+ u8 i, j;
+ int index[NXPWIFI_MAX_BSS_NUM];
+
+ memset(index, 0, sizeof(index));
+ for (i = 0; i < adapter->priv_num; i++)
+ if (adapter->priv[i]->bss_type == bss_type &&
+ !(adapter->priv[i]->bss_mode ==
+ NL80211_IFTYPE_UNSPECIFIED)) {
+ index[adapter->priv[i]->bss_num] = 1;
+ }
+ for (j = 0; j < NXPWIFI_MAX_BSS_NUM; j++)
+ if (!index[j])
+ return j;
+ return -ENOENT;
+}
+
+/* return unused private entry for requested bss type */
+static inline struct nxpwifi_private *
+nxpwifi_get_unused_priv_by_bss_type(struct nxpwifi_adapter *adapter,
+ u8 bss_type)
+{
+ u8 i;
+
+ for (i = 0; i < adapter->priv_num; i++)
+ if (adapter->priv[i]->bss_mode ==
+ NL80211_IFTYPE_UNSPECIFIED) {
+ adapter->priv[i]->bss_num =
+ nxpwifi_get_unused_bss_num(adapter, bss_type);
+ break;
+ }
+
+ return ((i < adapter->priv_num) ? adapter->priv[i] : NULL);
+}
+
+/* return private structure attached to netdev */
+static inline struct nxpwifi_private *
+nxpwifi_netdev_get_priv(struct net_device *dev)
+{
+ return (struct nxpwifi_private *)(*(unsigned long *)netdev_priv(dev));
+}
+
+/* return true if skb contains a management frame */
+static inline bool nxpwifi_is_skb_mgmt_frame(struct sk_buff *skb)
+{
+ return (get_unaligned_le32(skb->data) == PKT_TYPE_MGMT);
+}
+
+/* channel closed by CSA */
+static inline u8
+nxpwifi_11h_get_csa_closed_channel(struct nxpwifi_private *priv)
+{
+ if (!priv->csa_chan)
+ return 0;
+
+ /* clear CSA if DFS switch timeout expired */
+ if (time_after(jiffies, priv->csa_expire_time)) {
+ priv->csa_chan = 0;
+ priv->csa_expire_time = 0;
+ }
+
+ return priv->csa_chan;
+}
+
+static inline u8 nxpwifi_is_any_intf_active(struct nxpwifi_private *priv)
+{
+ struct nxpwifi_private *priv_tmp;
+ int i;
+
+ for (i = 0; i < priv->adapter->priv_num; i++) {
+ priv_tmp = priv->adapter->priv[i];
+ if ((GET_BSS_ROLE(priv_tmp) == NXPWIFI_BSS_ROLE_UAP &&
+ priv_tmp->bss_started) ||
+ (GET_BSS_ROLE(priv_tmp) == NXPWIFI_BSS_ROLE_STA &&
+ priv_tmp->media_connected))
+ return 1;
+ }
+
+ return 0;
+}
+
+int nxpwifi_init_shutdown_fw(struct nxpwifi_private *priv,
+ u32 func_init_shutdown);
+
+int nxpwifi_add_card(void *card, struct completion *fw_done,
+ struct nxpwifi_if_ops *if_ops, u8 iface_type,
+ struct device *dev);
+void nxpwifi_remove_card(struct nxpwifi_adapter *adapter);
+
+void nxpwifi_get_version(struct nxpwifi_adapter *adapter, char *version,
+ int maxlen);
+int
+nxpwifi_request_set_multicast_list(struct nxpwifi_private *priv,
+ struct nxpwifi_multicast_list *mcast_list);
+int nxpwifi_copy_mcast_addr(struct nxpwifi_multicast_list *mlist,
+ struct net_device *dev);
+int nxpwifi_wait_queue_complete(struct nxpwifi_adapter *adapter,
+ struct cmd_ctrl_node *cmd_queued);
+int nxpwifi_bss_start(struct nxpwifi_private *priv, struct cfg80211_bss *bss,
+ struct cfg80211_ssid *req_ssid);
+int nxpwifi_cancel_hs(struct nxpwifi_private *priv, int cmd_type);
+bool nxpwifi_enable_hs(struct nxpwifi_adapter *adapter);
+int nxpwifi_disable_auto_ds(struct nxpwifi_private *priv);
+int nxpwifi_drv_get_data_rate(struct nxpwifi_private *priv, u32 *rate);
+
+int nxpwifi_scan_networks(struct nxpwifi_private *priv,
+ const struct nxpwifi_user_scan_cfg *user_scan_in);
+int nxpwifi_set_radio(struct nxpwifi_private *priv, u8 option);
+
+int nxpwifi_set_encode(struct nxpwifi_private *priv, struct key_params *kp,
+ const u8 *key, int key_len, u8 key_index,
+ const u8 *mac_addr, int disable);
+
+int nxpwifi_set_gen_ie(struct nxpwifi_private *priv, const u8 *ie, int ie_len);
+
+int nxpwifi_get_ver_ext(struct nxpwifi_private *priv, u32 version_str_sel);
+
+int nxpwifi_remain_on_chan_cfg(struct nxpwifi_private *priv, u16 action,
+ struct ieee80211_channel *chan,
+ unsigned int duration);
+
+int nxpwifi_get_stats_info(struct nxpwifi_private *priv,
+ struct nxpwifi_ds_get_stats *log);
+
+int nxpwifi_reg_write(struct nxpwifi_private *priv, u32 reg_type,
+ u32 reg_offset, u32 reg_value);
+
+int nxpwifi_reg_read(struct nxpwifi_private *priv, u32 reg_type,
+ u32 reg_offset, u32 *value);
+
+int nxpwifi_eeprom_read(struct nxpwifi_private *priv, u16 offset, u16 bytes,
+ u8 *value);
+
+int nxpwifi_set_11n_httx_cfg(struct nxpwifi_private *priv, int data);
+
+int nxpwifi_get_11n_httx_cfg(struct nxpwifi_private *priv, int *data);
+
+int nxpwifi_set_tx_rate_cfg(struct nxpwifi_private *priv, int tx_rate_index);
+
+int nxpwifi_get_tx_rate_cfg(struct nxpwifi_private *priv, int *tx_rate_index);
+
+int nxpwifi_drv_set_power(struct nxpwifi_private *priv, u32 *ps_mode);
+
+int nxpwifi_drv_get_driver_version(struct nxpwifi_adapter *adapter,
+ char *version, int max_len);
+
+int nxpwifi_set_tx_power(struct nxpwifi_private *priv,
+ struct nxpwifi_power_cfg *power_cfg);
+
+void nxpwifi_main_process(struct nxpwifi_adapter *adapter);
+
+void nxpwifi_queue_tx_pkt(struct nxpwifi_private *priv, struct sk_buff *skb);
+
+int nxpwifi_get_bss_info(struct nxpwifi_private *priv,
+ struct nxpwifi_bss_info *info);
+int nxpwifi_fill_new_bss_desc(struct nxpwifi_private *priv,
+ struct cfg80211_bss *bss,
+ struct nxpwifi_bssdescriptor *bss_desc);
+int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
+ struct nxpwifi_bssdescriptor *bss_entry);
+int nxpwifi_check_network_compatibility(struct nxpwifi_private *priv,
+ struct nxpwifi_bssdescriptor *bss_desc);
+
+u8 nxpwifi_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type);
+u8 nxpwifi_get_chan_type(struct nxpwifi_private *priv);
+
+struct wireless_dev *nxpwifi_add_virtual_intf(struct wiphy *wiphy,
+ const char *name,
+ unsigned char name_assign_type,
+ enum nl80211_iftype type,
+ struct vif_params *params);
+int nxpwifi_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev);
+
+int nxpwifi_add_wowlan_magic_pkt_filter(struct nxpwifi_adapter *adapter);
+
+int nxpwifi_set_mgmt_ies(struct nxpwifi_private *priv,
+ struct cfg80211_beacon_data *data);
+int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv);
+u8 *nxpwifi_11d_code_2_region(u8 code);
+void nxpwifi_init_11h_params(struct nxpwifi_private *priv);
+int nxpwifi_is_11h_active(struct nxpwifi_private *priv);
+int nxpwifi_11h_activate(struct nxpwifi_private *priv, bool flag);
+void nxpwifi_11h_process_join(struct nxpwifi_private *priv, u8 **buffer,
+ struct nxpwifi_bssdescriptor *bss_desc);
+int nxpwifi_11h_handle_event_chanswann(struct nxpwifi_private *priv);
+
+extern const struct ethtool_ops nxpwifi_ethtool_ops;
+
+void nxpwifi_del_all_sta_list(struct nxpwifi_private *priv);
+void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac);
+void
+nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies,
+ int ies_len, struct nxpwifi_sta_node *node);
+struct nxpwifi_sta_node *
+nxpwifi_add_sta_entry(struct nxpwifi_private *priv, const u8 *mac);
+struct nxpwifi_sta_node *
+nxpwifi_get_sta_entry(struct nxpwifi_private *priv, const u8 *mac);
+struct nxpwifi_sta_node *
+nxpwifi_get_sta_entry_rcu(struct nxpwifi_private *priv, const u8 *mac);
+int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter);
+
+int nxpwifi_cmd_issue_chan_report_request(struct nxpwifi_private *priv,
+ struct host_cmd_ds_command *cmd,
+ void *data_buf);
+int nxpwifi_11h_handle_chanrpt_ready(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+
+void nxpwifi_parse_tx_status_event(struct nxpwifi_private *priv,
+ void *event_body);
+
+struct sk_buff *
+nxpwifi_clone_skb_for_tx_status(struct nxpwifi_private *priv,
+ struct sk_buff *skb, u8 flag, u64 *cookie);
+void nxpwifi_reset_conn_state_work(struct wiphy *wiphy, struct wiphy_work *work);
+void nxpwifi_dfs_cac_work(struct wiphy *wiphy, struct wiphy_work *work);
+void nxpwifi_dfs_chan_sw_work(struct wiphy *wiphy, struct wiphy_work *work);
+void nxpwifi_abort_cac(struct nxpwifi_private *priv);
+int nxpwifi_stop_radar_detection(struct nxpwifi_private *priv,
+ struct cfg80211_chan_def *chandef);
+int nxpwifi_11h_handle_radar_detected(struct nxpwifi_private *priv,
+ struct sk_buff *skb);
+
+void nxpwifi_hist_data_set(struct nxpwifi_private *priv, u8 rx_rate, s8 snr,
+ s8 nflr);
+void nxpwifi_hist_data_reset(struct nxpwifi_private *priv);
+void nxpwifi_hist_data_add(struct nxpwifi_private *priv,
+ u8 rx_rate, s8 snr, s8 nflr);
+u8 nxpwifi_adjust_data_rate(struct nxpwifi_private *priv,
+ u8 rx_rate, u8 ht_info);
+
+void nxpwifi_drv_info_dump(struct nxpwifi_adapter *adapter);
+void nxpwifi_prepare_fw_dump_info(struct nxpwifi_adapter *adapter);
+void nxpwifi_upload_device_dump(struct nxpwifi_adapter *adapter);
+void *nxpwifi_alloc_dma_align_buf(int rx_len, gfp_t flags);
+void nxpwifi_fw_dump_event(struct nxpwifi_private *priv);
+int nxpwifi_get_wakeup_reason(struct nxpwifi_private *priv, u16 action,
+ int cmd_type,
+ struct nxpwifi_ds_wakeup_reason *wakeup_reason);
+int nxpwifi_get_chan_info(struct nxpwifi_private *priv,
+ struct nxpwifi_channel_band *channel_band);
+void nxpwifi_coex_ampdu_rxwinsize(struct nxpwifi_adapter *adapter);
+void nxpwifi_11n_delba(struct nxpwifi_private *priv, int tid);
+int nxpwifi_send_domain_info_cmd_fw(struct wiphy *wiphy, enum nl80211_band band);
+int nxpwifi_set_mac_address(struct nxpwifi_private *priv,
+ struct net_device *dev,
+ bool external, u8 *new_mac);
+void nxpwifi_devdump_tmo_func(unsigned long function_context);
+
+#ifdef CONFIG_DEBUG_FS
+void nxpwifi_debugfs_init(void);
+void nxpwifi_debugfs_remove(void);
+
+void nxpwifi_dev_debugfs_init(struct nxpwifi_private *priv);
+void nxpwifi_dev_debugfs_remove(struct nxpwifi_private *priv);
+#endif
+int nxpwifi_reinit_sw(struct nxpwifi_adapter *adapter);
+void nxpwifi_shutdown_sw(struct nxpwifi_adapter *adapter);
+#endif /* !_NXPWIFI_MAIN_H_ */
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox