* [PATCH 1/2] net: wireless: fixed to checkpatch errors
From: Ozgur Karatas @ 2016-12-21 22:13 UTC (permalink / raw)
To: johannes, David Miller; +Cc: linux-wireless, netdev, linux-kernel
Fixed to checkpatch errors, Normally, comment's */ had to be finish on the next line.
The patch fix to readability and Coding Style.
Sİgned-off-by: Ozgur Karatas <okaratas@member.fsf.org>
---
net/wireless/chan.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/net/wireless/chan.c b/net/wireless/chan.c
index 5497d022..8c7ac7f 100644
--- a/net/wireless/chan.c
+++ b/net/wireless/chan.c
@@ -891,7 +891,8 @@ cfg80211_get_chan_state(struct wireless_dev *wdev,
: CHAN_MODE_EXCLUSIVE;
/* consider worst-case - IBSS can try to return to the
- * original user-specified channel as creator */
+ * original user-specified channel as creator
+ */
if (wdev->ibss_dfs_possible)
*radar_detect |= BIT(wdev->chandef.width);
return;
--
2.1.4
^ permalink raw reply related
* [PATCH] rtlwifi: rtl_usb: Fix missing entry in USB driver's private data
From: Larry Finger @ 2016-12-21 17:18 UTC (permalink / raw)
To: kvalo, Linus Torvalds
Cc: devel, linux-wireless, Larry Finger, linux-kernel, driver-devel
These drivers need to be able to reference "struct ieee80211_hw" from
the driver's private data, and vice versa. The USB driver failed to
store the address of ieee80211_hw in the private data. Although this
bug has been present for a long time, it was not exposed until
commit ba9f93f82aba ("rtlwifi: Fix enter/exit power_save").
Fixes: ba9f93f82aba ("rtlwifi: Fix enter/exit power_save")
Signed-off-by: Larry Finger <Larry.Finger@lwfinger.net>
---
Kalle,
This patch needs to be included in 4.10.
Larry
---
drivers/net/wireless/realtek/rtlwifi/usb.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/realtek/rtlwifi/usb.c b/drivers/net/wireless/realtek/rtlwifi/usb.c
index 40160c7..bf00399 100644
--- a/drivers/net/wireless/realtek/rtlwifi/usb.c
+++ b/drivers/net/wireless/realtek/rtlwifi/usb.c
@@ -1047,6 +1047,7 @@ int rtl_usb_probe(struct usb_interface *intf,
return -ENOMEM;
}
rtlpriv = hw->priv;
+ rtlpriv->hw = hw;
rtlpriv->usb_data = kzalloc(RTL_USB_MAX_RX_COUNT * sizeof(u32),
GFP_KERNEL);
if (!rtlpriv->usb_data)
--
2.10.2
^ permalink raw reply related
* Re: [PATCH 2/3] NFC: trf7970a: Add device tree option of 1.8 Volt IO voltage
From: Mark Greer @ 2016-12-21 16:13 UTC (permalink / raw)
To: Geoff Lansberry
Cc: linux-wireless, lauro.venancio, aloisio.almeida, sameo, robh+dt,
mark.rutland, netdev, devicetree, linux-kernel, justin
In-Reply-To: <32FAB08E-BE8E-4127-80A6-013300B43BD0@kuvee.com>
On Wed, Dec 21, 2016 at 06:47:36AM -0500, Geoff Lansberry wrote:
> Thanks Mark. Should I resubmit patches with the requested edits today, or wait a bit for more comments? What is the desired etiquette?
Its up to you. I don't think there are any hard & fast rules on this.
If it were me, I would likely spin a new version today because there are
several responses already and it lets people review them at their leisure
over the holidays.
Just a thought - you may want to consider separating the third patch from
the other two. The problems the first two solve are well understood and
have reasonable solutions (I believe). The third one - for me, at least -
tries to fix a problem that is not well understood yet.
Mark
--
^ permalink raw reply
* Re: ath10k mesh error 80MHz channel
From: Matteo Grandi @ 2016-12-21 15:21 UTC (permalink / raw)
To: LinuxWireless Mailing List
In-Reply-To: <CAHdg3xaR=KZBOisPSPtamRquce_wLPrCrXPm9Co5OWw5cOu0jw@mail.gmail.com>
Hi all again, just an update to the previous message:
looking at iw list I found this situation:
Frequencies:
* 5180 MHz [36] (20.0 dBm)
* 5200 MHz [40] (20.0 dBm)
* 5220 MHz [44] (20.0 dBm)
* 5240 MHz [48] (20.0 dBm)
* 5260 MHz [52] (20.0 dBm) (no IR, radar detection)
* 5280 MHz [56] (20.0 dBm) (no IR, radar detection)
* 5300 MHz [60] (20.0 dBm) (no IR, radar detection)
* 5320 MHz [64] (20.0 dBm) (no IR, radar detection)
* 5500 MHz [100] (23.0 dBm) (no IR, radar detection)
* 5520 MHz [104] (23.0 dBm) (no IR, radar detection)
* 5540 MHz [108] (23.0 dBm) (no IR, radar detection)
* 5560 MHz [112] (23.0 dBm) (no IR, radar detection)
* 5580 MHz [116] (23.0 dBm) (no IR, radar detection)
* 5600 MHz [120] (23.0 dBm) (no IR, radar detection)
* 5620 MHz [124] (23.0 dBm) (no IR, radar detection)
* 5640 MHz [128] (23.0 dBm) (no IR, radar detection)
* 5660 MHz [132] (23.0 dBm) (no IR, radar detection)
* 5680 MHz [136] (23.0 dBm) (no IR, radar detection)
* 5700 MHz [140] (23.0 dBm) (no IR, radar detection)
* 5745 MHz [149] (disabled)
* 5765 MHz [153] (disabled)
* 5785 MHz [157] (disabled)
* 5805 MHz [161] (disabled)
* 5825 MHz [165] (disabled)
Supported commands:
* new_interface
* set_interface
* new_key
The no Initial Radiation prevent the interface to start transmitting.
I'm almost sure that it's a regulatory domain issue, but i already
tried to reinstall the CRD Agent, and even changing the regulatory
domain it change only the global (i.e. CH) while the iw list provide
the same results.
root@MrProper:~# iw reg get
global
country CH: DFS-ETSI
(2402 - 2482 @ 40), (N/A, 20), (N/A)
(5170 - 5250 @ 80), (N/A, 20), (N/A), AUTO-BW
(5250 - 5330 @ 80), (N/A, 20), (0 ms), DFS, AUTO-BW
(5490 - 5710 @ 160), (N/A, 27), (0 ms), DFS
(57000 - 66000 @ 2160), (N/A, 40), (N/A)
phy#1
country US: DFS-FCC
(2402 - 2472 @ 40), (N/A, 30), (N/A)
(5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
(5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
(5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
(5735 - 5835 @ 80), (N/A, 30), (N/A)
(57240 - 63720 @ 2160), (N/A, 40), (N/A)
phy#0
country US: DFS-FCC
(2402 - 2472 @ 40), (N/A, 30), (N/A)
(5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
(5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
(5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
(5735 - 5835 @ 80), (N/A, 30), (N/A)
(57240 - 63720 @ 2160), (N/A, 40), (N/A)
phy#2
country US: DFS-FCC
(2402 - 2472 @ 40), (N/A, 30), (N/A)
(5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
(5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
(5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
(5735 - 5835 @ 80), (N/A, 30), (N/A)
(57240 - 63720 @ 2160), (N/A, 40), (N/A)
Is there a way to remove the no IR flags in the allowed channels?
Thanks a lot again
Matteo
2016-12-21 15:19 GMT+01:00 Matteo Grandi <iu5bdp@gmail.com>:
> Dear all,
>
> I'm configuring a simple mesh network using four miniPCIe adapters
> (Compex WLE900VX) on two Gateworks Ventana 5410 boards.
> Actually, based on the guide
> https://wireless.wiki.kernel.org/en/users/drivers/ath10k/mesh
> I don't have any problem setting up the mesh network on channels 36,
> 149, 153, respectively 5180 80 5210, 5745 80 5775, and 5765 80 5795
> (even if MIMO is not working).
>
> But while configuring the interfaces to work in 80MHz channel
> bandwidth (802.11ac using ath10k driver) on a different channel I
> bump into the error:
>
> command failed: Invalid argument (-22)
>
> immediately after launching the command for joining the mesh: iw dev
> <if_name> mesh join <mesh_name>.
>
> The syslog provide only this:
>
> Dec 21 13:52:03 MrProper kernel: [ 7257.058617] util.c |
> ieee80211_set_wmm_default: ac=0, enable_qos=true, vif.type=7,
> NL80211_IFTYPE_STATION=2
> Dec 21 13:52:03 MrProper kernel: [ 7257.059654] IPv6:
> ADDRCONF(NETDEV_UP): mpp1: link is not ready
> Dec 21 13:52:03 MrProper kernel: [ 7257.205911] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:03 MrProper kernel: [ 7257.581898] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:03 MrProper kernel: [ 7257.613627] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:04 MrProper kernel: [ 7258.084733] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:04 MrProper kernel: [ 7258.180963] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:04 MrProper kernel: [ 7258.325382] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:04 MrProper kernel: [ 7258.661534] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:05 MrProper kernel: [ 7259.029525] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:05 MrProper kernel: [ 7259.392563] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:08 MrProper kernel: [ 7262.386101] ath10k_warn: 18
> callbacks suppressed
> Dec 21 13:52:08 MrProper kernel: [ 7262.386154] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:09 MrProper kernel: [ 7262.737520] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:09 MrProper kernel: [ 7262.785597] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:09 MrProper kernel: [ 7262.971144] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:09 MrProper kernel: [ 7262.981414] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:09 MrProper kernel: [ 7263.091578] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:09 MrProper kernel: [ 7263.445603] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:09 MrProper kernel: [ 7263.458727] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:09 MrProper kernel: [ 7263.582180] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:09 MrProper kernel: [ 7263.589649] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:14 MrProper kernel: [ 7267.742838] ath10k_warn: 44
> callbacks suppressed
> Dec 21 13:52:14 MrProper kernel: [ 7267.742887] ath10k_pci
> 0000:07:00.0: no channel configured; ignoring frame(s)!
> Dec 21 13:52:14 MrProper kernel: [ 7267.744466] ath10k_pci
> 0000:07:00.0: no channel
>
> Did someone have a similar issue?
> Are there, maybe, some channels that even present in the regulatory
> domain with the @80 flags are not configurable in this way? (36, 52,
> 100, 116, 132, 149 should be ok, but only 36 and 149 work)
> Or maybe it's my reg-domain issue (see below)?
>
> root@MrProper:~# iw reg get
> global
> country US: DFS-FCC
> (2402 - 2472 @ 40), (N/A, 30), (N/A)
> (5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
> (5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
> (5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
> (5735 - 5835 @ 80), (N/A, 30), (N/A)
> (57240 - 63720 @ 2160), (N/A, 40), (N/A)
>
> phy#1
> country US: DFS-FCC
> (2402 - 2472 @ 40), (N/A, 30), (N/A)
> (5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
> (5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
> (5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
> (5735 - 5835 @ 80), (N/A, 30), (N/A)
> (57240 - 63720 @ 2160), (N/A, 40), (N/A)
>
> phy#0
> country US: DFS-FCC
> (2402 - 2472 @ 40), (N/A, 30), (N/A)
> (5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
> (5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
> (5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
> (5735 - 5835 @ 80), (N/A, 30), (N/A)
> (57240 - 63720 @ 2160), (N/A, 40), (N/A)
>
> phy#2
> country US: DFS-FCC
> (2402 - 2472 @ 40), (N/A, 30), (N/A)
> (5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
> (5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
> (5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
> (5735 - 5835 @ 80), (N/A, 30), (N/A)
> (57240 - 63720 @ 2160), (N/A, 40), (N/A)
>
>
> Thanks a lot for every hint!
>
> Matteo
^ permalink raw reply
* Re: rtlwifi: Fix kernel oops introduced with commit e49656147359
From: Kalle Valo @ 2016-12-21 14:35 UTC (permalink / raw)
To: Larry Finger
Cc: Linus Torvalds, devel, linux-wireless, Larry Finger, linux-kernel,
driver-devel, Stable, Wei Yongjun
In-Reply-To: <20161220023812.5999-1-Larry.Finger@lwfinger.net>
Larry Finger <Larry.Finger@lwfinger.net> wrote:
> With commit e49656147359 {"rtlwifi: Use dev_kfree_skb_irq instead of
> kfree_skb"), the method used to free an skb was changed because the
> kfree_skb() was inside a spinlock. What was forgotten is that kfree_skb()
> guards against a NULL value for the argument. Routine dev_kfree_skb_irq()
> does not, and a test is needed to prevent kernel panics.
>
> Fixes: e49656147359 ("rtlwifi: Use dev_kfree_skb_irq instead of kfree_skb")
> Signed-off-by: Larry Finger <Larry.Finger@lwfinger.net>
> Cc: Stable <stable@vger.kernel.org> # 4.9+
> Cc: Wei Yongjun <weiyongjun1@huawei.com>
Patch applied to wireless-drivers.git, thanks.
22b68b93ae25 rtlwifi: Fix kernel oops introduced with commit e49656147359
--
https://patchwork.kernel.org/patch/9481055/
Documentation about submitting wireless patches and checking status
from patchwork:
https://wireless.wiki.kernel.org/en/developers/documentation/submittingpatches
^ permalink raw reply
* Re: [v2] ath9k: do not return early to fix rcu unlocking
From: Kalle Valo @ 2016-12-21 14:29 UTC (permalink / raw)
To: Tobias Klausmann
Cc: kvalo, helgaas, linux-kernel, linux-pci, marc.zyngier,
janusz.dziedzic, ath9k-devel, linux-wireless, rmanohar,
bharat.kumar.gogada, Tobias Klausmann, # v4 . 9
In-Reply-To: <20161213170807.4853-1-tobias.johannes.klausmann@mni.thm.de>
Tobias Klausmann <tobias.johannes.klausmann@mni.thm.de> wrote:
> Starting with commit d94a461d7a7d ("ath9k: use ieee80211_tx_status_noskb
> where possible") the driver uses rcu_read_lock() && rcu_read_unlock(), yet on
> returning early in ath_tx_edma_tasklet() the unlock is missing leading to stalls
> and suspicious RCU usage:
>
> ===============================
> [ INFO: suspicious RCU usage. ]
> 4.9.0-rc8 #11 Not tainted
> -------------------------------
> kernel/rcu/tree.c:705 Illegal idle entry in RCU read-side critical section.!
>
> other info that might help us debug this:
>
> RCU used illegally from idle CPU!
> rcu_scheduler_active = 1, debug_locks = 0
> RCU used illegally from extended quiescent state!
> 1 lock held by swapper/7/0:
> #0:
> (
> rcu_read_lock
> ){......}
> , at:
> [<ffffffffa06ed110>] ath_tx_edma_tasklet+0x0/0x450 [ath9k]
>
> stack backtrace:
> CPU: 7 PID: 0 Comm: swapper/7 Not tainted 4.9.0-rc8 #11
> Hardware name: Acer Aspire V3-571G/VA50_HC_CR, BIOS V2.21 12/16/2013
> ffff88025efc3f38 ffffffff8132b1e5 ffff88017ede4540 0000000000000001
> ffff88025efc3f68 ffffffff810a25f7 ffff88025efcee60 ffff88017edebdd8
> ffff88025eeb5400 0000000000000091 ffff88025efc3f88 ffffffff810c3cd4
> Call Trace:
> <IRQ>
> [<ffffffff8132b1e5>] dump_stack+0x68/0x93
> [<ffffffff810a25f7>] lockdep_rcu_suspicious+0xd7/0x110
> [<ffffffff810c3cd4>] rcu_eqs_enter_common.constprop.85+0x154/0x200
> [<ffffffff810c5a54>] rcu_irq_exit+0x44/0xa0
> [<ffffffff81058631>] irq_exit+0x61/0xd0
> [<ffffffff81018d25>] do_IRQ+0x65/0x110
> [<ffffffff81672189>] common_interrupt+0x89/0x89
> <EOI>
> [<ffffffff814ffe11>] ? cpuidle_enter_state+0x151/0x200
> [<ffffffff814ffee2>] cpuidle_enter+0x12/0x20
> [<ffffffff8109a6ae>] call_cpuidle+0x1e/0x40
> [<ffffffff8109a8f6>] cpu_startup_entry+0x146/0x220
> [<ffffffff810336f8>] start_secondary+0x148/0x170
>
> Signed-off-by: Tobias Klausmann <tobias.johannes.klausmann@mni.thm.de>
> Fixes: d94a461d7a7d ("ath9k: use ieee80211_tx_status_noskb where possible")
> Cc: <stable@vger.kernel.org> # v4.9
> Acked-by: Felix Fietkau <nbd@nbd.name>
Patch applied to ath-current branch of ath.git, thanks.
d1f1c0e289e1 ath9k: do not return early to fix rcu unlocking
--
https://patchwork.kernel.org/patch/9472709/
Documentation about submitting wireless patches and checking status
from patchwork:
https://wireless.wiki.kernel.org/en/developers/documentation/submittingpatches
^ permalink raw reply
* ath10k mesh error 80MHz channel
From: Matteo Grandi @ 2016-12-21 14:19 UTC (permalink / raw)
To: LinuxWireless Mailing List
Dear all,
I'm configuring a simple mesh network using four miniPCIe adapters
(Compex WLE900VX) on two Gateworks Ventana 5410 boards.
Actually, based on the guide
https://wireless.wiki.kernel.org/en/users/drivers/ath10k/mesh
I don't have any problem setting up the mesh network on channels 36,
149, 153, respectively 5180 80 5210, 5745 80 5775, and 5765 80 5795
(even if MIMO is not working).
But while configuring the interfaces to work in 80MHz channel
bandwidth (802.11ac using ath10k driver) on a different channel I
bump into the error:
command failed: Invalid argument (-22)
immediately after launching the command for joining the mesh: iw dev
<if_name> mesh join <mesh_name>.
The syslog provide only this:
Dec 21 13:52:03 MrProper kernel: [ 7257.058617] util.c |
ieee80211_set_wmm_default: ac=0, enable_qos=true, vif.type=7,
NL80211_IFTYPE_STATION=2
Dec 21 13:52:03 MrProper kernel: [ 7257.059654] IPv6:
ADDRCONF(NETDEV_UP): mpp1: link is not ready
Dec 21 13:52:03 MrProper kernel: [ 7257.205911] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:03 MrProper kernel: [ 7257.581898] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:03 MrProper kernel: [ 7257.613627] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:04 MrProper kernel: [ 7258.084733] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:04 MrProper kernel: [ 7258.180963] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:04 MrProper kernel: [ 7258.325382] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:04 MrProper kernel: [ 7258.661534] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:05 MrProper kernel: [ 7259.029525] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:05 MrProper kernel: [ 7259.392563] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:08 MrProper kernel: [ 7262.386101] ath10k_warn: 18
callbacks suppressed
Dec 21 13:52:08 MrProper kernel: [ 7262.386154] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:09 MrProper kernel: [ 7262.737520] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:09 MrProper kernel: [ 7262.785597] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:09 MrProper kernel: [ 7262.971144] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:09 MrProper kernel: [ 7262.981414] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:09 MrProper kernel: [ 7263.091578] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:09 MrProper kernel: [ 7263.445603] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:09 MrProper kernel: [ 7263.458727] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:09 MrProper kernel: [ 7263.582180] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:09 MrProper kernel: [ 7263.589649] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:14 MrProper kernel: [ 7267.742838] ath10k_warn: 44
callbacks suppressed
Dec 21 13:52:14 MrProper kernel: [ 7267.742887] ath10k_pci
0000:07:00.0: no channel configured; ignoring frame(s)!
Dec 21 13:52:14 MrProper kernel: [ 7267.744466] ath10k_pci
0000:07:00.0: no channel
Did someone have a similar issue?
Are there, maybe, some channels that even present in the regulatory
domain with the @80 flags are not configurable in this way? (36, 52,
100, 116, 132, 149 should be ok, but only 36 and 149 work)
Or maybe it's my reg-domain issue (see below)?
root@MrProper:~# iw reg get
global
country US: DFS-FCC
(2402 - 2472 @ 40), (N/A, 30), (N/A)
(5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
(5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
(5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
(5735 - 5835 @ 80), (N/A, 30), (N/A)
(57240 - 63720 @ 2160), (N/A, 40), (N/A)
phy#1
country US: DFS-FCC
(2402 - 2472 @ 40), (N/A, 30), (N/A)
(5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
(5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
(5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
(5735 - 5835 @ 80), (N/A, 30), (N/A)
(57240 - 63720 @ 2160), (N/A, 40), (N/A)
phy#0
country US: DFS-FCC
(2402 - 2472 @ 40), (N/A, 30), (N/A)
(5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
(5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
(5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
(5735 - 5835 @ 80), (N/A, 30), (N/A)
(57240 - 63720 @ 2160), (N/A, 40), (N/A)
phy#2
country US: DFS-FCC
(2402 - 2472 @ 40), (N/A, 30), (N/A)
(5170 - 5250 @ 80), (N/A, 23), (N/A), AUTO-BW
(5250 - 5330 @ 80), (N/A, 23), (0 ms), DFS, AUTO-BW
(5490 - 5730 @ 160), (N/A, 23), (0 ms), DFS
(5735 - 5835 @ 80), (N/A, 30), (N/A)
(57240 - 63720 @ 2160), (N/A, 40), (N/A)
Thanks a lot for every hint!
Matteo
^ permalink raw reply
* [PATCH] ath10k: support dev_coredump for crash dump
From: Kalle Valo @ 2016-12-21 12:19 UTC (permalink / raw)
To: ath10k; +Cc: linux-wireless
From: Arun Khandavalli <akhandav@qti.qualcomm.com>
Whenever firmware crashes, and both CONFIG_ATH10K_DEBUGFS and
CONFIG_ALLOW_DEV_COREDUMP are enabled, dump information about the crash via a
devcoredump device. Dump can be read from userspace for further analysis from:
/sys/class/devcoredump/devcd*/data
As until now we have provided the firmware crash dump file via fw_crash_dump
debugfs keep it still available but deprecate and a warning print that the user
should switch to using dev_coredump.
Future improvement would be not to depend on CONFIG_ATH10K_DEBUGFS, as there
might be systems which want to get the firmware crash dump but not enable
debugfs. How to handle memory consumption is also something which needs to be
taken into account.
Signed-off-by: Arun Khandavalli <akhandav@qti.qualcomm.com>
[kvalo@qca.qualcomm.com: rebase, fixes, improve commit log]
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/core.c | 6 ++++
drivers/net/wireless/ath/ath10k/debug.c | 43 +++++++++++++++++++++++++++++--
drivers/net/wireless/ath/ath10k/debug.h | 8 ++++++
3 files changed, 54 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c
index 749e381edd38..3319db178c2e 100644
--- a/drivers/net/wireless/ath/ath10k/core.c
+++ b/drivers/net/wireless/ath/ath10k/core.c
@@ -1510,6 +1510,7 @@ static int ath10k_init_hw_params(struct ath10k *ar)
static void ath10k_core_restart(struct work_struct *work)
{
struct ath10k *ar = container_of(work, struct ath10k, restart_work);
+ int ret;
set_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags);
@@ -1561,6 +1562,11 @@ static void ath10k_core_restart(struct work_struct *work)
}
mutex_unlock(&ar->conf_mutex);
+
+ ret = ath10k_debug_fw_devcoredump(ar);
+ if (ret)
+ ath10k_warn(ar, "failed to send firmware crash dump via devcoredump: %d",
+ ret);
}
static void ath10k_core_set_coverage_class_work(struct work_struct *work)
diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
index 82a4c67f3672..e1a70dffc52a 100644
--- a/drivers/net/wireless/ath/ath10k/debug.c
+++ b/drivers/net/wireless/ath/ath10k/debug.c
@@ -21,6 +21,7 @@
#include <linux/utsname.h>
#include <linux/crc32.h>
#include <linux/firmware.h>
+#include <linux/devcoredump.h>
#include "core.h"
#include "debug.h"
@@ -721,7 +722,8 @@ struct ath10k_fw_crash_data *
}
EXPORT_SYMBOL(ath10k_debug_get_new_fw_crash_data);
-static struct ath10k_dump_file_data *ath10k_build_dump_file(struct ath10k *ar)
+static struct ath10k_dump_file_data *ath10k_build_dump_file(struct ath10k *ar,
+ bool mark_read)
{
struct ath10k_fw_crash_data *crash_data = ar->debug.fw_crash_data;
struct ath10k_dump_file_data *dump_data;
@@ -790,19 +792,54 @@ static struct ath10k_dump_file_data *ath10k_build_dump_file(struct ath10k *ar)
sizeof(crash_data->registers));
sofar += sizeof(*dump_tlv) + sizeof(crash_data->registers);
- ar->debug.fw_crash_data->crashed_since_read = false;
+ ar->debug.fw_crash_data->crashed_since_read = !mark_read;
spin_unlock_bh(&ar->data_lock);
return dump_data;
}
+int ath10k_debug_fw_devcoredump(struct ath10k *ar)
+{
+ struct ath10k_dump_file_data *dump;
+ void *dump_ptr;
+ u32 dump_len;
+
+ /* To keep the dump file available also for debugfs don't mark the
+ * file read, only debugfs should do that.
+ */
+ dump = ath10k_build_dump_file(ar, false);
+ if (!dump) {
+ ath10k_warn(ar, "no crash dump data found for devcoredump");
+ return -ENODATA;
+ }
+
+ /* Make a copy of the dump file for dev_coredumpv() as during the
+ * transition period we need to own the original file. Once
+ * fw_crash_dump debugfs file is removed no need to have a copy
+ * anymore.
+ */
+ dump_len = le32_to_cpu(dump->len);
+ dump_ptr = vzalloc(dump_len);
+
+ if (!dump_ptr)
+ return -ENOMEM;
+
+ memcpy(dump_ptr, dump, dump_len);
+
+ dev_coredumpv(ar->dev, dump_ptr, dump_len, GFP_KERNEL);
+
+ return 0;
+}
+
static int ath10k_fw_crash_dump_open(struct inode *inode, struct file *file)
{
struct ath10k *ar = inode->i_private;
struct ath10k_dump_file_data *dump;
- dump = ath10k_build_dump_file(ar);
+ ath10k_warn(ar, "fw_crash_dump debugfs file is deprecated, please use /sys/class/devcoredump instead.");
+
+ dump = ath10k_build_dump_file(ar, true);
if (!dump)
return -ENODATA;
diff --git a/drivers/net/wireless/ath/ath10k/debug.h b/drivers/net/wireless/ath/ath10k/debug.h
index 335512b11ca2..2368f47314ae 100644
--- a/drivers/net/wireless/ath/ath10k/debug.h
+++ b/drivers/net/wireless/ath/ath10k/debug.h
@@ -84,6 +84,9 @@ struct ath10k_fw_crash_data *
ath10k_debug_get_new_fw_crash_data(struct ath10k *ar);
void ath10k_debug_dbglog_add(struct ath10k *ar, u8 *buffer, int len);
+
+int ath10k_debug_fw_devcoredump(struct ath10k *ar);
+
#define ATH10K_DFS_STAT_INC(ar, c) (ar->debug.dfs_stats.c++)
void ath10k_debug_get_et_strings(struct ieee80211_hw *hw,
@@ -166,6 +169,11 @@ static inline u32 ath10k_debug_get_fw_dbglog_level(struct ath10k *ar)
return 0;
}
+static inline int ath10k_debug_fw_devcoredump(struct ath10k *ar)
+{
+ return 0;
+}
+
#define ATH10K_DFS_STAT_INC(ar, c) do { } while (0)
#define ath10k_debug_get_et_strings NULL
^ permalink raw reply related
* skb truesize bug related to ath10k
From: Ben Greear @ 2016-12-21 12:10 UTC (permalink / raw)
To: linux-wireless@vger.kernel.org
This warning is from the code below. Anyone seen this recently?
And, what sorts of code problems can generate this type of error? We
seem to have a way to reproduce this by bringing down and up many
station vdevs over and over again, so I can probably fix/test it if I
can understand the problem better.
We see this on QCA 9880 and 9984 NICs, so it is probably not firmware
specific issue. We have not seen this on 4.4 kernel, but we do see it
in our 4.7.
} else {
if (skb_shinfo(to)->nr_frags +
skb_shinfo(from)->nr_frags > MAX_SKB_FRAGS)
return false;
delta = from->truesize - SKB_TRUESIZE(skb_end_offset(from));
}
WARN_ON_ONCE(delta < len);
Dec 18 22:40:36 ct524-9933 kernel: WARNING: CPU: 7 PID: 0 at /home/greearb/git/linux-4.7.dev.y/net/core/skbuff.c:4283 skb_try_coalesce+0x402/0x410
Dec 18 22:40:36 ct524-9933 kernel: Modules linked in: nf_conntrack_netlink nf_conntrack nfnetlink wanlink(O) nf_defrag_ipv4 macvlan pktgen fuse vfat fat
coretemp intel_rapl x86_pkg_temp_thermal intel_powerclamp kvm_intel kvm irqbypass ath10k_pci ath10k_core ath mac80211 iTCO_wdt iTCO_vendor_support ipmi_ssif
cfg80211 hci_uart joydev i2c_i801 btbcm btqca btintel ie31200_edac bluetooth edac_core shpchp acpi_als tpm_tis kfifo_buf pinctrl_sunrisepoint intel_lpss_acpi
ipmi_msghandler tpm industrialio video pinctrl_intel intel_lpss acpi_power_meter sch_fq_codel nfsd auth_rpcgss nfs_acl lockd grace sunrpc 8021q garp stp llc mrp
ast drm_kms_helper ttm drm igb i2c_algo_bit ixgbe mdio hwmon dca ptp pps_core i2c_hid i2c_core fjes efivarfs ipv6 [last unloaded: nfnetlink]
Dec 18 22:40:36 ct524-9933 kernel: CPU: 7 PID: 0 Comm: swapper/7 Tainted: G O 4.7.10+ #46
Dec 18 22:40:36 ct524-9933 kernel: Hardware name: Supermicro Super Server/X11SSM, BIOS 1.0b 12/29/2015
Dec 18 22:40:36 ct524-9933 kernel: 0000000000000000 ffff8804779c3688 ffffffff81409351 0000000000000000
Dec 18 22:40:36 ct524-9933 kernel: 0000000000000000 ffff8804779c36c8 ffffffff81103976 000010bb779c36a8
Dec 18 22:40:36 ct524-9933 kernel: ffff88007ce1f800 00000000000005a8 ffff8804779c3724 ffff880075b85000
Dec 18 22:40:36 ct524-9933 kernel: Call Trace:
Dec 18 22:40:36 ct524-9933 kernel: <IRQ> [<ffffffff81409351>] dump_stack+0x63/0x82
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81103976>] __warn+0xc6/0xe0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81103a48>] warn_slowpath_null+0x18/0x20
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81760c62>] skb_try_coalesce+0x402/0x410
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817c7b79>] tcp_try_coalesce+0x39/0xa0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81761671>] ? skb_checksum+0x21/0x30
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817c8322>] tcp_queue_rcv+0x52/0x140
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817cee84>] tcp_rcv_established+0x364/0x6f0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817d9286>] tcp_v4_do_rcv+0x136/0x210
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817dab49>] tcp_v4_rcv+0x8d9/0xaf0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff8180a7f2>] ? iptable_filter_hook+0x22/0x60
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817b4bdb>] ip_local_deliver_finish+0x9b/0x1f0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817b4edb>] ip_local_deliver+0x5b/0xd0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817b4b40>] ? ip_rcv_finish+0x3f0/0x3f0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817b486a>] ip_rcv_finish+0x11a/0x3f0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817b51af>] ip_rcv+0x25f/0x380
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817b4750>] ? inet_del_offload+0x40/0x40
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81773cb4>] __netif_receive_skb_core+0x684/0xa80
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff8124925a>] ? kmem_cache_free+0x17a/0x180
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817740c3>] __netif_receive_skb+0x13/0x60
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81774133>] netif_receive_skb_internal+0x23/0x90
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff817741b7>] netif_receive_skb+0x17/0x80
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffffa0a08d07>] ieee80211_deliver_skb+0x157/0x1b0 [mac80211]
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffffa0a0ad9a>] ieee80211_rx_handlers+0xa8a/0x2560 [mac80211]
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff8155717a>] ? dma_pte_clear_level+0x14a/0x190
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffffa0a0d077>] ieee80211_prepare_and_rx_handle+0x597/0x10a0 [mac80211]
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffffa0a0ddfa>] ieee80211_rx_napi+0x27a/0x930 [mac80211]
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffffa0c179cc>] ath10k_process_rx+0x2ac/0x4a0 [ath10k_core]
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffffa0c17bf5>] ath10k_htt_rx_h_deliver+0x35/0x80 [ath10k_core]
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffffa0c1a421>] ath10k_htt_txrx_compl_task+0xc91/0xe60 [ath10k_core]
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81108afb>] tasklet_action+0x10b/0x120
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81108eca>] __do_softirq+0xca/0x2b0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff811091e9>] irq_exit+0x89/0x90
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff8102e69f>] do_IRQ+0x4f/0xd0
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff8184db42>] common_interrupt+0x82/0x82
Dec 18 22:40:36 ct524-9933 kernel: <EOI> [<ffffffff816e8020>] ? cpuidle_enter_state+0x110/0x290
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff816e81c2>] cpuidle_enter+0x12/0x20
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81144bb5>] call_cpuidle+0x25/0x40
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff81144f72>] cpu_startup_entry+0x2b2/0x370
Dec 18 22:40:36 ct524-9933 kernel: [<ffffffff8104d8cd>] start_secondary+0x14d/0x170
Dec 18 22:40:36 ct524-9933 kernel: ---[ end trace fa9eabf037886c79 ]---
--
Ben Greear <greearb@candelatech.com>
Candela Technologies Inc http://www.candelatech.com
^ permalink raw reply
* Re: [PATCH 2/3] NFC: trf7970a: Add device tree option of 1.8 Volt IO voltage
From: Geoff Lansberry @ 2016-12-21 11:47 UTC (permalink / raw)
To: Mark Greer
Cc: linux-wireless, lauro.venancio, aloisio.almeida, sameo, robh+dt,
mark.rutland, netdev, devicetree, linux-kernel, justin
In-Reply-To: <20161221022347.GB5444@animalcreek.com>
Thanks Mark. Should I resubmit patches with the requested edits today, or w=
ait a bit for more comments? What is the desired etiquette?
> On Dec 20, 2016, at 9:23 PM, Mark Greer <mgreer@animalcreek.com> wrote:
>=20
>> On Tue, Dec 20, 2016 at 11:16:31AM -0500, Geoff Lansberry wrote:
>> From: Geoff Lansberry <geoff@kuvee.com>
>>=20
>> The TRF7970A has configuration options for supporting hardware designs
>> with 1.8 Volt or 3.3 Volt IO. This commit adds a device tree option,
>> using a fixed regulator binding, for setting the io voltage to match
>> the hardware configuration. If no option is supplied it defaults to
>> 3.3 volt configuration.
>=20
> Sign-off ?? Same comment for you other patches.
>=20
> <time passes>
>=20
> Okay I see you have it at the end of the patch. It should be here.
> 'git commit -s' is your friend.
>=20
>> ---
>> .../devicetree/bindings/net/nfc/trf7970a.txt | 4 ++--
>> drivers/nfc/trf7970a.c | 28 +++++++++++++++++=
++++-
>> 2 files changed, 29 insertions(+), 3 deletions(-)
>>=20
>> diff --git a/Documentation/devicetree/bindings/net/nfc/trf7970a.txt b/Doc=
umentation/devicetree/bindings/net/nfc/trf7970a.txt
>> index e262ac1..b5777d8 100644
>> --- a/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
>> +++ b/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
>> @@ -21,9 +21,9 @@ Optional SoC Specific Properties:
>> - t5t-rmb-extra-byte-quirk: Specify that the trf7970a has the erratum
>> where an extra byte is returned by Read Multiple Block commands issued
>> to Type 5 tags.
>> +- vdd-io-supply: Regulator specifying voltage for vdd-io
>> - clock-frequency: Set to specify that the input frequency to the trf7970=
a is 13560000Hz or 27120000Hz
>>=20
>> -
>> Example (for ARM-based BeagleBone with TRF7970A on SPI1):
>>=20
>> &spi1 {
>> @@ -41,11 +41,11 @@ Example (for ARM-based BeagleBone with TRF7970A on SP=
I1):
>> <&gpio2 5 GPIO_ACTIVE_LOW>;
>> vin-supply =3D <&ldo3_reg>;
>> vin-voltage-override =3D <5000000>;
>> + vdd-io-supply =3D <&ldo2_reg>;
>> autosuspend-delay =3D <30000>;
>> irq-status-read-quirk;
>> en2-rf-quirk;
>> t5t-rmb-extra-byte-quirk;
>> - vdd_io_1v8;
>=20
> It was already mentioned but this shouldn't have been added in the
> previous patch so it shouldn't be here now.
>=20
>> clock-frequency =3D <27120000>;
>> status =3D "okay";
>> };
>> diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c
>> index 4e051e9..8a88195 100644
>> --- a/drivers/nfc/trf7970a.c
>> +++ b/drivers/nfc/trf7970a.c
>=20
>> @@ -2062,6 +2068,7 @@ static int trf7970a_probe(struct spi_device *spi)
>> return ret;
>> }
>>=20
>> +
>=20
> Please don't add an extra blank line.
>=20
>> of_property_read_u32(np, "clock-frequency", &clk_freq);
>> if ((clk_freq !=3D TRF7970A_27MHZ_CLOCK_FREQUENCY) ||
>> (clk_freq !=3D TRF7970A_27MHZ_CLOCK_FREQUENCY)) {
>> @@ -2105,6 +2112,25 @@ static int trf7970a_probe(struct spi_device *spi)
>> if (uvolts > 4000000)
>> trf->chip_status_ctrl =3D TRF7970A_CHIP_STATUS_VRS5_3;
>>=20
>> + trf->regulator =3D devm_regulator_get(&spi->dev, "vdd-io");
>> + if (IS_ERR(trf->regulator)) {
>> + ret =3D PTR_ERR(trf->regulator);
>> + dev_err(trf->dev, "Can't get VDD_IO regulator: %d\n", ret);
>> + goto err_destroy_lock;
>> + }
>> +
>> + ret =3D regulator_enable(trf->regulator);
>> + if (ret) {
>> + dev_err(trf->dev, "Can't enable VDD_IO: %d\n", ret);
>> + goto err_destroy_lock;
>> + }
>> +
>> +
>=20
> Please don't add an extra blank line.
>=20
>> + if (regulator_get_voltage(trf->regulator) =3D=3D 1800000) {
>> + trf->io_ctrl =3D TRF7970A_REG_IO_CTRL_IO_LOW;
>> + dev_dbg(trf->dev, "trf7970a config vdd_io to 1.8V\n");
>> + }
>> +
>> trf->ddev =3D nfc_digital_allocate_device(&trf7970a_nfc_ops,
>> TRF7970A_SUPPORTED_PROTOCOLS,
>> NFC_DIGITAL_DRV_CAPS_IN_CRC |
>> --=20
>> Signed-off-by: Geoff Lansberry <geoff@kuvee.com>
>=20
> Your 'Signed-off-by:' goes at the end of the commit description not here.
>=20
> Overall, I think you did the right thing (unless someone disagrees).
> Just some minor issues.
>=20
> Mark
> --
^ permalink raw reply
* [RFC] nl80211: allow multiple active scheduled scan requests
From: Arend van Spriel @ 2016-12-21 10:20 UTC (permalink / raw)
To: Johannes Berg; +Cc: linux-wireless, Arend van Spriel, Dmitry Shmidt
In-Reply-To: <1481645071.20412.30.camel@sipsolutions.net>
This patch implements the idea to have multiple scheduled scan requests
running concurrently. It mainly illustrates how to deal with the incoming
request from user-space in terms of backward compatibility. In order to
use multiple scheduled scans user-space needs to provide a flag attribute
NL80211_ATTR_SCHED_SCAN_MULTI to indicate support. If not the request is
treated as a legacy scan.
Cc: Dmitry Shmidt <dimitrysh@google.com>
Signed-off-by: Arend van Spriel <arend.vanspriel@broadcom.com>
---
Hi Johannes,
Did a bit of coding on the Universal/multi-scheduled scan idea. This
just deals with handling requests. Not sure if I got the RCU stuff
right so any remarks are welcome.
Regards,
Arend
---
include/net/cfg80211.h | 7 ++++
include/uapi/linux/nl80211.h | 12 ++++++-
net/wireless/core.c | 30 ++++++++++------
net/wireless/core.h | 10 +++++-
net/wireless/nl80211.c | 38 ++++++++++++++++++--
net/wireless/scan.c | 83 +++++++++++++++++++++++++++++++++++---------
6 files changed, 148 insertions(+), 32 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 814be4b..f5c0592 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1594,6 +1594,7 @@ struct cfg80211_sched_scan_plan {
/**
* struct cfg80211_sched_scan_request - scheduled scan request description
*
+ * @reqid: identifies this request.
* @ssids: SSIDs to scan for (passed in the probe_reqs in active scans)
* @n_ssids: number of SSIDs
* @n_channels: total number of channels to scan
@@ -1622,12 +1623,14 @@ struct cfg80211_sched_scan_plan {
* @rcu_head: RCU callback used to free the struct
* @owner_nlportid: netlink portid of owner (if this should is a request
* owned by a particular socket)
+ * @list: for keeping list of requests.
* @delay: delay in seconds to use before starting the first scan
* cycle. The driver may ignore this parameter and start
* immediately (or at any other time), if this feature is not
* supported.
*/
struct cfg80211_sched_scan_request {
+ u64 reqid;
struct cfg80211_ssid *ssids;
int n_ssids;
u32 n_channels;
@@ -1651,6 +1654,7 @@ struct cfg80211_sched_scan_request {
unsigned long scan_start;
struct rcu_head rcu_head;
u32 owner_nlportid;
+ struct list_head list;
/* keep last */
struct ieee80211_channel *channels[0];
@@ -3415,6 +3419,8 @@ struct wiphy_iftype_ext_capab {
* this variable determines its size
* @max_scan_ssids: maximum number of SSIDs the device can scan for in
* any given scan
+ * @max_sched_scan_reqs: maximum number of scheduled scan requests that
+ * the device can run concurrently.
* @max_sched_scan_ssids: maximum number of SSIDs the device can scan
* for in any given scheduled scan
* @max_match_sets: maximum number of match sets the device can handle
@@ -3547,6 +3553,7 @@ struct wiphy {
int bss_priv_size;
u8 max_scan_ssids;
+ u8 max_sched_scan_reqs;
u8 max_sched_scan_ssids;
u8 max_match_sets;
u16 max_scan_ie_len;
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 6b76e3b..4045058 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -351,7 +351,9 @@
* are used. Extra IEs can also be passed from the userspace by
* using the %NL80211_ATTR_IE attribute. The first cycle of the
* scheduled scan can be delayed by %NL80211_ATTR_SCHED_SCAN_DELAY
- * is supplied.
+ * is supplied. If the device supports multiple concurrent scheduled
+ * scans, it will allow such when the caller provides the flag attribute
+ * %NL80211_ATTR_SCHED_SCAN_MULTI to indicate user-space support for it.
* @NL80211_CMD_STOP_SCHED_SCAN: stop a scheduled scan. Returns -ENOENT if
* scheduled scan is not running. The caller may assume that as soon
* as the call returns, it is safe to start a new scheduled scan again.
@@ -1980,6 +1982,11 @@ enum nl80211_commands {
* @NL80211_ATTR_BSSID: The BSSID of the AP. Note that %NL80211_ATTR_MAC is also
* used in various commands/events for specifying the BSSID.
*
+ * @NL80211_ATTR_SCHED_SCAN_MULTI: flag attribute which user-space shall use to
+ * indicate that it supports multiple active scheduled scan requests.
+ * @NL80211_ATTR_SCHED_SCAN_MAX_REQS: indicates maximum number of scheduled
+ * scan request that may be active for the device (u8).
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -2386,6 +2393,9 @@ enum nl80211_attrs {
NL80211_ATTR_BSSID,
+ NL80211_ATTR_SCHED_SCAN_MULTI,
+ NL80211_ATTR_SCHED_SCAN_MAX_REQS,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 158c59e..1584234 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -346,13 +346,17 @@ static void cfg80211_destroy_iface_wk(struct work_struct *work)
static void cfg80211_sched_scan_stop_wk(struct work_struct *work)
{
struct cfg80211_registered_device *rdev;
+ struct cfg80211_sched_scan_request *pos, *tmp;
rdev = container_of(work, struct cfg80211_registered_device,
sched_scan_stop_wk);
rtnl_lock();
- __cfg80211_stop_sched_scan(rdev, false);
+ /* request gets removed from list so need safe iterator */
+ list_for_each_entry_safe(pos, tmp, &rdev->sched_scan_req_list, list) {
+ cfg80211_stop_sched_scan_req(rdev, pos, false);
+ }
rtnl_unlock();
}
@@ -436,6 +440,7 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv,
spin_lock_init(&rdev->beacon_registrations_lock);
spin_lock_init(&rdev->bss_lock);
INIT_LIST_HEAD(&rdev->bss_list);
+ INIT_LIST_HEAD(&rdev->sched_scan_req_list);
INIT_WORK(&rdev->scan_done_wk, __cfg80211_scan_done);
INIT_WORK(&rdev->sched_scan_results_wk, __cfg80211_sched_scan_results);
INIT_LIST_HEAD(&rdev->mlme_unreg);
@@ -690,6 +695,10 @@ int wiphy_register(struct wiphy *wiphy)
(wiphy->bss_select_support & ~(BIT(__NL80211_BSS_SELECT_ATTR_AFTER_LAST) - 2))))
return -EINVAL;
+ if ((wiphy->flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) &&
+ !wiphy->max_sched_scan_reqs)
+ wiphy->max_sched_scan_reqs = 1;
+
if (wiphy->addresses)
memcpy(wiphy->perm_addr, wiphy->addresses[0].addr, ETH_ALEN);
@@ -1000,7 +1009,7 @@ void __cfg80211_leave(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev)
{
struct net_device *dev = wdev->netdev;
- struct cfg80211_sched_scan_request *sched_scan_req;
+ struct cfg80211_sched_scan_request *pos, *tmp;
ASSERT_RTNL();
ASSERT_WDEV_LOCK(wdev);
@@ -1011,9 +1020,10 @@ void __cfg80211_leave(struct cfg80211_registered_device *rdev,
break;
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_STATION:
- sched_scan_req = rtnl_dereference(rdev->sched_scan_req);
- if (sched_scan_req && dev == sched_scan_req->dev)
- __cfg80211_stop_sched_scan(rdev, false);
+ list_for_each_entry_safe(pos, tmp, &rdev->sched_scan_req_list, list) {
+ if (dev == pos->dev)
+ cfg80211_stop_sched_scan_req(rdev, pos, false);
+ }
#ifdef CONFIG_CFG80211_WEXT
kfree(wdev->wext.ie);
@@ -1088,7 +1098,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_registered_device *rdev;
- struct cfg80211_sched_scan_request *sched_scan_req;
+ struct cfg80211_sched_scan_request *pos, *tmp;
if (!wdev)
return NOTIFY_DONE;
@@ -1155,10 +1165,10 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
___cfg80211_scan_done(rdev, false);
}
- sched_scan_req = rtnl_dereference(rdev->sched_scan_req);
- if (WARN_ON(sched_scan_req &&
- sched_scan_req->dev == wdev->netdev)) {
- __cfg80211_stop_sched_scan(rdev, false);
+ list_for_each_entry_safe(pos, tmp,
+ &rdev->sched_scan_req_list, list) {
+ if (WARN_ON(pos && pos->dev == wdev->netdev))
+ cfg80211_stop_sched_scan_req(rdev, pos, false);
}
rdev->opencount--;
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 9820fa2..951954b 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -73,6 +73,8 @@ struct cfg80211_registered_device {
u32 bss_generation;
struct cfg80211_scan_request *scan_req; /* protected by RTNL */
struct sk_buff *scan_msg;
+ u8 sched_scan_req_count;
+ struct list_head sched_scan_req_list;
struct cfg80211_sched_scan_request __rcu *sched_scan_req;
unsigned long suspend_at;
struct work_struct scan_done_wk;
@@ -419,9 +421,15 @@ int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev,
void __cfg80211_scan_done(struct work_struct *wk);
void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev,
bool send_message);
+void cfg80211_add_sched_scan_req(struct cfg80211_registered_device *rdev,
+ struct cfg80211_sched_scan_request *req);
+bool cfg80211_legacy_sched_scan_active(struct cfg80211_registered_device *rdev);
void __cfg80211_sched_scan_results(struct work_struct *wk);
+int cfg80211_stop_sched_scan_req(struct cfg80211_registered_device *rdev,
+ struct cfg80211_sched_scan_request *req,
+ bool driver_initiated);
int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
- bool driver_initiated);
+ u64 reqid, bool driver_initiated);
void cfg80211_upload_connect_keys(struct wireless_dev *wdev);
int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
struct net_device *dev, enum nl80211_iftype ntype,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 3df85a7..f1b253c 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -405,6 +405,7 @@ enum nl80211_multicast_groups {
[NL80211_ATTR_FILS_NONCES] = { .len = 2 * FILS_NONCE_LEN },
[NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED] = { .type = NLA_FLAG, },
[NL80211_ATTR_BSSID] = { .len = ETH_ALEN },
+ [NL80211_ATTR_SCHED_SCAN_MULTI] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -1463,6 +1464,8 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
rdev->wiphy.coverage_class) ||
nla_put_u8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS,
rdev->wiphy.max_scan_ssids) ||
+ nla_put_u8(msg, NL80211_ATTR_SCHED_SCAN_MAX_REQS,
+ rdev->wiphy.max_sched_scan_reqs) ||
nla_put_u8(msg, NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS,
rdev->wiphy.max_sched_scan_ssids) ||
nla_put_u16(msg, NL80211_ATTR_MAX_SCAN_IE_LEN,
@@ -7185,9 +7188,17 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
!rdev->ops->sched_scan_start)
return -EOPNOTSUPP;
- if (rdev->sched_scan_req)
+ /*
+ * allow only one legacy scheduled scan if user-space
+ * does not indicate multiple scheduled scan support.
+ */
+ if (!info->attrs[NL80211_ATTR_SCHED_SCAN_MULTI] &&
+ cfg80211_legacy_sched_scan_active(rdev))
return -EINPROGRESS;
+ if (rdev->sched_scan_req_count == rdev->wiphy.max_sched_scan_reqs)
+ return -ENOSPC;
+
sched_scan_req = nl80211_parse_sched_scan(&rdev->wiphy, wdev,
info->attrs);
@@ -7195,6 +7206,12 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
if (err)
goto out_err;
+ /* leave request id zero for legacy request */
+ if (info->attrs[NL80211_ATTR_SCHED_SCAN_MULTI]) {
+ while (!sched_scan_req->reqid)
+ sched_scan_req->reqid = rdev->wiphy.cookie_counter++;
+ }
+
err = rdev_sched_scan_start(rdev, dev, sched_scan_req);
if (err)
goto out_free;
@@ -7205,7 +7222,7 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
if (info->attrs[NL80211_ATTR_SOCKET_OWNER])
sched_scan_req->owner_nlportid = info->snd_portid;
- rcu_assign_pointer(rdev->sched_scan_req, sched_scan_req);
+ cfg80211_add_sched_scan_req(rdev, sched_scan_req);
nl80211_send_sched_scan(rdev, dev,
NL80211_CMD_START_SCHED_SCAN);
@@ -7220,13 +7237,28 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
static int nl80211_stop_sched_scan(struct sk_buff *skb,
struct genl_info *info)
{
+ struct cfg80211_sched_scan_request *req;
struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ u64 cookie;
if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) ||
!rdev->ops->sched_scan_stop)
return -EOPNOTSUPP;
- return __cfg80211_stop_sched_scan(rdev, false);
+ if (info->attrs[NL80211_ATTR_COOKIE]) {
+ cookie = nla_get_u64(info->attrs[NL80211_ATTR_COOKIE]);
+ return __cfg80211_stop_sched_scan(rdev, cookie, false);
+ } else {
+ req = list_first_or_null_rcu(&rdev->sched_scan_req_list,
+ struct cfg80211_sched_scan_request,
+ list);
+ if (!req || req->reqid ||
+ (req->owner_nlportid &&
+ req->owner_nlportid != info->snd_portid))
+ return -ENOENT;
+
+ return cfg80211_stop_sched_scan_req(rdev, req, false);
+ }
}
static int nl80211_start_radar_detection(struct sk_buff *skb,
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index b5bd58d..942059e1 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -249,6 +249,46 @@ void cfg80211_scan_done(struct cfg80211_scan_request *request,
}
EXPORT_SYMBOL(cfg80211_scan_done);
+void cfg80211_add_sched_scan_req(struct cfg80211_registered_device *rdev,
+ struct cfg80211_sched_scan_request *req)
+{
+ if (req->reqid)
+ list_add_tail_rcu(&req->list, &rdev->sched_scan_req_list);
+ else
+ list_add_rcu(&req->list, &rdev->sched_scan_req_list);
+ rdev->sched_scan_req_count++;
+}
+
+static void cfg80211_del_sched_scan_req(struct cfg80211_registered_device *rdev,
+ struct cfg80211_sched_scan_request *req)
+{
+ list_del_rcu(&req->list);
+ kfree_rcu(req, rcu_head);
+ synchronize_rcu();
+ rdev->sched_scan_req_count--;
+}
+
+static struct cfg80211_sched_scan_request *
+cfg80211_find_sched_scan_req(struct cfg80211_registered_device *rdev, u64 reqid)
+{
+ struct cfg80211_sched_scan_request *pos;
+ list_for_each_entry(pos, &rdev->sched_scan_req_list, list) {
+ if (pos->reqid == reqid)
+ return pos;
+ }
+ return ERR_PTR(-ENOENT);
+}
+
+bool cfg80211_legacy_sched_scan_active(struct cfg80211_registered_device *rdev)
+{
+ struct cfg80211_sched_scan_request *req;
+
+ req = list_first_or_null_rcu(&rdev->sched_scan_req_list,
+ struct cfg80211_sched_scan_request, list);
+ /* request id 0 indicates legacy request in progress */
+ return req && !req->reqid;
+}
+
void __cfg80211_sched_scan_results(struct work_struct *wk)
{
struct cfg80211_registered_device *rdev;
@@ -295,7 +335,7 @@ void cfg80211_sched_scan_stopped_rtnl(struct wiphy *wiphy)
trace_cfg80211_sched_scan_stopped(wiphy);
- __cfg80211_stop_sched_scan(rdev, true);
+ __cfg80211_stop_sched_scan(rdev, 0, true);
}
EXPORT_SYMBOL(cfg80211_sched_scan_stopped_rtnl);
@@ -307,34 +347,43 @@ void cfg80211_sched_scan_stopped(struct wiphy *wiphy)
}
EXPORT_SYMBOL(cfg80211_sched_scan_stopped);
-int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
- bool driver_initiated)
+int cfg80211_stop_sched_scan_req(struct cfg80211_registered_device *rdev,
+ struct cfg80211_sched_scan_request *req,
+ bool driver_initiated)
{
- struct cfg80211_sched_scan_request *sched_scan_req;
- struct net_device *dev;
-
ASSERT_RTNL();
- if (!rdev->sched_scan_req)
- return -ENOENT;
-
- sched_scan_req = rtnl_dereference(rdev->sched_scan_req);
- dev = sched_scan_req->dev;
-
if (!driver_initiated) {
- int err = rdev_sched_scan_stop(rdev, dev);
+ int err = rdev_sched_scan_stop(rdev, req->dev);
if (err)
return err;
}
- nl80211_send_sched_scan(rdev, dev, NL80211_CMD_SCHED_SCAN_STOPPED);
+ nl80211_send_sched_scan(rdev, req->dev, NL80211_CMD_SCHED_SCAN_STOPPED);
- RCU_INIT_POINTER(rdev->sched_scan_req, NULL);
- kfree_rcu(sched_scan_req, rcu_head);
+ cfg80211_del_sched_scan_req(rdev, req);
return 0;
}
+int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
+ u64 reqid, bool driver_initiated)
+{
+ struct cfg80211_sched_scan_request *sched_scan_req;
+
+ ASSERT_RTNL();
+
+ if (!rdev->sched_scan_req_count)
+ return -ENOENT;
+
+ sched_scan_req = cfg80211_find_sched_scan_req(rdev, reqid);
+ if (IS_ERR(sched_scan_req))
+ return PTR_ERR(sched_scan_req);
+
+ return cfg80211_stop_sched_scan_req(rdev, sched_scan_req,
+ driver_initiated);
+}
+
void cfg80211_bss_age(struct cfg80211_registered_device *rdev,
unsigned long age_secs)
{
@@ -1078,7 +1127,7 @@ struct cfg80211_bss *
else
rcu_assign_pointer(tmp.pub.beacon_ies, ies);
rcu_assign_pointer(tmp.pub.ies, ies);
-
+
memcpy(tmp.pub.bssid, mgmt->bssid, ETH_ALEN);
tmp.pub.channel = channel;
tmp.pub.scan_width = data->scan_width;
--
1.9.1
^ permalink raw reply related
* Re: [PATCH v2 2/2] cfg80211: Add support to sched scan to report better BSSs
From: Arend Van Spriel @ 2016-12-21 9:18 UTC (permalink / raw)
To: Malinen, Jouni, Johannes Berg
Cc: Vamsi, Krishna, linux-wireless@vger.kernel.org
In-Reply-To: <20161220205221.GB9756@jouni.qca.qualcomm.com>
On 20-12-2016 21:52, Malinen, Jouni wrote:
> On Fri, Dec 16, 2016 at 10:56:51AM +0100, Johannes Berg wrote:
>> On Thu, 2016-12-15 at 11:06 +0000, Malinen, Jouni wrote:
>>> Maybe the nl80211.h description was not clear enough, but the
>>> comments in cfg80211.h should be quite clear on how this was designed
>>> to work at the implementation level:
>>>
>>> "If the current connected BSS is in the 2.4 GHz band, other BSSs in
>>> the 2.4 GHz band to be reported should have better RSSI by
>>> @relative_rssi and other BSSs in the 5 GHz band to be reported should
>>> have better RSSI by (@relative_rssi - @relative_rssi_5g_pref).
>>> If the current connected BSS is in the 5 GHz band, other BSSs in the
>>> 2.4 GHz band to be reported should have better RSSI by
>>> (@relative_rssi + @relative_rssi_5g_pref) and other BSSs in the 5 GHz
>>> band to be reported should have better RSSI by by @relative_rssi."
>>
>> Oh, right. Should probably be in nl80211.h too, to set expectations
>> from userspace.
>
> Sure, we can update that in the next revision.
>
>>> At minimum, we would need to clearly document struct
>>> nl80211_bss_select_rssi_adjust, but even if we do, I'm not sure it
>>> really is ideal mechanism to move to now that I realized it is not an
>>> array, but a single band,delta pair.
>>
>> We can move to an array easily in the future by extending the attribute
>> length and advertising the number of array entries that are supported,
>> if that's your biggest concern? I don't see it as being very useful
>> right now since I don't think we'll see offloaded roaming between 2.4/5
>> and 60 GHz anytime soon. This may change when we add more bands later,
>> I suppose.
>
> Hmm.. So do you want us to move to using this packed struct in the new
> attribute instead of using a signed 8-bit integer as the variable value?
That was my suggestion so it is more clear what user-space wants by
making it specify the band explicitly. So in the explanation above
reference to "5g" should be "specified band" etc. Whether you reuse the
packed struct nl80211_bss_select_rssi_adjust or come up with a new
(identical?) one is irrelevant to me.
Also I don't see the array issue. @relative_rssi_5g_pref with s8 value
seems same as @rssi_adjust with (band=5g, s8 value) packed together. Or
am I missing something here.
>>> If we are talking only about roaming within an ESS (a single SSID),
>>> that would sound clear, but which relative RSSI rules would apply if
>>> there are match sets for both the currently connected SSID and
>>> another SSID that the candidate BSS is for?
>>
>> Right, I see how this might become a problem. I generally see no issue
>> with supporting multiple matchsets with different SSIDs but all having
>> the "relative to connected BSS RSSI filter" (which would disregard the
>> SSID specified in the matchset), but this then would become a problem
>> when multiple matchsets are specified with *different* such RSSI
>> filters, e.g. one matchset would specify that you want a 5G preference
>> of 10dB, but the other would specify a preference of only 5dB.
>>
>> Conceptually in this approach, that would be supported, but firmware
>> likely would not be able to express this, I suppose?
>
> That's certainly not at the level we were planning on implementing.. :)
Right. So having "relative rssi" matchset attribute is off the table as
far as I am concerned.
>>> I think I'm missing something here.. Where would the threshold value
>>> (how much better new BSS needs to be) be stored? And do we really
>>> want something like the combination of
>>> NL80211_BSS_SELECT_ATTR_BAND_PREF and
>>> NL80211_BSS_SELECT_ATTR_RSSI_ADJUST which seems to be two different
>>> ways of doing band preference (the former without specifying delta
>>> and the latter with specific delta)? Or am I still not understanding
>>> how NL80211_BSS_SELECT_ATTR_* really works?
It is documented here:
/**
* enum nl80211_bss_select_attr - attributes for bss selection.
*
[...]
*
* One and only one of these attributes are found within
%NL80211_ATTR_BSS_SELECT
* for %NL80211_CMD_CONNECT. It specifies the required BSS selection
behaviour
* which the driver shall use.
*/
It is checked in nl80211.c [1]
>> No, you're right, I missed the "better by" threshold.
>>
>> I think between that (unless we add that, we could technically extend
>> flag attributes to allow them being an int as well, or add a new one)
>> and the fact that the device may not support different relative RSSI
>> matches in different matchsets, I'm almost convinced that adding new
>> attributes is better.
>
> I'm not completely sure how to interpret all this and also the last
> email from Arend in this thread. Could either (or both :) of you provide
> more detailed suggestion on what exactly you would like us to change, if
> anything, in the attribute design now so that we can try to close on
> this?
To summarize: 1) stick with the new attributes on request level (so not
matchset level), 2) use packed struct for @relative_rssi_5g_pref.
Regards,
Arend
[1] http://lxr.free-electrons.com/source/net/wireless/nl80211.c#L6382
^ permalink raw reply
* [PATCH v3] rfkill: Add rfkill-any LED trigger
From: Michał Kępień @ 2016-12-21 8:45 UTC (permalink / raw)
To: Johannes Berg, David S . Miller
Cc: Михаил Кринкин,
linux-wireless, netdev, linux-kernel
Add a new "global" (i.e. not per-rfkill device) LED trigger, rfkill-any,
which may be useful on laptops with a single "radio LED" and multiple
radio transmitters. The trigger is meant to turn a LED on whenever
there is at least one radio transmitter active and turn it off
otherwise.
This requires taking rfkill_global_mutex before calling
rfkill_set_block() in rfkill_resume(): since
rfkill_any_led_trigger_event(true) is called from rfkill_set_block()
unconditionally, each caller of the latter needs to take care of locking
rfkill_global_mutex.
Signed-off-by: Michał Kępień <kernel@kempniu.pl>
---
Jonathan, I refrained from resending patch 1/2 from v2 as part of this
series as it is currently applied in mac80211-next/master along with
Arnd's fix. Please let me know if you would like me to handle this
differently.
Mike, could you please test whether this version works fine on your
machine? Thanks!
Changes from v2:
- Handle the global mutex properly when rfkill_set_{hw,sw}_state() or
rfkill_set_states() is called from within an rfkill callback. v2
always tried to lock the global mutex in such a case, which led to a
deadlock when an rfkill driver called one of the above functions
from its query or set_block callback. This is solved by defining a
new bitfield, RFKILL_BLOCK_SW_HASLOCK, which is set before the above
callbacks are invoked and cleared afterwards; the functions listed
above use this bitfield to tell rfkill_any_led_trigger_event()
whether the global mutex is currently held or not.
RFKILL_BLOCK_SW_SETCALL cannot be reused for this purpose as setting
it before invoking the query callback would cause any calls to
rfkill_set_sw_state() made from within that callback to work on
RFKILL_BLOCK_SW_PREV instead of RFKILL_BLOCK_SW and thus change the
way rfkill_set_block() behaves.
- As rfkill_any_led_trigger_event() now takes a boolean argument which
tells it whether the global mutex was already taken by the caller,
all calls to __rfkill_any_led_trigger_event() outside
rfkill_any_led_trigger_event() have been replaced with calls to
rfkill_any_led_trigger_event(true).
net/rfkill/core.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 86 insertions(+), 4 deletions(-)
diff --git a/net/rfkill/core.c b/net/rfkill/core.c
index afa4f71b4c7b..688eac7b97ef 100644
--- a/net/rfkill/core.c
+++ b/net/rfkill/core.c
@@ -44,6 +44,7 @@
#define RFKILL_BLOCK_ANY (RFKILL_BLOCK_HW |\
RFKILL_BLOCK_SW |\
RFKILL_BLOCK_SW_PREV)
+#define RFKILL_BLOCK_SW_HASLOCK BIT(30)
#define RFKILL_BLOCK_SW_SETCALL BIT(31)
struct rfkill {
@@ -176,6 +177,51 @@ static void rfkill_led_trigger_unregister(struct rfkill *rfkill)
{
led_trigger_unregister(&rfkill->led_trigger);
}
+
+static struct led_trigger rfkill_any_led_trigger;
+
+static void __rfkill_any_led_trigger_event(void)
+{
+ enum led_brightness brightness = LED_OFF;
+ struct rfkill *rfkill;
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ if (!(rfkill->state & RFKILL_BLOCK_ANY)) {
+ brightness = LED_FULL;
+ break;
+ }
+ }
+
+ led_trigger_event(&rfkill_any_led_trigger, brightness);
+}
+
+static void rfkill_any_led_trigger_event(bool global_locked)
+{
+ if (global_locked) {
+ __rfkill_any_led_trigger_event();
+ } else {
+ mutex_lock(&rfkill_global_mutex);
+ __rfkill_any_led_trigger_event();
+ mutex_unlock(&rfkill_global_mutex);
+ }
+}
+
+static void rfkill_any_led_trigger_activate(struct led_classdev *led_cdev)
+{
+ rfkill_any_led_trigger_event(false);
+}
+
+static int rfkill_any_led_trigger_register(void)
+{
+ rfkill_any_led_trigger.name = "rfkill-any";
+ rfkill_any_led_trigger.activate = rfkill_any_led_trigger_activate;
+ return led_trigger_register(&rfkill_any_led_trigger);
+}
+
+static void rfkill_any_led_trigger_unregister(void)
+{
+ led_trigger_unregister(&rfkill_any_led_trigger);
+}
#else
static void rfkill_led_trigger_event(struct rfkill *rfkill)
{
@@ -189,6 +235,19 @@ static inline int rfkill_led_trigger_register(struct rfkill *rfkill)
static inline void rfkill_led_trigger_unregister(struct rfkill *rfkill)
{
}
+
+static void rfkill_any_led_trigger_event(bool global_locked)
+{
+}
+
+static int rfkill_any_led_trigger_register(void)
+{
+ return 0;
+}
+
+static void rfkill_any_led_trigger_unregister(void)
+{
+}
#endif /* CONFIG_RFKILL_LEDS */
static void rfkill_fill_event(struct rfkill_event *ev, struct rfkill *rfkill,
@@ -253,6 +312,10 @@ static void rfkill_set_block(struct rfkill *rfkill, bool blocked)
if (unlikely(rfkill->dev.power.power_state.event & PM_EVENT_SLEEP))
return;
+ spin_lock_irqsave(&rfkill->lock, flags);
+ rfkill->state |= RFKILL_BLOCK_SW_HASLOCK;
+ spin_unlock_irqrestore(&rfkill->lock, flags);
+
/*
* Some platforms (...!) generate input events which affect the
* _hard_ kill state -- whenever something tries to change the
@@ -292,11 +355,13 @@ static void rfkill_set_block(struct rfkill *rfkill, bool blocked)
rfkill->state &= ~RFKILL_BLOCK_SW;
}
rfkill->state &= ~RFKILL_BLOCK_SW_SETCALL;
+ rfkill->state &= ~RFKILL_BLOCK_SW_HASLOCK;
rfkill->state &= ~RFKILL_BLOCK_SW_PREV;
curr = rfkill->state & RFKILL_BLOCK_SW;
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
+ rfkill_any_led_trigger_event(true);
if (prev != curr)
rfkill_event(rfkill);
@@ -463,7 +528,7 @@ bool rfkill_get_global_sw_state(const enum rfkill_type type)
bool rfkill_set_hw_state(struct rfkill *rfkill, bool blocked)
{
unsigned long flags;
- bool ret, prev;
+ bool ret, prev, global_locked;
BUG_ON(!rfkill);
@@ -474,9 +539,11 @@ bool rfkill_set_hw_state(struct rfkill *rfkill, bool blocked)
else
rfkill->state &= ~RFKILL_BLOCK_HW;
ret = !!(rfkill->state & RFKILL_BLOCK_ANY);
+ global_locked = !!(rfkill->state & RFKILL_BLOCK_SW_HASLOCK);
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
+ rfkill_any_led_trigger_event(global_locked);
if (rfkill->registered && prev != blocked)
schedule_work(&rfkill->uevent_work);
@@ -502,7 +569,7 @@ static void __rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
{
unsigned long flags;
- bool prev, hwblock;
+ bool prev, hwblock, global_locked;
BUG_ON(!rfkill);
@@ -511,6 +578,7 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
__rfkill_set_sw_state(rfkill, blocked);
hwblock = !!(rfkill->state & RFKILL_BLOCK_HW);
blocked = blocked || hwblock;
+ global_locked = !!(rfkill->state & RFKILL_BLOCK_SW_HASLOCK);
spin_unlock_irqrestore(&rfkill->lock, flags);
if (!rfkill->registered)
@@ -520,6 +588,7 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
schedule_work(&rfkill->uevent_work);
rfkill_led_trigger_event(rfkill);
+ rfkill_any_led_trigger_event(global_locked);
return blocked;
}
@@ -542,7 +611,7 @@ EXPORT_SYMBOL(rfkill_init_sw_state);
void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
{
unsigned long flags;
- bool swprev, hwprev;
+ bool swprev, hwprev, global_locked;
BUG_ON(!rfkill);
@@ -559,6 +628,7 @@ void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
rfkill->state |= RFKILL_BLOCK_HW;
else
rfkill->state &= ~RFKILL_BLOCK_HW;
+ global_locked = !!(rfkill->state & RFKILL_BLOCK_SW_HASLOCK);
spin_unlock_irqrestore(&rfkill->lock, flags);
@@ -569,6 +639,7 @@ void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
schedule_work(&rfkill->uevent_work);
rfkill_led_trigger_event(rfkill);
+ rfkill_any_led_trigger_event(global_locked);
}
}
EXPORT_SYMBOL(rfkill_set_states);
@@ -812,8 +883,10 @@ static int rfkill_resume(struct device *dev)
rfkill->suspended = false;
if (!rfkill->persistent) {
+ mutex_lock(&rfkill_global_mutex);
cur = !!(rfkill->state & RFKILL_BLOCK_SW);
rfkill_set_block(rfkill, cur);
+ mutex_unlock(&rfkill_global_mutex);
}
if (rfkill->ops->poll && !rfkill->polling_paused)
@@ -985,6 +1058,7 @@ int __must_check rfkill_register(struct rfkill *rfkill)
#endif
}
+ rfkill_any_led_trigger_event(true);
rfkill_send_events(rfkill, RFKILL_OP_ADD);
mutex_unlock(&rfkill_global_mutex);
@@ -1017,6 +1091,7 @@ void rfkill_unregister(struct rfkill *rfkill)
mutex_lock(&rfkill_global_mutex);
rfkill_send_events(rfkill, RFKILL_OP_DEL);
list_del_init(&rfkill->node);
+ rfkill_any_led_trigger_event(true);
mutex_unlock(&rfkill_global_mutex);
rfkill_led_trigger_unregister(rfkill);
@@ -1269,6 +1344,10 @@ static int __init rfkill_init(void)
if (error)
goto error_misc;
+ error = rfkill_any_led_trigger_register();
+ if (error)
+ goto error_led_trigger;
+
#ifdef CONFIG_RFKILL_INPUT
error = rfkill_handler_init();
if (error)
@@ -1279,8 +1358,10 @@ static int __init rfkill_init(void)
#ifdef CONFIG_RFKILL_INPUT
error_input:
- misc_deregister(&rfkill_miscdev);
+ rfkill_any_led_trigger_unregister();
#endif
+error_led_trigger:
+ misc_deregister(&rfkill_miscdev);
error_misc:
class_unregister(&rfkill_class);
error_class:
@@ -1293,6 +1374,7 @@ static void __exit rfkill_exit(void)
#ifdef CONFIG_RFKILL_INPUT
rfkill_handler_exit();
#endif
+ rfkill_any_led_trigger_unregister();
misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
}
--
2.11.0
^ permalink raw reply related
* [PATCH] linux-firmware: mwlwifi: update 88W8864 firmware.
From: David Lin @ 2016-12-21 6:53 UTC (permalink / raw)
To: Ben Hutchings
Cc: linux-firmware@kernel.org, linux-wireless@vger.kernel.org,
Chor Teck Law, James Lin, Pete Hsieh
Release Version: 7.2.9.26.
Signed-off-by: David Lin <dlin@marvell.com>
---
mwlwifi/88W8864.bin | Bin 116356 -> 118776 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/mwlwifi/88W8864.bin b/mwlwifi/88W8864.bin
index 19b21a6bfbdd04073ebd62561fa8c2df8c80575b..e6d12b9efd98593578f3e49f0b1c91ba400657e8 100644
GIT binary patch
delta 52963
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
delta 50616
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
--
1.9.3
^ permalink raw reply
* [PATCH v9] Add new mac80211 driver mwlwifi.
From: David Lin @ 2016-12-21 4:11 UTC (permalink / raw)
To: Kalle Valo
Cc: linux-wireless@vger.kernel.org, Johannes Berg, Chor Teck Law,
James Lin, Pete Hsieh
This patch provides the mwlwifi driver for Marvell 8863, 8864 and 8897
chipsets.
This driver was developed as part of the openwrt.org project to support
Linksys WRT1900AC and is maintained on https://github.com/kaloz/mwlwifi.
The mwlwifi driver differs from existing mwifiex driver:
o mwlwifi is a "softmac driver" using the kernel mac802.11 subsystem
to provide full AP/Wireless Bridge functionality (routers).
o mwifiex is a "fullmac driver" which provides a comprehensive set of
client functions (laptops/embedded devices)
o only mwlwifi supports Marvell AP chip 886X series
NOTE: Users with Marvell 88W8897 chipsets currently should enable
(CONFIG=Y or M) either CONFIG_MWIFIEX or CONFIG_MWLWIFI, NOT BOTH.
mwlwifi driver leveraged code from existing MWL8K driver in the
following areas:
- 802.11n setting for mac80211
- Functions needed to hook up to mac80211
- Interactions with mac80211 to establish BA streams
- Partial firmware APIs, some data fields
- Method to pass Rx packets to mac80211 except 11ac rates
In addition, mwlwifi driver supports:
- future scalability and future development (refactored source code)
- Marvell 802.11ac chipsets, including combo BT devices
- 802.11ac related settings and functions
- concurrent AP+STA functionalities with single firmware per chip
- firmware APIs for the supported chipset
- communicating new mac80211 settings to firmware
- Different TX/RX datapath where applicable
- A-MSDU and A-MPDU
- Refined the code to establish BA streams
Signed-off-by: David Lin <dlin@marvell.com>
---
MAINTAINERS | 6 +
drivers/net/wireless/marvell/Kconfig | 1 +
drivers/net/wireless/marvell/Makefile | 1 +
drivers/net/wireless/marvell/mwlwifi/Kconfig | 23 +
drivers/net/wireless/marvell/mwlwifi/Makefile | 13 +
drivers/net/wireless/marvell/mwlwifi/debugfs.c | 830 +++++++
drivers/net/wireless/marvell/mwlwifi/debugfs.h | 24 +
drivers/net/wireless/marvell/mwlwifi/dev.h | 516 +++++
drivers/net/wireless/marvell/mwlwifi/fwcmd.c | 2837 +++++++++++++++++++++++
drivers/net/wireless/marvell/mwlwifi/fwcmd.h | 223 ++
drivers/net/wireless/marvell/mwlwifi/fwdl.c | 186 ++
drivers/net/wireless/marvell/mwlwifi/fwdl.h | 25 +
drivers/net/wireless/marvell/mwlwifi/hostcmd.h | 913 ++++++++
drivers/net/wireless/marvell/mwlwifi/isr.c | 172 ++
drivers/net/wireless/marvell/mwlwifi/isr.h | 27 +
drivers/net/wireless/marvell/mwlwifi/mac80211.c | 719 ++++++
drivers/net/wireless/marvell/mwlwifi/main.c | 840 +++++++
drivers/net/wireless/marvell/mwlwifi/rx.c | 513 ++++
drivers/net/wireless/marvell/mwlwifi/rx.h | 25 +
drivers/net/wireless/marvell/mwlwifi/sysadpt.h | 83 +
drivers/net/wireless/marvell/mwlwifi/thermal.c | 182 ++
drivers/net/wireless/marvell/mwlwifi/thermal.h | 42 +
drivers/net/wireless/marvell/mwlwifi/tx.c | 1250 ++++++++++
drivers/net/wireless/marvell/mwlwifi/tx.h | 37 +
24 files changed, 9488 insertions(+)
create mode 100644 drivers/net/wireless/marvell/mwlwifi/Kconfig
create mode 100644 drivers/net/wireless/marvell/mwlwifi/Makefile
create mode 100644 drivers/net/wireless/marvell/mwlwifi/debugfs.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/debugfs.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/dev.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/fwcmd.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/fwcmd.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/fwdl.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/fwdl.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/hostcmd.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/isr.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/isr.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/mac80211.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/main.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/rx.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/rx.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/sysadpt.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/thermal.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/thermal.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/tx.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/tx.h
diff --git a/MAINTAINERS b/MAINTAINERS
index e773ad5..b7410b1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7624,6 +7624,12 @@ L: linux-wireless@vger.kernel.org
S: Odd Fixes
F: drivers/net/wireless/marvell/mwl8k.c
+MARVELL MWLWIFI WIRELESS DRIVER
+M: David Lin <dlin@marvell.com>
+L: linux-wireless@vger.kernel.org
+S: Maintained
+F: drivers/net/wireless/marvell/mwlwifi/
+
MARVELL SOC MMC/SD/SDIO CONTROLLER DRIVER
M: Nicolas Pitre <nico@fluxnic.net>
S: Odd Fixes
diff --git a/drivers/net/wireless/marvell/Kconfig b/drivers/net/wireless/marvell/Kconfig
index 4938c7e..c7c7e0e4 100644
--- a/drivers/net/wireless/marvell/Kconfig
+++ b/drivers/net/wireless/marvell/Kconfig
@@ -14,6 +14,7 @@ if WLAN_VENDOR_MARVELL
source "drivers/net/wireless/marvell/libertas/Kconfig"
source "drivers/net/wireless/marvell/libertas_tf/Kconfig"
source "drivers/net/wireless/marvell/mwifiex/Kconfig"
+source "drivers/net/wireless/marvell/mwlwifi/Kconfig"
config MWL8K
tristate "Marvell 88W8xxx PCI/PCIe Wireless support"
diff --git a/drivers/net/wireless/marvell/Makefile b/drivers/net/wireless/marvell/Makefile
index 1b0a7d2..04dff33 100644
--- a/drivers/net/wireless/marvell/Makefile
+++ b/drivers/net/wireless/marvell/Makefile
@@ -2,5 +2,6 @@ obj-$(CONFIG_LIBERTAS) += libertas/
obj-$(CONFIG_LIBERTAS_THINFIRM) += libertas_tf/
obj-$(CONFIG_MWIFIEX) += mwifiex/
+obj-$(CONFIG_MWLWIFI) += mwlwifi/
obj-$(CONFIG_MWL8K) += mwl8k.o
diff --git a/drivers/net/wireless/marvell/mwlwifi/Kconfig b/drivers/net/wireless/marvell/mwlwifi/Kconfig
new file mode 100644
index 0000000..a9bcb9c
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/Kconfig
@@ -0,0 +1,23 @@
+config MWLWIFI
+ tristate "Marvell Avastar 88W8864/88W8897 PCIe driver (mac80211 compatible)"
+ depends on PCI && MAC80211
+ select FW_LOADER
+ ---help---
+ Select to build the driver supporting the:
+
+ Marvell Wireless Wi-Fi 88W8864 modules
+ Marvell Wireless Wi-Fi 88W8897 modules
+
+ This driver uses the kernel's mac80211 subsystem.
+
+ If you want to compile the driver as a module (= code which can be
+ inserted in and removed from the running kernel whenever you want),
+ say M here and read <file:Documentation/kbuild/modules.txt>. The
+ module will be called mwlwifi.
+
+ NOTE: Selecting this driver may cause conflict with MWIFIEX driver
+ that also operates on the same part number 88W8897. Users should
+ select either MWIFIEX or MWLWIFI, not both. MWIFIEX is fullmac,
+ supporting more comprehensive client functions for laptops/embedded
+ devices. MWLWIFI is mac80211-based for full AP/Wireless Bridge.
+
diff --git a/drivers/net/wireless/marvell/mwlwifi/Makefile b/drivers/net/wireless/marvell/mwlwifi/Makefile
new file mode 100644
index 0000000..37af74f
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/Makefile
@@ -0,0 +1,13 @@
+obj-$(CONFIG_MWLWIFI) += mwlwifi.o
+
+mwlwifi-objs += main.o
+mwlwifi-objs += mac80211.o
+mwlwifi-objs += fwdl.o
+mwlwifi-objs += fwcmd.o
+mwlwifi-objs += tx.o
+mwlwifi-objs += rx.o
+mwlwifi-objs += isr.o
+mwlwifi-$(CONFIG_THERMAL) += thermal.o
+mwlwifi-$(CONFIG_DEBUG_FS) += debugfs.o
+
+ccflags-y += -D__CHECK_ENDIAN__
diff --git a/drivers/net/wireless/marvell/mwlwifi/debugfs.c b/drivers/net/wireless/marvell/mwlwifi/debugfs.c
new file mode 100644
index 0000000..631ce6a
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/debugfs.c
@@ -0,0 +1,830 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file implements debug fs related functions. */
+
+#include <linux/debugfs.h>
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "hostcmd.h"
+#include "fwcmd.h"
+#include "thermal.h"
+#include "debugfs.h"
+
+#define MWLWIFI_DEBUGFS_ADD_FILE(name) do { \
+ if (!debugfs_create_file(#name, 0644, priv->debugfs_phy, \
+ priv, &mwl_debugfs_##name##_fops)) \
+ return; \
+} while (0)
+
+#define MWLWIFI_DEBUGFS_FILE_OPS(name) \
+static const struct file_operations mwl_debugfs_##name##_fops = { \
+ .read = mwl_debugfs_##name##_read, \
+ .write = mwl_debugfs_##name##_write, \
+ .open = simple_open, \
+}
+
+#define MWLWIFI_DEBUGFS_FILE_READ_OPS(name) \
+static const struct file_operations mwl_debugfs_##name##_fops = { \
+ .read = mwl_debugfs_##name##_read, \
+ .open = simple_open, \
+}
+
+#define MWLWIFI_DEBUGFS_FILE_WRITE_OPS(name) \
+static const struct file_operations mwl_debugfs_##name##_fops = { \
+ .write = mwl_debugfs_##name##_write, \
+ .open = simple_open, \
+}
+
+static void dump_data(char *p, int size, int *len, u8 *data,
+ int data_len, char *title)
+{
+ int cur_byte = 0;
+ int i;
+
+ *len += scnprintf(p + *len, size - *len, "%s\n", title);
+
+ for (cur_byte = 0; cur_byte < data_len; cur_byte += 8) {
+ if ((cur_byte + 8) < data_len) {
+ for (i = 0; i < 8; i++)
+ *len += scnprintf(p + *len, size - *len,
+ "0x%02x ",
+ *(data + cur_byte + i));
+ *len += scnprintf(p + *len, size - *len, "\n");
+ } else {
+ for (i = 0; i < (data_len - cur_byte); i++)
+ *len += scnprintf(p + *len, size - *len,
+ "0x%02x ",
+ *(data + cur_byte + i));
+ *len += scnprintf(p + *len, size - *len, "\n");
+ break;
+ }
+ }
+}
+
+static ssize_t mwl_debugfs_info_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ ssize_t ret;
+
+ if (!p)
+ return -ENOMEM;
+
+ len += scnprintf(p + len, size - len, "\n");
+ len += scnprintf(p + len, size - len,
+ "driver name: %s\n", MWL_DRV_NAME);
+ len += scnprintf(p + len, size - len, "chip type: %s\n",
+ (priv->chip_type == MWL8864) ? "88W8864" : "88W8897");
+ len += scnprintf(p + len, size - len,
+ "hw version: %X\n", priv->hw_data.hw_version);
+ len += scnprintf(p + len, size - len, "firmware version: 0x%08x\n",
+ priv->hw_data.fw_release_num);
+ len += scnprintf(p + len, size - len,
+ "power table loaded from dts: %s\n",
+ priv->forbidden_setting ? "no" : "yes");
+ len += scnprintf(p + len, size - len, "firmware region code: 0x%x\n",
+ priv->fw_region_code);
+ len += scnprintf(p + len, size - len,
+ "mac address: %pM\n", priv->hw_data.mac_addr);
+ len += scnprintf(p + len, size - len,
+ "2g: %s\n", priv->disable_2g ? "disable" : "enable");
+ len += scnprintf(p + len, size - len,
+ "5g: %s\n", priv->disable_5g ? "disable" : "enable");
+ len += scnprintf(p + len, size - len, "antenna: %d %d\n",
+ (priv->antenna_tx == ANTENNA_TX_4_AUTO) ? 4 : 2,
+ (priv->antenna_rx == ANTENNA_TX_4_AUTO) ? 4 : 2);
+ len += scnprintf(p + len, size - len, "irq number: %d\n", priv->irq);
+ len += scnprintf(p + len, size - len, "iobase0: %p\n", priv->iobase0);
+ len += scnprintf(p + len, size - len, "iobase1: %p\n", priv->iobase1);
+ len += scnprintf(p + len, size - len,
+ "tx limit: %d\n", priv->txq_limit);
+ len += scnprintf(p + len, size - len,
+ "rx limit: %d\n", priv->recv_limit);
+ len += scnprintf(p + len, size - len, "ap macid support: %08x\n",
+ priv->ap_macids_supported);
+ len += scnprintf(p + len, size - len, "sta macid support: %08x\n",
+ priv->sta_macids_supported);
+ len += scnprintf(p + len, size - len,
+ "macid used: %08x\n", priv->macids_used);
+ len += scnprintf(p + len, size - len,
+ "qe trigger number: %d\n", priv->qe_trigger_num);
+ len += scnprintf(p + len, size - len, "\n");
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+ free_page(page);
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_vif_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ struct mwl_vif *mwl_vif;
+ struct ieee80211_vif *vif;
+ char ssid[IEEE80211_MAX_SSID_LEN + 1];
+ ssize_t ret;
+
+ if (!p)
+ return -ENOMEM;
+
+ len += scnprintf(p + len, size - len, "\n");
+ spin_lock_bh(&priv->vif_lock);
+ list_for_each_entry(mwl_vif, &priv->vif_list, list) {
+ vif = container_of((char *)mwl_vif, struct ieee80211_vif,
+ drv_priv[0]);
+ len += scnprintf(p + len, size - len,
+ "macid: %d\n", mwl_vif->macid);
+ switch (vif->type) {
+ case NL80211_IFTYPE_AP:
+ len += scnprintf(p + len, size - len, "type: ap\n");
+ memcpy(ssid, vif->bss_conf.ssid,
+ vif->bss_conf.ssid_len);
+ ssid[vif->bss_conf.ssid_len] = 0;
+ len += scnprintf(p + len, size - len,
+ "ssid: %s\n", ssid);
+ len += scnprintf(p + len, size - len,
+ "mac address: %pM\n", mwl_vif->bssid);
+ break;
+ case NL80211_IFTYPE_STATION:
+ len += scnprintf(p + len, size - len, "type: sta\n");
+ len += scnprintf(p + len, size - len,
+ "mac address: %pM\n",
+ mwl_vif->sta_mac);
+ break;
+ default:
+ len += scnprintf(p + len, size - len,
+ "type: unknown\n");
+ break;
+ }
+ len += scnprintf(p + len, size - len, "hw_crypto_enabled: %s\n",
+ mwl_vif->is_hw_crypto_enabled ?
+ "true" : "false");
+ len += scnprintf(p + len, size - len,
+ "key idx: %d\n", mwl_vif->keyidx);
+ len += scnprintf(p + len, size - len,
+ "IV: %08x%04x\n", mwl_vif->iv32,
+ mwl_vif->iv16);
+ dump_data(p, size, &len, mwl_vif->beacon_info.ie_wmm_ptr,
+ mwl_vif->beacon_info.ie_wmm_len, "WMM:");
+ dump_data(p, size, &len, mwl_vif->beacon_info.ie_rsn_ptr,
+ mwl_vif->beacon_info.ie_rsn_len, "RSN:");
+ dump_data(p, size, &len, mwl_vif->beacon_info.ie_rsn48_ptr,
+ mwl_vif->beacon_info.ie_rsn48_len, "RSN48:");
+ dump_data(p, size, &len, mwl_vif->beacon_info.ie_ht_ptr,
+ mwl_vif->beacon_info.ie_ht_len, "HT:");
+ dump_data(p, size, &len, mwl_vif->beacon_info.ie_vht_ptr,
+ mwl_vif->beacon_info.ie_vht_len, "VHT:");
+ len += scnprintf(p + len, size - len, "\n");
+ }
+ spin_unlock_bh(&priv->vif_lock);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+ free_page(page);
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_sta_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ struct mwl_sta *sta_info;
+ struct ieee80211_sta *sta;
+ ssize_t ret;
+
+ if (!p)
+ return -ENOMEM;
+
+ len += scnprintf(p + len, size - len, "\n");
+ spin_lock_bh(&priv->sta_lock);
+ list_for_each_entry(sta_info, &priv->sta_list, list) {
+ sta = container_of((char *)sta_info, struct ieee80211_sta,
+ drv_priv[0]);
+ len += scnprintf(p + len, size - len,
+ "mac address: %pM\n", sta->addr);
+ len += scnprintf(p + len, size - len, "aid: %u\n", sta->aid);
+ len += scnprintf(p + len, size - len, "ampdu: %s\n",
+ sta_info->is_ampdu_allowed ? "true" : "false");
+ len += scnprintf(p + len, size - len, "amsdu: %s\n",
+ sta_info->is_amsdu_allowed ? "true" : "false");
+ if (sta_info->is_amsdu_allowed) {
+ len += scnprintf(p + len, size - len,
+ "amsdu cap: 0x%02x\n",
+ sta_info->amsdu_ctrl.cap);
+ }
+ if (sta->ht_cap.ht_supported) {
+ len += scnprintf(p + len, size - len,
+ "ht_cap: 0x%04x, ampdu: %02x, %02x\n",
+ sta->ht_cap.cap,
+ sta->ht_cap.ampdu_factor,
+ sta->ht_cap.ampdu_density);
+ len += scnprintf(p + len, size - len,
+ "rx_mask: 0x%02x, %02x, %02x, %02x\n",
+ sta->ht_cap.mcs.rx_mask[0],
+ sta->ht_cap.mcs.rx_mask[1],
+ sta->ht_cap.mcs.rx_mask[2],
+ sta->ht_cap.mcs.rx_mask[3]);
+ }
+ if (sta->vht_cap.vht_supported) {
+ len += scnprintf(p + len, size - len,
+ "vht_cap: 0x%08x, mcs: %02x, %02x\n",
+ sta->vht_cap.cap,
+ sta->vht_cap.vht_mcs.rx_mcs_map,
+ sta->vht_cap.vht_mcs.tx_mcs_map);
+ }
+ len += scnprintf(p + len, size - len, "rx_bw: %d, rx_nss: %d\n",
+ sta->bandwidth, sta->rx_nss);
+ len += scnprintf(p + len, size - len,
+ "tdls: %d, tdls_init: %d\n",
+ sta->tdls, sta->tdls_initiator);
+ len += scnprintf(p + len, size - len, "wme: %d, mfp: %d\n",
+ sta->wme, sta->mfp);
+ len += scnprintf(p + len, size - len, "IV: %08x%04x\n",
+ sta_info->iv32, sta_info->iv16);
+ len += scnprintf(p + len, size - len, "\n");
+ }
+ spin_unlock_bh(&priv->sta_lock);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+ free_page(page);
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_ampdu_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ struct mwl_ampdu_stream *stream;
+ int i;
+ struct mwl_sta *sta_info;
+ struct ieee80211_sta *sta;
+ ssize_t ret;
+
+ if (!p)
+ return -ENOMEM;
+
+ len += scnprintf(p + len, size - len, "\n");
+ spin_lock_bh(&priv->stream_lock);
+ for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) {
+ stream = &priv->ampdu[i];
+ len += scnprintf(p + len, size - len, "stream: %d\n", i);
+ len += scnprintf(p + len, size - len, "idx: %u\n", stream->idx);
+ len += scnprintf(p + len, size - len,
+ "state: %u\n", stream->state);
+ if (stream->sta) {
+ len += scnprintf(p + len, size - len,
+ "mac address: %pM\n",
+ stream->sta->addr);
+ len += scnprintf(p + len, size - len,
+ "tid: %u\n", stream->tid);
+ }
+ }
+ spin_unlock_bh(&priv->stream_lock);
+ spin_lock_bh(&priv->sta_lock);
+ list_for_each_entry(sta_info, &priv->sta_list, list) {
+ for (i = 0; i < MWL_MAX_TID; i++) {
+ if (sta_info->check_ba_failed[i]) {
+ sta = container_of((char *)sta_info,
+ struct ieee80211_sta,
+ drv_priv[0]);
+ len += scnprintf(p + len, size - len,
+ "%pM(%d): %d\n",
+ sta->addr, i,
+ sta_info->check_ba_failed[i]);
+ }
+ }
+ }
+ spin_unlock_bh(&priv->sta_lock);
+ len += scnprintf(p + len, size - len, "\n");
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+ free_page(page);
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_device_pwrtbl_read(struct file *file,
+ char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ int i, j;
+ ssize_t ret;
+
+ if (!p)
+ return -ENOMEM;
+
+ len += scnprintf(p + len, size - len, "\n");
+ len += scnprintf(p + len, size - len,
+ "power table loaded from dts: %s\n",
+ priv->forbidden_setting ? "no" : "yes");
+ len += scnprintf(p + len, size - len, "firmware region code: 0x%x\n",
+ priv->fw_region_code);
+ len += scnprintf(p + len, size - len, "number of channel: %d\n",
+ priv->number_of_channels);
+ for (i = 0; i < priv->number_of_channels; i++) {
+ len += scnprintf(p + len, size - len, "%3d ",
+ priv->device_pwr_tbl[i].channel);
+ for (j = 0; j < SYSADPT_TX_POWER_LEVEL_TOTAL; j++)
+ len += scnprintf(p + len, size - len, "%3d ",
+ priv->device_pwr_tbl[i].tx_pwr[j]);
+ len += scnprintf(p + len, size - len, "%3d ",
+ priv->device_pwr_tbl[i].dfs_capable);
+ len += scnprintf(p + len, size - len, "%3d ",
+ priv->device_pwr_tbl[i].ax_ant);
+ len += scnprintf(p + len, size - len, "%3d\n",
+ priv->device_pwr_tbl[i].cdd);
+ }
+ len += scnprintf(p + len, size - len, "\n");
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+ free_page(page);
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_tx_desc_read(struct file *file,
+ char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ struct mwl_desc_data *desc;
+ int i, num, write_item = -1, free_item = -1;
+ ssize_t ret;
+
+ spin_lock_bh(&priv->tx_desc_lock);
+ num = priv->tx_desc_num;
+ desc = &priv->desc_data[num];
+ len += scnprintf(p + len, size - len, "num: %i fw_desc_cnt:%i\n",
+ num, priv->fw_desc_cnt[num]);
+ for (i = 0; i < SYSADPT_MAX_NUM_TX_DESC; i++) {
+ len += scnprintf(p + len, size - len, "%3i %x\n", i,
+ desc->tx_hndl[i].pdesc->status);
+ if (desc->pnext_tx_hndl == &desc->tx_hndl[i])
+ write_item = i;
+ if (desc->pstale_tx_hndl == &desc->tx_hndl[i])
+ free_item = i;
+ }
+ len += scnprintf(p + len, size - len, "next:%i stale:%i\n",
+ write_item, free_item);
+ len += scnprintf(p + len, size - len, "\n");
+ spin_unlock_bh(&priv->tx_desc_lock);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+ free_page(page);
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_tx_desc_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ int tx_desc_num = 0;
+ ssize_t ret;
+
+ if (!buf)
+ return -ENOMEM;
+
+ if (copy_from_user(buf, ubuf, buf_size)) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ if (kstrtoint(buf, 0, &tx_desc_num)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if ((tx_desc_num < 0) || (tx_desc_num >= SYSADPT_NUM_OF_DESC_DATA)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ priv->tx_desc_num = tx_desc_num;
+ ret = count;
+
+err:
+ free_page(addr);
+ return ret;
+}
+
+static ssize_t mwl_debugfs_dfs_channel_read(struct file *file,
+ char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ struct ieee80211_supported_band *sband;
+ struct ieee80211_channel *channel;
+ int i;
+ ssize_t ret;
+
+ if (!p)
+ return -ENOMEM;
+
+ sband = priv->hw->wiphy->bands[NL80211_BAND_5GHZ];
+ if (!sband)
+ return -EINVAL;
+
+ len += scnprintf(p + len, size - len, "\n");
+ for (i = 0; i < sband->n_channels; i++) {
+ channel = &sband->channels[i];
+ if (channel->flags & IEEE80211_CHAN_RADAR) {
+ len += scnprintf(p + len, size - len,
+ "%d(%d): flags: %08x dfs_state: %d\n",
+ channel->hw_value,
+ channel->center_freq,
+ channel->flags, channel->dfs_state);
+ len += scnprintf(p + len, size - len,
+ "cac timer: %d ms\n",
+ channel->dfs_cac_ms);
+ }
+ }
+ len += scnprintf(p + len, size - len, "\n");
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+ free_page(page);
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_dfs_channel_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ struct ieee80211_supported_band *sband;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ int dfs_state = 0;
+ int cac_time = -1;
+ struct ieee80211_channel *channel;
+ int i;
+ ssize_t ret;
+
+ if (!buf)
+ return -ENOMEM;
+
+ sband = priv->hw->wiphy->bands[NL80211_BAND_5GHZ];
+ if (!sband) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (copy_from_user(buf, ubuf, buf_size)) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ ret = sscanf(buf, "%d %d", &dfs_state, &cac_time);
+
+ if ((ret < 1) || (ret > 2)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ for (i = 0; i < sband->n_channels; i++) {
+ channel = &sband->channels[i];
+ if (channel->flags & IEEE80211_CHAN_RADAR) {
+ channel->dfs_state = dfs_state;
+ if (cac_time != -1)
+ channel->dfs_cac_ms = cac_time * 1000;
+ }
+ }
+ ret = count;
+
+err:
+ free_page(addr);
+ return ret;
+}
+
+static ssize_t mwl_debugfs_dfs_radar_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ ssize_t ret;
+
+ if (!p)
+ return -ENOMEM;
+
+ len += scnprintf(p + len, size - len, "\n");
+ len += scnprintf(p + len, size - len,
+ "csa_active: %d\n", priv->csa_active);
+ len += scnprintf(p + len, size - len,
+ "dfs_region: %d\n", priv->dfs_region);
+ len += scnprintf(p + len, size - len,
+ "chirp_count_min: %d\n", priv->dfs_chirp_count_min);
+ len += scnprintf(p + len, size - len, "chirp_time_interval: %d\n",
+ priv->dfs_chirp_time_interval);
+ len += scnprintf(p + len, size - len,
+ "pw_filter: %d\n", priv->dfs_pw_filter);
+ len += scnprintf(p + len, size - len,
+ "min_num_radar: %d\n", priv->dfs_min_num_radar);
+ len += scnprintf(p + len, size - len,
+ "min_pri_count: %d\n", priv->dfs_min_pri_count);
+ len += scnprintf(p + len, size - len, "\n");
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+ free_page(page);
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_dfs_radar_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+
+ wiphy_info(priv->hw->wiphy, "simulate radar detected\n");
+ ieee80211_radar_detected(priv->hw);
+
+ return count;
+}
+
+static ssize_t mwl_debugfs_thermal_read(struct file *file,
+ char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ ssize_t ret;
+
+ if (!p)
+ return -ENOMEM;
+
+ mwl_fwcmd_get_temp(priv->hw, &priv->temperature);
+
+ len += scnprintf(p + len, size - len, "\n");
+ len += scnprintf(p + len, size - len, "quiet period: %d\n",
+ priv->quiet_period);
+ len += scnprintf(p + len, size - len, "throttle state: %d\n",
+ priv->throttle_state);
+ len += scnprintf(p + len, size - len, "temperature: %d\n",
+ priv->temperature);
+ len += scnprintf(p + len, size - len, "\n");
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+ free_page(page);
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_thermal_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ int throttle_state;
+ ssize_t ret;
+
+ if (!buf)
+ return -ENOMEM;
+
+ if (copy_from_user(buf, ubuf, buf_size)) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ if (kstrtoint(buf, 0, &throttle_state)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (throttle_state > SYSADPT_THERMAL_THROTTLE_MAX) {
+ wiphy_warn(priv->hw->wiphy,
+ "throttle state %d is exceeding the limit %d\n",
+ throttle_state, SYSADPT_THERMAL_THROTTLE_MAX);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ priv->throttle_state = throttle_state;
+ mwl_thermal_set_throttling(priv);
+ ret = count;
+
+err:
+ free_page(addr);
+ return ret;
+}
+
+static int mwl_debugfs_reg_access(struct mwl_priv *priv, bool write)
+{
+ struct ieee80211_hw *hw = priv->hw;
+ u8 set;
+ u32 *addr_val;
+ int ret = 0;
+
+ set = write ? WL_SET : WL_GET;
+
+ switch (priv->reg_type) {
+ case MWL_ACCESS_ADDR0:
+ if (set == WL_GET)
+ priv->reg_value =
+ readl(priv->iobase0 + priv->reg_offset);
+ else
+ writel(priv->reg_value,
+ priv->iobase0 + priv->reg_offset);
+ break;
+ case MWL_ACCESS_ADDR1:
+ if (set == WL_GET)
+ priv->reg_value =
+ readl(priv->iobase1 + priv->reg_offset);
+ else
+ writel(priv->reg_value,
+ priv->iobase1 + priv->reg_offset);
+ break;
+ case MWL_ACCESS_ADDR:
+ addr_val = kmalloc(64 * sizeof(u32), GFP_KERNEL);
+ if (addr_val) {
+ memset(addr_val, 0, 64 * sizeof(u32));
+ addr_val[0] = priv->reg_value;
+ ret = mwl_fwcmd_get_addr_value(hw, priv->reg_offset,
+ 4, addr_val, set);
+ if ((!ret) && (set == WL_GET))
+ priv->reg_value = addr_val[0];
+ kfree(addr_val);
+ } else {
+ ret = -ENOMEM;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static ssize_t mwl_debugfs_regrdwr_read(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long page = get_zeroed_page(GFP_KERNEL);
+ char *p = (char *)page;
+ int len = 0, size = PAGE_SIZE;
+ int ret = 0;
+
+ if (!p)
+ return -ENOMEM;
+
+ if (!priv->reg_type) {
+ /* No command has been given */
+ len += scnprintf(p + len, size - len, "0");
+ goto none;
+ }
+
+ /* Set command has been given */
+ if (priv->reg_value != UINT_MAX) {
+ ret = mwl_debugfs_reg_access(priv, true);
+ goto done;
+ }
+ /* Get command has been given */
+ ret = mwl_debugfs_reg_access(priv, false);
+
+done:
+ if (!ret)
+ len += scnprintf(p + len, size - len, "%u 0x%08x 0x%08x\n",
+ priv->reg_type, priv->reg_offset,
+ priv->reg_value);
+ else
+ len += scnprintf(p + len, size - len,
+ "error: %d(%u 0x%08x 0x%08x)\n",
+ ret, priv->reg_type, priv->reg_offset,
+ priv->reg_value);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len);
+
+none:
+
+ free_page(page);
+ return ret;
+}
+
+static ssize_t mwl_debugfs_regrdwr_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ int ret;
+ u32 reg_type = 0, reg_offset = 0, reg_value = UINT_MAX;
+
+ if (!buf)
+ return -ENOMEM;
+
+ if (copy_from_user(buf, ubuf, buf_size)) {
+ ret = -EFAULT;
+ goto done;
+ }
+
+ ret = sscanf(buf, "%u %x %x", ®_type, ®_offset, ®_value);
+
+ if (!reg_type) {
+ ret = -EINVAL;
+ goto done;
+ } else {
+ priv->reg_type = reg_type;
+ priv->reg_offset = reg_offset;
+ priv->reg_value = reg_value;
+ ret = count;
+ }
+done:
+
+ free_page(addr);
+ return ret;
+}
+
+MWLWIFI_DEBUGFS_FILE_READ_OPS(info);
+MWLWIFI_DEBUGFS_FILE_READ_OPS(vif);
+MWLWIFI_DEBUGFS_FILE_READ_OPS(sta);
+MWLWIFI_DEBUGFS_FILE_READ_OPS(ampdu);
+MWLWIFI_DEBUGFS_FILE_READ_OPS(device_pwrtbl);
+MWLWIFI_DEBUGFS_FILE_OPS(tx_desc);
+MWLWIFI_DEBUGFS_FILE_OPS(dfs_channel);
+MWLWIFI_DEBUGFS_FILE_OPS(dfs_radar);
+MWLWIFI_DEBUGFS_FILE_OPS(thermal);
+MWLWIFI_DEBUGFS_FILE_OPS(regrdwr);
+
+void mwl_debugfs_init(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ if (!priv->debugfs_phy)
+ priv->debugfs_phy = debugfs_create_dir("mwlwifi",
+ hw->wiphy->debugfsdir);
+
+ if (!priv->debugfs_phy)
+ return;
+
+ MWLWIFI_DEBUGFS_ADD_FILE(info);
+ MWLWIFI_DEBUGFS_ADD_FILE(vif);
+ MWLWIFI_DEBUGFS_ADD_FILE(sta);
+ MWLWIFI_DEBUGFS_ADD_FILE(ampdu);
+ MWLWIFI_DEBUGFS_ADD_FILE(device_pwrtbl);
+ MWLWIFI_DEBUGFS_ADD_FILE(tx_desc);
+ MWLWIFI_DEBUGFS_ADD_FILE(dfs_channel);
+ MWLWIFI_DEBUGFS_ADD_FILE(dfs_radar);
+ MWLWIFI_DEBUGFS_ADD_FILE(thermal);
+ MWLWIFI_DEBUGFS_ADD_FILE(regrdwr);
+}
+
+void mwl_debugfs_remove(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ debugfs_remove(priv->debugfs_phy);
+ priv->debugfs_phy = NULL;
+}
diff --git a/drivers/net/wireless/marvell/mwlwifi/debugfs.h b/drivers/net/wireless/marvell/mwlwifi/debugfs.h
new file mode 100644
index 0000000..b4c3eb3
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/debugfs.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines debug fs related functions. */
+
+#ifndef _MWL_DEBUGFS_H_
+#define _MWL_DEBUGFS_H_
+
+void mwl_debugfs_init(struct ieee80211_hw *hw);
+void mwl_debugfs_remove(struct ieee80211_hw *hw);
+
+#endif /* _MWL_DEBUGFS_H_ */
diff --git a/drivers/net/wireless/marvell/mwlwifi/dev.h b/drivers/net/wireless/marvell/mwlwifi/dev.h
new file mode 100644
index 0000000..c7b10ac
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/dev.h
@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines device related information. */
+
+#ifndef _DEV_H_
+#define _DEV_H_
+
+#include <linux/version.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <net/mac80211.h>
+
+#define MWL_DRV_NAME KBUILD_MODNAME
+
+/* Map to 0x80000000 (Bus control) on BAR0 */
+#define MACREG_REG_H2A_INTERRUPT_EVENTS 0x00000C18 /* (From host to ARM) */
+#define MACREG_REG_H2A_INTERRUPT_CAUSE 0x00000C1C /* (From host to ARM) */
+#define MACREG_REG_H2A_INTERRUPT_MASK 0x00000C20 /* (From host to ARM) */
+#define MACREG_REG_H2A_INTERRUPT_CLEAR_SEL 0x00000C24 /* (From host to ARM) */
+#define MACREG_REG_H2A_INTERRUPT_STATUS_MASK 0x00000C28 /* (From host to ARM) */
+
+#define MACREG_REG_A2H_INTERRUPT_EVENTS 0x00000C2C /* (From ARM to host) */
+#define MACREG_REG_A2H_INTERRUPT_CAUSE 0x00000C30 /* (From ARM to host) */
+#define MACREG_REG_A2H_INTERRUPT_MASK 0x00000C34 /* (From ARM to host) */
+#define MACREG_REG_A2H_INTERRUPT_CLEAR_SEL 0x00000C38 /* (From ARM to host) */
+#define MACREG_REG_A2H_INTERRUPT_STATUS_MASK 0x00000C3C /* (From ARM to host) */
+
+/* Map to 0x80000000 on BAR1 */
+#define MACREG_REG_GEN_PTR 0x00000C10
+#define MACREG_REG_INT_CODE 0x00000C14
+
+/* Bit definition for MACREG_REG_A2H_INTERRUPT_CAUSE (A2HRIC) */
+#define MACREG_A2HRIC_BIT_TX_DONE BIT(0)
+#define MACREG_A2HRIC_BIT_RX_RDY BIT(1)
+#define MACREG_A2HRIC_BIT_OPC_DONE BIT(2)
+#define MACREG_A2HRIC_BIT_MAC_EVENT BIT(3)
+#define MACREG_A2HRIC_BIT_RX_PROBLEM BIT(4)
+#define MACREG_A2HRIC_BIT_RADIO_OFF BIT(5)
+#define MACREG_A2HRIC_BIT_RADIO_ON BIT(6)
+#define MACREG_A2HRIC_BIT_RADAR_DETECT BIT(7)
+#define MACREG_A2HRIC_BIT_ICV_ERROR BIT(8)
+#define MACREG_A2HRIC_BIT_WEAKIV_ERROR BIT(9)
+#define MACREG_A2HRIC_BIT_QUE_EMPTY BIT(10)
+#define MACREG_A2HRIC_BIT_QUE_FULL BIT(11)
+#define MACREG_A2HRIC_BIT_CHAN_SWITCH BIT(12)
+#define MACREG_A2HRIC_BIT_TX_WATCHDOG BIT(13)
+#define MACREG_A2HRIC_BA_WATCHDOG BIT(14)
+/* 15 taken by ISR_TXACK */
+#define MACREG_A2HRIC_BIT_SSU_DONE BIT(16)
+#define MACREG_A2HRIC_CONSEC_TXFAIL BIT(17)
+
+#define ISR_SRC_BITS (MACREG_A2HRIC_BIT_RX_RDY | \
+ MACREG_A2HRIC_BIT_TX_DONE | \
+ MACREG_A2HRIC_BIT_OPC_DONE | \
+ MACREG_A2HRIC_BIT_MAC_EVENT | \
+ MACREG_A2HRIC_BIT_WEAKIV_ERROR | \
+ MACREG_A2HRIC_BIT_ICV_ERROR | \
+ MACREG_A2HRIC_BIT_SSU_DONE | \
+ MACREG_A2HRIC_BIT_RADAR_DETECT | \
+ MACREG_A2HRIC_BIT_CHAN_SWITCH | \
+ MACREG_A2HRIC_BIT_TX_WATCHDOG | \
+ MACREG_A2HRIC_BIT_QUE_EMPTY | \
+ MACREG_A2HRIC_BA_WATCHDOG | \
+ MACREG_A2HRIC_CONSEC_TXFAIL)
+
+#define MACREG_A2HRIC_BIT_MASK ISR_SRC_BITS
+
+/* Bit definition for MACREG_REG_H2A_INTERRUPT_CAUSE (H2ARIC) */
+#define MACREG_H2ARIC_BIT_PPA_READY BIT(0)
+#define MACREG_H2ARIC_BIT_DOOR_BELL BIT(1)
+#define MACREG_H2ARIC_BIT_PS BIT(2)
+#define MACREG_H2ARIC_BIT_PSPOLL BIT(3)
+#define ISR_RESET BIT(15)
+#define ISR_RESET_AP33 BIT(26)
+
+/* Data descriptor related constants */
+#define EAGLE_RXD_CTRL_DRIVER_OWN 0x00
+#define EAGLE_RXD_CTRL_DMA_OWN 0x80
+
+#define EAGLE_RXD_STATUS_OK 0x01
+
+#define EAGLE_TXD_STATUS_IDLE 0x00000000
+#define EAGLE_TXD_STATUS_OK 0x00000001
+#define EAGLE_TXD_STATUS_FW_OWNED 0x80000000
+
+/* Antenna control */
+#define ANTENNA_TX_4_AUTO 0
+#define ANTENNA_TX_2 3
+#define ANTENNA_RX_4_AUTO 0
+#define ANTENNA_RX_2 2
+
+/* Band related constants */
+#define BAND_24_CHANNEL_NUM 14
+#define BAND_24_RATE_NUM 13
+#define BAND_50_CHANNEL_NUM 24
+#define BAND_50_RATE_NUM 8
+
+/* vif and station */
+#define NUM_WEP_KEYS 4
+#define MWL_MAX_TID 8
+#define MWL_AMSDU_SIZE_4K 0
+#define MWL_AMSDU_SIZE_8K 1
+
+/* power init */
+#define MWL_POWER_INIT_1 1
+#define MWL_POWER_INIT_2 2
+
+enum {
+ MWL8864 = 0,
+ MWL8897,
+ MWLUNKNOWN,
+};
+
+enum {
+ AP_MODE_11AC = 0x10, /* generic 11ac indication mode */
+ AP_MODE_2_4GHZ_11AC_MIXED = 0x17,
+};
+
+enum {
+ AMPDU_NO_STREAM,
+ AMPDU_STREAM_NEW,
+ AMPDU_STREAM_IN_PROGRESS,
+ AMPDU_STREAM_ACTIVE,
+};
+
+struct mwl_chip_info {
+ const char *part_name;
+ const char *fw_image;
+ int antenna_tx;
+ int antenna_rx;
+};
+
+struct mwl_device_pwr_tbl {
+ u8 channel;
+ u8 tx_pwr[SYSADPT_TX_POWER_LEVEL_TOTAL];
+ u8 dfs_capable;
+ u8 ax_ant;
+ u8 cdd;
+};
+
+struct mwl_tx_pwr_tbl {
+ u8 channel;
+ u8 setcap;
+ u16 txantenna2;
+ u16 tx_power[SYSADPT_TX_POWER_LEVEL_TOTAL];
+ bool cdd;
+};
+
+struct mwl_hw_data {
+ u32 fw_release_num; /* MajNbr:MinNbr:SubMin:PatchLevel */
+ u8 hw_version; /* plain number indicating version */
+ u8 host_interface; /* plain number of interface */
+ u16 max_num_tx_desc; /* max number of TX descriptors */
+ u16 max_num_mc_addr; /* max number multicast addresses */
+ u16 num_antennas; /* number antennas used */
+ u16 region_code; /* region (eg. 0x10 for USA FCC) */
+ unsigned char mac_addr[ETH_ALEN]; /* well known -> AA:BB:CC:DD:EE:FF */
+};
+
+#define MWL_TX_RATE_FORMAT_MASK 0x00000003
+#define MWL_TX_RATE_BANDWIDTH_MASK 0x00000030
+#define MWL_TX_RATE_BANDWIDTH_SHIFT 4
+#define MWL_TX_RATE_SHORTGI_MASK 0x00000040
+#define MWL_TX_RATE_SHORTGI_SHIFT 6
+#define MWL_TX_RATE_RATEIDMCS_MASK 0x00007F00
+#define MWL_TX_RATE_RATEIDMCS_SHIFT 8
+
+struct mwl_tx_desc {
+ u8 data_rate;
+ u8 tx_priority;
+ __le16 qos_ctrl;
+ __le32 pkt_ptr;
+ __le16 pkt_len;
+ u8 dest_addr[ETH_ALEN];
+ __le32 pphys_next;
+ __le32 sap_pkt_info;
+ __le32 rate_info;
+ u8 type;
+ u8 xmit_control; /* bit 0: use rateinfo, bit 1: disable ampdu */
+ __le16 reserved;
+ __le32 tcpack_sn;
+ __le32 tcpack_src_dst;
+ __le32 reserved1;
+ __le32 reserved2;
+ u8 reserved3[2];
+ u8 packet_info;
+ u8 packet_id;
+ __le16 packet_len_and_retry;
+ __le16 packet_rate_info;
+ __le32 reserved4;
+ __le32 status;
+} __packed;
+
+struct mwl_tx_hndl {
+ struct sk_buff *psk_buff;
+ struct mwl_tx_desc *pdesc;
+ struct mwl_tx_hndl *pnext;
+};
+
+#define MWL_RX_RATE_FORMAT_MASK 0x0007
+#define MWL_RX_RATE_NSS_MASK 0x0018
+#define MWL_RX_RATE_NSS_SHIFT 3
+#define MWL_RX_RATE_BW_MASK 0x0060
+#define MWL_RX_RATE_BW_SHIFT 5
+#define MWL_RX_RATE_GI_MASK 0x0080
+#define MWL_RX_RATE_GI_SHIFT 7
+#define MWL_RX_RATE_RT_MASK 0xFF00
+#define MWL_RX_RATE_RT_SHIFT 8
+
+struct mwl_rx_desc {
+ __le16 pkt_len; /* total length of received data */
+ __le16 rate; /* receive rate information */
+ __le32 pphys_buff_data; /* physical address of payload data */
+ __le32 pphys_next; /* physical address of next RX desc */
+ __le16 qos_ctrl; /* received QosCtrl field variable */
+ __le16 ht_sig2; /* like name states */
+ __le32 hw_rssi_info;
+ __le32 hw_noise_floor_info;
+ u8 noise_floor;
+ u8 reserved[3];
+ u8 rssi; /* received signal strengt indication */
+ u8 status; /* status field containing USED bit */
+ u8 channel; /* channel this pkt was received on */
+ u8 rx_control; /* the control element of the desc */
+ __le32 reserved1[3];
+} __packed;
+
+struct mwl_rx_hndl {
+ struct sk_buff *psk_buff; /* associated sk_buff for Linux */
+ struct mwl_rx_desc *pdesc;
+ struct mwl_rx_hndl *pnext;
+};
+
+struct mwl_desc_data {
+ dma_addr_t pphys_tx_ring; /* ptr to first TX desc (phys.) */
+ struct mwl_tx_desc *ptx_ring; /* ptr to first TX desc (virt.) */
+ struct mwl_tx_hndl *tx_hndl;
+ struct mwl_tx_hndl *pnext_tx_hndl; /* next TX handle that can be used */
+ struct mwl_tx_hndl *pstale_tx_hndl;/* the staled TX handle */
+ dma_addr_t pphys_rx_ring; /* ptr to first RX desc (phys.) */
+ struct mwl_rx_desc *prx_ring; /* ptr to first RX desc (virt.) */
+ struct mwl_rx_hndl *rx_hndl;
+ struct mwl_rx_hndl *pnext_rx_hndl; /* next RX handle that can be used */
+ u32 wcb_base; /* FW base offset for registers */
+ u32 rx_desc_write; /* FW descriptor write position */
+ u32 rx_desc_read; /* FW descriptor read position */
+ u32 rx_buf_size; /* length of the RX buffers */
+};
+
+struct mwl_ampdu_stream {
+ struct ieee80211_sta *sta;
+ u8 tid;
+ u8 state;
+ u8 idx;
+};
+
+#ifdef CONFIG_DEBUG_FS
+#define MAC_REG_ADDR_PCI(offset) ((priv->iobase1 + 0xA000) + (offset))
+
+#define MWL_ACCESS_MAC 1
+#define MWL_ACCESS_RF 2
+#define MWL_ACCESS_BBP 3
+#define MWL_ACCESS_CAU 4
+#define MWL_ACCESS_ADDR0 5
+#define MWL_ACCESS_ADDR1 6
+#define MWL_ACCESS_ADDR 7
+#endif
+
+struct mwl_priv {
+ struct ieee80211_hw *hw;
+ struct firmware *fw_ucode;
+ bool fw_device_pwrtbl;
+ bool forbidden_setting;
+ bool regulatory_set;
+ u32 fw_region_code;
+ char fw_alpha2[2];
+ u8 number_of_channels;
+ struct mwl_device_pwr_tbl device_pwr_tbl[SYSADPT_MAX_NUM_CHANNELS];
+ int chip_type;
+
+ struct device_node *dt_node;
+ struct device_node *pwr_node;
+ bool disable_2g;
+ bool disable_5g;
+ int antenna_tx;
+ int antenna_rx;
+
+ struct mwl_tx_pwr_tbl tx_pwr_tbl[SYSADPT_MAX_NUM_CHANNELS];
+ bool cdd;
+ u16 txantenna2;
+ u8 powinited;
+ u16 max_tx_pow[SYSADPT_TX_POWER_LEVEL_TOTAL]; /* max tx power (dBm) */
+ u16 target_powers[SYSADPT_TX_POWER_LEVEL_TOTAL]; /* target powers */
+
+ struct pci_dev *pdev;
+ struct device *dev;
+ void __iomem *iobase0; /* MEM Base Address Register 0 */
+ void __iomem *iobase1; /* MEM Base Address Register 1 */
+ u32 next_bar_num;
+
+ struct mutex fwcmd_mutex; /* for firmware command */
+ unsigned short *pcmd_buf; /* pointer to CmdBuf (virtual) */
+ dma_addr_t pphys_cmd_buf; /* pointer to CmdBuf (physical) */
+ bool in_send_cmd;
+
+ int irq;
+ struct mwl_hw_data hw_data; /* Adapter HW specific info */
+
+ /* various descriptor data */
+ /* for tx descriptor data */
+ spinlock_t tx_desc_lock ____cacheline_aligned_in_smp;
+ struct mwl_desc_data desc_data[SYSADPT_NUM_OF_DESC_DATA];
+ struct sk_buff_head txq[SYSADPT_NUM_OF_DESC_DATA];
+ struct sk_buff_head delay_q;
+ /* number of descriptors owned by fw at any one time */
+ int fw_desc_cnt[SYSADPT_NUM_OF_DESC_DATA];
+
+ struct tasklet_struct tx_task;
+ struct tasklet_struct tx_done_task;
+ struct tasklet_struct rx_task;
+ struct tasklet_struct qe_task;
+ int txq_limit;
+ bool is_tx_done_schedule;
+ int recv_limit;
+ bool is_rx_schedule;
+ bool is_qe_schedule;
+ u32 qe_trigger_num;
+ unsigned long qe_trigger_time;
+
+ struct timer_list period_timer;
+
+ s8 noise; /* Most recently reported noise in dBm */
+ struct ieee80211_supported_band band_24;
+ struct ieee80211_channel channels_24[BAND_24_CHANNEL_NUM];
+ struct ieee80211_rate rates_24[BAND_24_RATE_NUM];
+ struct ieee80211_supported_band band_50;
+ struct ieee80211_channel channels_50[BAND_50_CHANNEL_NUM];
+ struct ieee80211_rate rates_50[BAND_50_RATE_NUM];
+
+ u32 ap_macids_supported;
+ u32 sta_macids_supported;
+ u32 macids_used;
+ u32 running_bsses; /* bitmap of running BSSes */
+
+ struct {
+ spinlock_t vif_lock; /* for private interface info */
+ struct list_head vif_list; /* List of interfaces. */
+ } ____cacheline_aligned_in_smp;
+
+ struct {
+ spinlock_t sta_lock; /* for private sta info */
+ struct list_head sta_list; /* List of stations */
+ } ____cacheline_aligned_in_smp;
+
+ bool radio_on;
+ bool radio_short_preamble;
+ bool wmm_enabled;
+ struct ieee80211_tx_queue_params wmm_params[SYSADPT_TX_WMM_QUEUES];
+
+ /* ampdu stream information */
+ /* for ampdu stream */
+ struct {
+ spinlock_t stream_lock; /* for BA stream */
+ struct mwl_ampdu_stream ampdu[SYSADPT_TX_AMPDU_QUEUES];
+ } ____cacheline_aligned_in_smp;
+ struct work_struct watchdog_ba_handle;
+
+ bool csa_active;
+ struct work_struct chnl_switch_handle;
+ enum nl80211_dfs_regions dfs_region;
+ u16 dfs_chirp_count_min;
+ u16 dfs_chirp_time_interval;
+ u16 dfs_pw_filter;
+ u16 dfs_min_num_radar;
+ u16 dfs_min_pri_count;
+
+ struct thermal_cooling_device *cdev;
+ u32 throttle_state;
+ u32 quiet_period;
+ int temperature;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs_phy;
+ u32 reg_type;
+ u32 reg_offset;
+ u32 reg_value;
+ int tx_desc_num;
+#endif
+};
+
+struct beacon_info {
+ bool valid;
+ u16 cap_info;
+ u8 power_constraint;
+ u8 b_rate_set[SYSADPT_MAX_DATA_RATES_G];
+ u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G];
+ u8 ie_list_ht[148];
+ u8 ie_list_vht[24];
+ u8 *ie_wmm_ptr;
+ u8 *ie_wsc_ptr;
+ u8 *ie_rsn_ptr;
+ u8 *ie_rsn48_ptr;
+ u8 *ie_ht_ptr;
+ u8 *ie_vht_ptr;
+ u8 *ie_country_ptr;
+ u8 ie_wmm_len;
+ u8 ie_wsc_len;
+ u8 ie_rsn_len;
+ u8 ie_rsn48_len;
+ u8 ie_ht_len;
+ u8 ie_vht_len;
+ u8 ie_country_len;
+};
+
+struct mwl_vif {
+ struct list_head list;
+ int macid; /* Firmware macid for this vif. */
+ u16 seqno; /* Non AMPDU sequence number assigned by driver. */
+ struct { /* Saved WEP keys */
+ u8 enabled;
+ u8 key[sizeof(struct ieee80211_key_conf) + WLAN_KEY_LEN_WEP104];
+ } wep_key_conf[NUM_WEP_KEYS];
+ u8 bssid[ETH_ALEN]; /* BSSID */
+ u8 sta_mac[ETH_ALEN]; /* Station mac address */
+ /* A flag to indicate is HW crypto is enabled for this bssid */
+ bool is_hw_crypto_enabled;
+ /* Indicate if this is station mode */
+ struct beacon_info beacon_info;
+ u16 iv16;
+ u32 iv32;
+ s8 keyidx;
+};
+
+struct mwl_tx_info {
+ unsigned long start_time;
+ u32 pkts;
+};
+
+struct mwl_amsdu_frag {
+ struct sk_buff *skb;
+ u8 *cur_pos;
+ unsigned long jiffies;
+ u8 pad;
+ u8 num;
+};
+
+struct mwl_amsdu_ctrl {
+ struct mwl_amsdu_frag frag[SYSADPT_TX_WMM_QUEUES];
+ u8 cap;
+};
+
+struct mwl_sta {
+ struct list_head list;
+ bool is_mesh_node;
+ bool is_ampdu_allowed;
+ struct mwl_tx_info tx_stats[MWL_MAX_TID];
+ u32 check_ba_failed[MWL_MAX_TID];
+ bool is_amsdu_allowed;
+ /* for amsdu aggregation */
+ struct {
+ spinlock_t amsdu_lock; /* for amsdu */
+ struct mwl_amsdu_ctrl amsdu_ctrl;
+ } ____cacheline_aligned_in_smp;
+ u16 iv16;
+ u32 iv32;
+};
+
+/* DMA header used by firmware and hardware. */
+struct mwl_dma_data {
+ __le16 fwlen;
+ struct ieee80211_hdr wh;
+ char data[0];
+} __packed;
+
+/* Transmission information to transmit a socket buffer. */
+struct mwl_tx_ctrl {
+ void *vif;
+ void *sta;
+ void *k_conf;
+ void *amsdu_pkts;
+ u8 tx_priority;
+ u8 type;
+ u16 qos_ctrl;
+ u8 xmit_control;
+};
+
+static inline struct mwl_vif *mwl_dev_get_vif(const struct ieee80211_vif *vif)
+{
+ return (struct mwl_vif *)&vif->drv_priv;
+}
+
+static inline struct mwl_sta *mwl_dev_get_sta(const struct ieee80211_sta *sta)
+{
+ return (struct mwl_sta *)&sta->drv_priv;
+}
+
+/* Defined in mac80211.c. */
+extern const struct ieee80211_ops mwl_mac80211_ops;
+
+#endif /* _DEV_H_ */
diff --git a/drivers/net/wireless/marvell/mwlwifi/fwcmd.c b/drivers/net/wireless/marvell/mwlwifi/fwcmd.c
new file mode 100644
index 0000000..9c3ccf9
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/fwcmd.c
@@ -0,0 +1,2837 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file implements firmware host command related
+ * functions.
+ */
+
+#include <linux/etherdevice.h>
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "fwcmd.h"
+#include "hostcmd.h"
+
+#define MAX_WAIT_FW_COMPLETE_ITERATIONS 2000
+#define MAX_WAIT_GET_HW_SPECS_ITERATONS 3
+
+struct cmd_header {
+ __le16 command;
+ __le16 len;
+} __packed;
+
+static bool mwl_fwcmd_chk_adapter(struct mwl_priv *priv)
+{
+ u32 regval;
+
+ regval = readl(priv->iobase1 + MACREG_REG_INT_CODE);
+
+ if (regval == 0xffffffff) {
+ wiphy_err(priv->hw->wiphy, "adapter does not exist\n");
+ return false;
+ }
+
+ return true;
+}
+
+static void mwl_fwcmd_send_cmd(struct mwl_priv *priv)
+{
+ writel(priv->pphys_cmd_buf, priv->iobase1 + MACREG_REG_GEN_PTR);
+ writel(MACREG_H2ARIC_BIT_DOOR_BELL,
+ priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS);
+}
+
+static char *mwl_fwcmd_get_cmd_string(unsigned short cmd)
+{
+ int max_entries = 0;
+ int curr_cmd = 0;
+
+ static const struct {
+ u16 cmd;
+ char *cmd_string;
+ } cmds[] = {
+ { HOSTCMD_CMD_GET_HW_SPEC, "GetHwSpecifications" },
+ { HOSTCMD_CMD_SET_HW_SPEC, "SetHwSepcifications" },
+ { HOSTCMD_CMD_802_11_GET_STAT, "80211GetStat" },
+ { HOSTCMD_CMD_802_11_RADIO_CONTROL, "80211RadioControl" },
+ { HOSTCMD_CMD_MEM_ADDR_ACCESS, "MEMAddrAccess" },
+ { HOSTCMD_CMD_802_11_TX_POWER, "80211TxPower" },
+ { HOSTCMD_CMD_802_11_RF_ANTENNA, "80211RfAntenna" },
+ { HOSTCMD_CMD_BROADCAST_SSID_ENABLE, "broadcast_ssid_enable" },
+ { HOSTCMD_CMD_SET_RF_CHANNEL, "SetRfChannel" },
+ { HOSTCMD_CMD_SET_AID, "SetAid" },
+ { HOSTCMD_CMD_SET_INFRA_MODE, "SetInfraMode" },
+ { HOSTCMD_CMD_802_11_RTS_THSD, "80211RtsThreshold" },
+ { HOSTCMD_CMD_SET_EDCA_PARAMS, "SetEDCAParams" },
+ { HOSTCMD_CMD_802_11H_DETECT_RADAR, "80211hDetectRadar" },
+ { HOSTCMD_CMD_SET_WMM_MODE, "SetWMMMode" },
+ { HOSTCMD_CMD_HT_GUARD_INTERVAL, "HtGuardInterval" },
+ { HOSTCMD_CMD_SET_FIXED_RATE, "SetFixedRate" },
+ { HOSTCMD_CMD_SET_IES, "SetInformationElements" },
+ { HOSTCMD_CMD_SET_LINKADAPT_CS_MODE, "LinkAdaptCsMode" },
+ { HOSTCMD_CMD_SET_MAC_ADDR, "SetMacAddr" },
+ { HOSTCMD_CMD_SET_RATE_ADAPT_MODE, "SetRateAdaptationMode" },
+ { HOSTCMD_CMD_GET_WATCHDOG_BITMAP, "GetWatchdogBitMap" },
+ { HOSTCMD_CMD_DEL_MAC_ADDR, "DelMacAddr" },
+ { HOSTCMD_CMD_BSS_START, "BssStart" },
+ { HOSTCMD_CMD_AP_BEACON, "SetApBeacon" },
+ { HOSTCMD_CMD_SET_NEW_STN, "SetNewStation" },
+ { HOSTCMD_CMD_SET_APMODE, "SetApMode" },
+ { HOSTCMD_CMD_SET_SWITCH_CHANNEL, "SetSwitchChannel" },
+ { HOSTCMD_CMD_UPDATE_ENCRYPTION, "UpdateEncryption" },
+ { HOSTCMD_CMD_BASTREAM, "BAStream" },
+ { HOSTCMD_CMD_SET_SPECTRUM_MGMT, "SetSpectrumMgmt" },
+ { HOSTCMD_CMD_SET_POWER_CONSTRAINT, "SetPowerConstraint" },
+ { HOSTCMD_CMD_SET_COUNTRY_CODE, "SetCountryCode" },
+ { HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL, "SetOptimizationLevel" },
+ { HOSTCMD_CMD_SET_WSC_IE, "SetWscIE" },
+ { HOSTCMD_CMD_DWDS_ENABLE, "DwdsEnable" },
+ { HOSTCMD_CMD_FW_FLUSH_TIMER, "FwFlushTimer" },
+ { HOSTCMD_CMD_SET_CDD, "SetCDD" },
+ { HOSTCMD_CMD_GET_TEMP, "GetTemp" },
+ { HOSTCMD_CMD_GET_FW_REGION_CODE, "GetFwRegionCode" },
+ { HOSTCMD_CMD_GET_DEVICE_PWR_TBL, "GetDevicePwrTbl" },
+ { HOSTCMD_CMD_QUIET_MODE, "QuietMode" },
+ };
+
+ max_entries = ARRAY_SIZE(cmds);
+
+ for (curr_cmd = 0; curr_cmd < max_entries; curr_cmd++)
+ if ((cmd & 0x7fff) == cmds[curr_cmd].cmd)
+ return cmds[curr_cmd].cmd_string;
+
+ return "unknown";
+}
+
+static int mwl_fwcmd_wait_complete(struct mwl_priv *priv, unsigned short cmd)
+{
+ unsigned int curr_iteration = MAX_WAIT_FW_COMPLETE_ITERATIONS;
+ unsigned short int_code = 0;
+
+ do {
+ int_code = le16_to_cpu(*((__le16 *)&priv->pcmd_buf[0]));
+ usleep_range(1000, 2000);
+ } while ((int_code != cmd) && (--curr_iteration));
+
+ if (curr_iteration == 0) {
+ wiphy_err(priv->hw->wiphy, "cmd 0x%04x=%s timed out\n",
+ cmd, mwl_fwcmd_get_cmd_string(cmd));
+ wiphy_err(priv->hw->wiphy, "return code: 0x%04x\n", int_code);
+ return -EIO;
+ }
+
+ usleep_range(3000, 5000);
+
+ return 0;
+}
+
+static int mwl_fwcmd_exec_cmd(struct mwl_priv *priv, unsigned short cmd)
+{
+ bool busy = false;
+
+ might_sleep();
+
+ if (!mwl_fwcmd_chk_adapter(priv)) {
+ wiphy_err(priv->hw->wiphy, "adapter does not exist\n");
+ priv->in_send_cmd = false;
+ return -EIO;
+ }
+
+ if (!priv->in_send_cmd) {
+ priv->in_send_cmd = true;
+ mwl_fwcmd_send_cmd(priv);
+ if (mwl_fwcmd_wait_complete(priv, 0x8000 | cmd)) {
+ wiphy_err(priv->hw->wiphy, "timeout: 0x%04x\n", cmd);
+ priv->in_send_cmd = false;
+ return -EIO;
+ }
+ } else {
+ wiphy_warn(priv->hw->wiphy,
+ "previous command is still running\n");
+ busy = true;
+ }
+
+ if (!busy)
+ priv->in_send_cmd = false;
+
+ return 0;
+}
+
+static int mwl_fwcmd_802_11_radio_control(struct mwl_priv *priv,
+ bool enable, bool force)
+{
+ struct hostcmd_cmd_802_11_radio_control *pcmd;
+
+ if (enable == priv->radio_on && !force)
+ return 0;
+
+ pcmd = (struct hostcmd_cmd_802_11_radio_control *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RADIO_CONTROL);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(WL_SET);
+ pcmd->control = cpu_to_le16(priv->radio_short_preamble ?
+ WL_AUTO_PREAMBLE : WL_LONG_PREAMBLE);
+ pcmd->radio_on = cpu_to_le16(enable ? WL_ENABLE : WL_DISABLE);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RADIO_CONTROL)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ priv->radio_on = enable;
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+static int mwl_fwcmd_get_tx_powers(struct mwl_priv *priv, u16 *powlist, u16 ch,
+ u16 band, u16 width, u16 sub_ch)
+{
+ struct hostcmd_cmd_802_11_tx_power *pcmd;
+ int i;
+
+ pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_TX_POWER);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_GET_LIST);
+ pcmd->ch = cpu_to_le16(ch);
+ pcmd->bw = cpu_to_le16(width);
+ pcmd->band = cpu_to_le16(band);
+ pcmd->sub_ch = cpu_to_le16(sub_ch);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_TX_POWER)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++)
+ powlist[i] = le16_to_cpu(pcmd->power_level_list[i]);
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+static int mwl_fwcmd_set_tx_powers(struct mwl_priv *priv, u16 txpow[],
+ u8 action, u16 ch, u16 band,
+ u16 width, u16 sub_ch)
+{
+ struct hostcmd_cmd_802_11_tx_power *pcmd;
+ int i;
+
+ pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_TX_POWER);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(action);
+ pcmd->ch = cpu_to_le16(ch);
+ pcmd->bw = cpu_to_le16(width);
+ pcmd->band = cpu_to_le16(band);
+ pcmd->sub_ch = cpu_to_le16(sub_ch);
+
+ for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++)
+ pcmd->power_level_list[i] = cpu_to_le16(txpow[i]);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_TX_POWER)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+static u8 mwl_fwcmd_get_80m_pri_chnl(u8 channel)
+{
+ u8 act_primary = ACT_PRIMARY_CHAN_0;
+
+ switch (channel) {
+ case 36:
+ act_primary = ACT_PRIMARY_CHAN_0;
+ break;
+ case 40:
+ act_primary = ACT_PRIMARY_CHAN_1;
+ break;
+ case 44:
+ act_primary = ACT_PRIMARY_CHAN_2;
+ break;
+ case 48:
+ act_primary = ACT_PRIMARY_CHAN_3;
+ break;
+ case 52:
+ act_primary = ACT_PRIMARY_CHAN_0;
+ break;
+ case 56:
+ act_primary = ACT_PRIMARY_CHAN_1;
+ break;
+ case 60:
+ act_primary = ACT_PRIMARY_CHAN_2;
+ break;
+ case 64:
+ act_primary = ACT_PRIMARY_CHAN_3;
+ break;
+ case 100:
+ act_primary = ACT_PRIMARY_CHAN_0;
+ break;
+ case 104:
+ act_primary = ACT_PRIMARY_CHAN_1;
+ break;
+ case 108:
+ act_primary = ACT_PRIMARY_CHAN_2;
+ break;
+ case 112:
+ act_primary = ACT_PRIMARY_CHAN_3;
+ break;
+ case 116:
+ act_primary = ACT_PRIMARY_CHAN_0;
+ break;
+ case 120:
+ act_primary = ACT_PRIMARY_CHAN_1;
+ break;
+ case 124:
+ act_primary = ACT_PRIMARY_CHAN_2;
+ break;
+ case 128:
+ act_primary = ACT_PRIMARY_CHAN_3;
+ break;
+ case 132:
+ act_primary = ACT_PRIMARY_CHAN_0;
+ break;
+ case 136:
+ act_primary = ACT_PRIMARY_CHAN_1;
+ break;
+ case 140:
+ act_primary = ACT_PRIMARY_CHAN_2;
+ break;
+ case 144:
+ act_primary = ACT_PRIMARY_CHAN_3;
+ break;
+ case 149:
+ act_primary = ACT_PRIMARY_CHAN_0;
+ break;
+ case 153:
+ act_primary = ACT_PRIMARY_CHAN_1;
+ break;
+ case 157:
+ act_primary = ACT_PRIMARY_CHAN_2;
+ break;
+ case 161:
+ act_primary = ACT_PRIMARY_CHAN_3;
+ break;
+ }
+
+ return act_primary;
+}
+
+static void mwl_fwcmd_parse_beacon(struct mwl_priv *priv,
+ struct mwl_vif *vif, u8 *beacon, int len)
+{
+ struct ieee80211_mgmt *mgmt;
+ struct beacon_info *beacon_info;
+ int baselen;
+ u8 *pos;
+ size_t left;
+ bool elem_parse_failed;
+
+ mgmt = (struct ieee80211_mgmt *)beacon;
+
+ baselen = (u8 *)mgmt->u.beacon.variable - (u8 *)mgmt;
+ if (baselen > len)
+ return;
+
+ beacon_info = &vif->beacon_info;
+ memset(beacon_info, 0, sizeof(struct beacon_info));
+ beacon_info->valid = false;
+ beacon_info->ie_ht_ptr = &beacon_info->ie_list_ht[0];
+ beacon_info->ie_vht_ptr = &beacon_info->ie_list_vht[0];
+
+ beacon_info->cap_info = le16_to_cpu(mgmt->u.beacon.capab_info);
+ beacon_info->power_constraint = 0;
+
+ pos = (u8 *)mgmt->u.beacon.variable;
+ left = len - baselen;
+
+ elem_parse_failed = false;
+
+ while (left >= 2) {
+ u8 id, elen;
+
+ id = *pos++;
+ elen = *pos++;
+ left -= 2;
+
+ if (elen > left) {
+ elem_parse_failed = true;
+ break;
+ }
+
+ switch (id) {
+ case WLAN_EID_COUNTRY:
+ beacon_info->ie_country_len = (elen + 2);
+ beacon_info->ie_country_ptr = (pos - 2);
+ break;
+ case WLAN_EID_SUPP_RATES:
+ case WLAN_EID_EXT_SUPP_RATES:
+ {
+ int idx, bi, oi;
+ u8 rate;
+
+ for (bi = 0; bi < SYSADPT_MAX_DATA_RATES_G;
+ bi++) {
+ if (beacon_info->b_rate_set[bi] == 0)
+ break;
+ }
+
+ for (oi = 0; oi < SYSADPT_MAX_DATA_RATES_G;
+ oi++) {
+ if (beacon_info->op_rate_set[oi] == 0)
+ break;
+ }
+
+ for (idx = 0; idx < elen; idx++) {
+ rate = pos[idx];
+ if ((rate & 0x80) != 0) {
+ if (bi < SYSADPT_MAX_DATA_RATES_G)
+ beacon_info->b_rate_set[bi++]
+ = rate & 0x7f;
+ else {
+ elem_parse_failed = true;
+ break;
+ }
+ }
+ if (oi < SYSADPT_MAX_DATA_RATES_G)
+ beacon_info->op_rate_set[oi++] =
+ rate & 0x7f;
+ else {
+ elem_parse_failed = true;
+ break;
+ }
+ }
+ }
+ break;
+ case WLAN_EID_PWR_CONSTRAINT:
+ if (elen == 1)
+ beacon_info->power_constraint = *pos;
+ break;
+ case WLAN_EID_RSN:
+ beacon_info->ie_rsn48_len = (elen + 2);
+ beacon_info->ie_rsn48_ptr = (pos - 2);
+ break;
+ case WLAN_EID_HT_CAPABILITY:
+ case WLAN_EID_HT_OPERATION:
+ case WLAN_EID_OVERLAP_BSS_SCAN_PARAM:
+ case WLAN_EID_EXT_CAPABILITY:
+ beacon_info->ie_ht_len += (elen + 2);
+ if (beacon_info->ie_ht_len >
+ sizeof(beacon_info->ie_list_ht)) {
+ elem_parse_failed = true;
+ } else {
+ *beacon_info->ie_ht_ptr++ = id;
+ *beacon_info->ie_ht_ptr++ = elen;
+ memcpy(beacon_info->ie_ht_ptr, pos, elen);
+ beacon_info->ie_ht_ptr += elen;
+ }
+ break;
+ case WLAN_EID_VHT_CAPABILITY:
+ case WLAN_EID_VHT_OPERATION:
+ case WLAN_EID_OPMODE_NOTIF:
+ beacon_info->ie_vht_len += (elen + 2);
+ if (beacon_info->ie_vht_len >
+ sizeof(beacon_info->ie_list_vht)) {
+ elem_parse_failed = true;
+ } else {
+ *beacon_info->ie_vht_ptr++ = id;
+ *beacon_info->ie_vht_ptr++ = elen;
+ memcpy(beacon_info->ie_vht_ptr, pos, elen);
+ beacon_info->ie_vht_ptr += elen;
+ }
+ break;
+ case WLAN_EID_VENDOR_SPECIFIC:
+ if ((pos[0] == 0x00) && (pos[1] == 0x50) &&
+ (pos[2] == 0xf2)) {
+ if (pos[3] == 0x01) {
+ beacon_info->ie_rsn_len = (elen + 2);
+ beacon_info->ie_rsn_ptr = (pos - 2);
+ }
+
+ if (pos[3] == 0x02) {
+ beacon_info->ie_wmm_len = (elen + 2);
+ beacon_info->ie_wmm_ptr = (pos - 2);
+ }
+
+ if (pos[3] == 0x04) {
+ beacon_info->ie_wsc_len = (elen + 2);
+ beacon_info->ie_wsc_ptr = (pos - 2);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ left -= elen;
+ pos += elen;
+ }
+
+ if (!elem_parse_failed) {
+ beacon_info->ie_ht_ptr = &beacon_info->ie_list_ht[0];
+ beacon_info->ie_vht_ptr = &beacon_info->ie_list_vht[0];
+ beacon_info->valid = true;
+ }
+}
+
+static int mwl_fwcmd_set_ies(struct mwl_priv *priv, struct mwl_vif *mwl_vif)
+{
+ struct hostcmd_cmd_set_ies *pcmd;
+ struct beacon_info *beacon = &mwl_vif->beacon_info;
+ u16 ie_list_len_proprietary = 0;
+
+ if (beacon->ie_ht_len > sizeof(pcmd->ie_list_ht))
+ goto einval;
+
+ if (beacon->ie_vht_len > sizeof(pcmd->ie_list_vht))
+ goto einval;
+
+ pcmd = (struct hostcmd_cmd_set_ies *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_IES);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET);
+
+ memcpy(pcmd->ie_list_ht, beacon->ie_ht_ptr, beacon->ie_ht_len);
+ pcmd->ie_list_len_ht = cpu_to_le16(beacon->ie_ht_len);
+
+ memcpy(pcmd->ie_list_vht, beacon->ie_vht_ptr, beacon->ie_vht_len);
+ pcmd->ie_list_len_vht = cpu_to_le16(beacon->ie_vht_len);
+
+ if (priv->chip_type == MWL8897) {
+ memcpy(pcmd->ie_list_proprietary + ie_list_len_proprietary,
+ beacon->ie_wmm_ptr, beacon->ie_wmm_len);
+ ie_list_len_proprietary += mwl_vif->beacon_info.ie_wmm_len;
+ }
+
+ pcmd->ie_list_len_proprietary = cpu_to_le16(ie_list_len_proprietary);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_IES)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+
+einval:
+
+ wiphy_err(priv->hw->wiphy, "length of IE is too long\n");
+
+ return -EINVAL;
+}
+
+static int mwl_fwcmd_set_ap_beacon(struct mwl_priv *priv,
+ struct mwl_vif *mwl_vif,
+ struct ieee80211_bss_conf *bss_conf)
+{
+ struct hostcmd_cmd_ap_beacon *pcmd;
+ struct ds_params *phy_ds_param_set;
+
+ /* wmm structure of start command is defined less one byte,
+ * due to following field country is not used, add byte one
+ * to bypass the check.
+ */
+ if (mwl_vif->beacon_info.ie_wmm_len >
+ (sizeof(pcmd->start_cmd.wmm_param) + 1))
+ goto ielenerr;
+
+ if (mwl_vif->beacon_info.ie_rsn_len > sizeof(pcmd->start_cmd.rsn_ie))
+ goto ielenerr;
+
+ if (mwl_vif->beacon_info.ie_rsn48_len >
+ sizeof(pcmd->start_cmd.rsn48_ie))
+ goto ielenerr;
+
+ pcmd = (struct hostcmd_cmd_ap_beacon *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_AP_BEACON);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ ether_addr_copy(pcmd->start_cmd.sta_mac_addr, mwl_vif->bssid);
+ memcpy(pcmd->start_cmd.ssid, bss_conf->ssid, bss_conf->ssid_len);
+ pcmd->start_cmd.bss_type = 1;
+ pcmd->start_cmd.bcn_period = cpu_to_le16(bss_conf->beacon_int);
+ pcmd->start_cmd.dtim_period = bss_conf->dtim_period; /* 8bit */
+
+ phy_ds_param_set = &pcmd->start_cmd.phy_param_set.ds_param_set;
+ phy_ds_param_set->elem_id = WLAN_EID_DS_PARAMS;
+ phy_ds_param_set->len = sizeof(phy_ds_param_set->current_chnl);
+ phy_ds_param_set->current_chnl = bss_conf->chandef.chan->hw_value;
+
+ pcmd->start_cmd.probe_delay = cpu_to_le16(10);
+ pcmd->start_cmd.cap_info = cpu_to_le16(mwl_vif->beacon_info.cap_info);
+
+ memcpy(&pcmd->start_cmd.wmm_param, mwl_vif->beacon_info.ie_wmm_ptr,
+ mwl_vif->beacon_info.ie_wmm_len);
+
+ memcpy(&pcmd->start_cmd.rsn_ie, mwl_vif->beacon_info.ie_rsn_ptr,
+ mwl_vif->beacon_info.ie_rsn_len);
+
+ memcpy(&pcmd->start_cmd.rsn48_ie, mwl_vif->beacon_info.ie_rsn48_ptr,
+ mwl_vif->beacon_info.ie_rsn48_len);
+
+ memcpy(pcmd->start_cmd.b_rate_set, mwl_vif->beacon_info.b_rate_set,
+ SYSADPT_MAX_DATA_RATES_G);
+
+ memcpy(pcmd->start_cmd.op_rate_set, mwl_vif->beacon_info.op_rate_set,
+ SYSADPT_MAX_DATA_RATES_G);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_AP_BEACON)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+
+ielenerr:
+
+ wiphy_err(priv->hw->wiphy, "length of IE is too long\n");
+
+ return -EINVAL;
+}
+
+static int mwl_fwcmd_set_spectrum_mgmt(struct mwl_priv *priv, bool enable)
+{
+ struct hostcmd_cmd_set_spectrum_mgmt *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_spectrum_mgmt *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_SPECTRUM_MGMT);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->spectrum_mgmt = cpu_to_le32(enable);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_SPECTRUM_MGMT)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+static int mwl_fwcmd_set_power_constraint(struct mwl_priv *priv,
+ u32 power_constraint)
+{
+ struct hostcmd_cmd_set_power_constraint *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_power_constraint *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_POWER_CONSTRAINT);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->power_constraint = cpu_to_le32(power_constraint);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_POWER_CONSTRAINT)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+static int mwl_fwcmd_set_country_code(struct mwl_priv *priv,
+ struct mwl_vif *mwl_vif,
+ struct ieee80211_bss_conf *bss_conf)
+{
+ struct hostcmd_cmd_set_country_code *pcmd;
+ struct beacon_info *b_inf = &mwl_vif->beacon_info;
+ u8 chnl_len;
+ bool a_band;
+ bool enable = false;
+
+ if (b_inf->ie_country_ptr) {
+ if (bss_conf->chandef.chan->band == NL80211_BAND_2GHZ)
+ a_band = false;
+ else if (bss_conf->chandef.chan->band == NL80211_BAND_5GHZ)
+ a_band = true;
+ else
+ return -EINVAL;
+
+ chnl_len = b_inf->ie_country_len - 5;
+ if (a_band) {
+ if (chnl_len > sizeof(pcmd->domain_info.domain_entry_a))
+ return -EINVAL;
+ } else {
+ if (chnl_len > sizeof(pcmd->domain_info.domain_entry_g))
+ return -EINVAL;
+ }
+
+ enable = true;
+ }
+
+ pcmd = (struct hostcmd_cmd_set_country_code *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_COUNTRY_CODE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le32(enable);
+ if (enable) {
+ memcpy(pcmd->domain_info.country_string,
+ b_inf->ie_country_ptr + 2, 3);
+ if (a_band) {
+ pcmd->domain_info.g_chnl_len = 0;
+ pcmd->domain_info.a_chnl_len = chnl_len;
+ memcpy(pcmd->domain_info.domain_entry_a,
+ b_inf->ie_country_ptr + 5, chnl_len);
+ } else {
+ pcmd->domain_info.a_chnl_len = 0;
+ pcmd->domain_info.g_chnl_len = chnl_len;
+ memcpy(pcmd->domain_info.domain_entry_g,
+ b_inf->ie_country_ptr + 5, chnl_len);
+ }
+ }
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_COUNTRY_CODE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+static int mwl_fwcmd_encryption_set_cmd_info(struct hostcmd_cmd_set_key *cmd,
+ u8 *addr,
+ struct ieee80211_key_conf *key)
+{
+ cmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION);
+ cmd->cmd_hdr.len = cpu_to_le16(sizeof(*cmd));
+ cmd->key_param.length = cpu_to_le16(sizeof(*cmd) -
+ offsetof(struct hostcmd_cmd_set_key, key_param));
+ cmd->key_param.key_index = cpu_to_le32(key->keyidx);
+ cmd->key_param.key_len = cpu_to_le16(key->keylen);
+ ether_addr_copy(cmd->key_param.mac_addr, addr);
+
+ switch (key->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_WEP);
+ if (key->keyidx == 0)
+ cmd->key_param.key_info =
+ cpu_to_le32(ENCR_KEY_FLAG_WEP_TXKEY);
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_TKIP);
+ cmd->key_param.key_info =
+ (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
+ cpu_to_le32(ENCR_KEY_FLAG_PAIRWISE) :
+ cpu_to_le32(ENCR_KEY_FLAG_TXGROUPKEY);
+ cmd->key_param.key_info |=
+ cpu_to_le32(ENCR_KEY_FLAG_MICKEY_VALID |
+ ENCR_KEY_FLAG_TSC_VALID);
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_AES);
+ cmd->key_param.key_info =
+ (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
+ cpu_to_le32(ENCR_KEY_FLAG_PAIRWISE) :
+ cpu_to_le32(ENCR_KEY_FLAG_TXGROUPKEY);
+ break;
+ case WLAN_CIPHER_SUITE_AES_CMAC:
+ return 1;
+ default:
+ return -ENOTSUPP;
+ }
+
+ return 0;
+}
+
+void mwl_fwcmd_reset(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ if (mwl_fwcmd_chk_adapter(priv))
+ writel(ISR_RESET,
+ priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS);
+}
+
+void mwl_fwcmd_int_enable(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ if (mwl_fwcmd_chk_adapter(priv)) {
+ writel(0x00,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK);
+ writel(MACREG_A2HRIC_BIT_MASK,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK);
+ }
+}
+
+void mwl_fwcmd_int_disable(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ if (mwl_fwcmd_chk_adapter(priv))
+ writel(0x00,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK);
+}
+
+int mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_get_hw_spec *pcmd;
+ int retry;
+ int i;
+
+ pcmd = (struct hostcmd_cmd_get_hw_spec *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ wiphy_debug(hw->wiphy, "pcmd = %p\n", pcmd);
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ eth_broadcast_addr(pcmd->permanent_addr);
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_HW_SPEC);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->fw_awake_cookie = cpu_to_le32(priv->pphys_cmd_buf + 2048);
+
+ retry = 0;
+ while (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_HW_SPEC)) {
+ if (retry++ > MAX_WAIT_GET_HW_SPECS_ITERATONS) {
+ wiphy_err(hw->wiphy, "can't get hw specs\n");
+ mutex_unlock(&priv->fwcmd_mutex);
+ return -EIO;
+ }
+
+ msleep(1000);
+ wiphy_debug(hw->wiphy,
+ "repeat command = %p\n", pcmd);
+ }
+
+ ether_addr_copy(&priv->hw_data.mac_addr[0], pcmd->permanent_addr);
+ priv->desc_data[0].wcb_base =
+ le32_to_cpu(pcmd->wcb_base0) & 0x0000ffff;
+ for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++)
+ priv->desc_data[i].wcb_base =
+ le32_to_cpu(pcmd->wcb_base[i - 1]) & 0x0000ffff;
+ priv->desc_data[0].rx_desc_read =
+ le32_to_cpu(pcmd->rxpd_rd_ptr) & 0x0000ffff;
+ priv->desc_data[0].rx_desc_write =
+ le32_to_cpu(pcmd->rxpd_wr_ptr) & 0x0000ffff;
+ priv->hw_data.region_code = le16_to_cpu(pcmd->region_code) & 0x00ff;
+ priv->hw_data.fw_release_num = le32_to_cpu(pcmd->fw_release_num);
+ priv->hw_data.max_num_tx_desc = le16_to_cpu(pcmd->num_wcb);
+ priv->hw_data.max_num_mc_addr = le16_to_cpu(pcmd->num_mcast_addr);
+ priv->hw_data.num_antennas = le16_to_cpu(pcmd->num_antenna);
+ priv->hw_data.hw_version = pcmd->version;
+ priv->hw_data.host_interface = pcmd->host_if;
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_hw_spec *pcmd;
+ int i;
+
+ pcmd = (struct hostcmd_cmd_set_hw_spec *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_HW_SPEC);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->wcb_base[0] = cpu_to_le32(priv->desc_data[0].pphys_tx_ring);
+ for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++)
+ pcmd->wcb_base[i] =
+ cpu_to_le32(priv->desc_data[i].pphys_tx_ring);
+ pcmd->tx_wcb_num_per_queue = cpu_to_le32(SYSADPT_MAX_NUM_TX_DESC);
+ pcmd->num_tx_queues = cpu_to_le32(SYSADPT_NUM_OF_DESC_DATA);
+ pcmd->total_rx_wcb = cpu_to_le32(SYSADPT_MAX_NUM_RX_DESC);
+ pcmd->rxpd_wr_ptr = cpu_to_le32(priv->desc_data[0].pphys_rx_ring);
+ pcmd->features = 0;
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_HW_SPEC)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_get_stat(struct ieee80211_hw *hw,
+ struct ieee80211_low_level_stats *stats)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_802_11_get_stat *pcmd;
+
+ pcmd = (struct hostcmd_cmd_802_11_get_stat *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_GET_STAT);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_GET_STAT)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ stats->dot11ACKFailureCount =
+ le32_to_cpu(pcmd->ack_failures);
+ stats->dot11RTSFailureCount =
+ le32_to_cpu(pcmd->rts_failures);
+ stats->dot11FCSErrorCount =
+ le32_to_cpu(pcmd->rx_fcs_errors);
+ stats->dot11RTSSuccessCount =
+ le32_to_cpu(pcmd->rts_successes);
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw)
+{
+ return mwl_fwcmd_802_11_radio_control(hw->priv, true, false);
+}
+
+int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw)
+{
+ return mwl_fwcmd_802_11_radio_control(hw->priv, false, false);
+}
+
+int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw, bool short_preamble)
+{
+ struct mwl_priv *priv = hw->priv;
+ int rc;
+
+ priv->radio_short_preamble = short_preamble;
+ rc = mwl_fwcmd_802_11_radio_control(priv, true, true);
+
+ return rc;
+}
+
+int mwl_fwcmd_get_addr_value(struct ieee80211_hw *hw, u32 addr, u32 len,
+ u32 *val, u16 set)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_mem_addr_access *pcmd;
+ int i;
+
+ pcmd = (struct hostcmd_cmd_mem_addr_access *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_MEM_ADDR_ACCESS);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->address = cpu_to_le32(addr);
+ pcmd->length = cpu_to_le16(len);
+ pcmd->value[0] = cpu_to_le32(*val);
+ pcmd->reserved = cpu_to_le16(set);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_MEM_ADDR_ACCESS)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ for (i = 0; i < len; i++)
+ val[i] = le32_to_cpu(pcmd->value[i]);
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_max_tx_power(struct ieee80211_hw *hw,
+ struct ieee80211_conf *conf, u8 fraction)
+{
+ struct ieee80211_channel *channel = conf->chandef.chan;
+ struct mwl_priv *priv = hw->priv;
+ int reduce_val = 0;
+ u16 band = 0, width = 0, sub_ch = 0;
+ u16 maxtxpow[SYSADPT_TX_POWER_LEVEL_TOTAL];
+ int i, tmp;
+ int rc = 0;
+
+ if (priv->forbidden_setting)
+ return rc;
+
+ switch (fraction) {
+ case 0:
+ reduce_val = 0; /* Max */
+ break;
+ case 1:
+ reduce_val = 2; /* 75% -1.25db */
+ break;
+ case 2:
+ reduce_val = 3; /* 50% -3db */
+ break;
+ case 3:
+ reduce_val = 6; /* 25% -6db */
+ break;
+ default:
+ /* larger than case 3, pCmd->MaxPowerLevel is min */
+ reduce_val = 0xff;
+ break;
+ }
+
+ if (channel->band == NL80211_BAND_2GHZ)
+ band = FREQ_BAND_2DOT4GHZ;
+ else if (channel->band == NL80211_BAND_5GHZ)
+ band = FREQ_BAND_5GHZ;
+
+ switch (conf->chandef.width) {
+ case NL80211_CHAN_WIDTH_20_NOHT:
+ case NL80211_CHAN_WIDTH_20:
+ width = CH_20_MHZ_WIDTH;
+ sub_ch = NO_EXT_CHANNEL;
+ break;
+ case NL80211_CHAN_WIDTH_40:
+ width = CH_40_MHZ_WIDTH;
+ if (conf->chandef.center_freq1 > channel->center_freq)
+ sub_ch = EXT_CH_ABOVE_CTRL_CH;
+ else
+ sub_ch = EXT_CH_BELOW_CTRL_CH;
+ break;
+ case NL80211_CHAN_WIDTH_80:
+ width = CH_80_MHZ_WIDTH;
+ if (conf->chandef.center_freq1 > channel->center_freq)
+ sub_ch = EXT_CH_ABOVE_CTRL_CH;
+ else
+ sub_ch = EXT_CH_BELOW_CTRL_CH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if ((priv->powinited & MWL_POWER_INIT_2) == 0) {
+ mwl_fwcmd_get_tx_powers(priv, priv->max_tx_pow,
+ channel->hw_value, band, width, sub_ch);
+ priv->powinited |= MWL_POWER_INIT_2;
+ }
+
+ if ((priv->powinited & MWL_POWER_INIT_1) == 0) {
+ mwl_fwcmd_get_tx_powers(priv, priv->target_powers,
+ channel->hw_value, band, width, sub_ch);
+ priv->powinited |= MWL_POWER_INIT_1;
+ }
+
+ for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) {
+ if (priv->target_powers[i] > priv->max_tx_pow[i])
+ tmp = priv->max_tx_pow[i];
+ else
+ tmp = priv->target_powers[i];
+ maxtxpow[i] = ((tmp - reduce_val) > 0) ? (tmp - reduce_val) : 0;
+ }
+
+ rc = mwl_fwcmd_set_tx_powers(priv, maxtxpow, HOSTCMD_ACT_GEN_SET,
+ channel->hw_value, band, width, sub_ch);
+
+ return rc;
+}
+
+int mwl_fwcmd_tx_power(struct ieee80211_hw *hw,
+ struct ieee80211_conf *conf, u8 fraction)
+{
+ struct ieee80211_channel *channel = conf->chandef.chan;
+ struct mwl_priv *priv = hw->priv;
+ int reduce_val = 0;
+ u16 band = 0, width = 0, sub_ch = 0;
+ u16 txpow[SYSADPT_TX_POWER_LEVEL_TOTAL];
+ int index, found = 0;
+ int i, tmp;
+ int rc = 0;
+
+ if (priv->forbidden_setting)
+ return rc;
+
+ switch (fraction) {
+ case 0:
+ reduce_val = 0; /* Max */
+ break;
+ case 1:
+ reduce_val = 2; /* 75% -1.25db */
+ break;
+ case 2:
+ reduce_val = 3; /* 50% -3db */
+ break;
+ case 3:
+ reduce_val = 6; /* 25% -6db */
+ break;
+ default:
+ /* larger than case 3, pCmd->MaxPowerLevel is min */
+ reduce_val = 0xff;
+ break;
+ }
+
+ if (channel->band == NL80211_BAND_2GHZ)
+ band = FREQ_BAND_2DOT4GHZ;
+ else if (channel->band == NL80211_BAND_5GHZ)
+ band = FREQ_BAND_5GHZ;
+
+ switch (conf->chandef.width) {
+ case NL80211_CHAN_WIDTH_20_NOHT:
+ case NL80211_CHAN_WIDTH_20:
+ width = CH_20_MHZ_WIDTH;
+ sub_ch = NO_EXT_CHANNEL;
+ break;
+ case NL80211_CHAN_WIDTH_40:
+ width = CH_40_MHZ_WIDTH;
+ if (conf->chandef.center_freq1 > channel->center_freq)
+ sub_ch = EXT_CH_ABOVE_CTRL_CH;
+ else
+ sub_ch = EXT_CH_BELOW_CTRL_CH;
+ break;
+ case NL80211_CHAN_WIDTH_80:
+ width = CH_80_MHZ_WIDTH;
+ if (conf->chandef.center_freq1 > channel->center_freq)
+ sub_ch = EXT_CH_ABOVE_CTRL_CH;
+ else
+ sub_ch = EXT_CH_BELOW_CTRL_CH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* search tx power table if exist */
+ for (index = 0; index < SYSADPT_MAX_NUM_CHANNELS; index++) {
+ struct mwl_tx_pwr_tbl *tx_pwr;
+
+ tx_pwr = &priv->tx_pwr_tbl[index];
+
+ /* do nothing if table is not loaded */
+ if (tx_pwr->channel == 0)
+ break;
+
+ if (tx_pwr->channel == channel->hw_value) {
+ priv->cdd = tx_pwr->cdd;
+ priv->txantenna2 = tx_pwr->txantenna2;
+
+ if (tx_pwr->setcap)
+ priv->powinited = MWL_POWER_INIT_1;
+ else
+ priv->powinited = MWL_POWER_INIT_2;
+
+ for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) {
+ if (tx_pwr->setcap)
+ priv->max_tx_pow[i] =
+ tx_pwr->tx_power[i];
+ else
+ priv->target_powers[i] =
+ tx_pwr->tx_power[i];
+ }
+
+ found = 1;
+ break;
+ }
+ }
+
+ if ((priv->powinited & MWL_POWER_INIT_2) == 0) {
+ mwl_fwcmd_get_tx_powers(priv, priv->max_tx_pow,
+ channel->hw_value, band, width, sub_ch);
+
+ priv->powinited |= MWL_POWER_INIT_2;
+ }
+
+ if ((priv->powinited & MWL_POWER_INIT_1) == 0) {
+ mwl_fwcmd_get_tx_powers(priv, priv->target_powers,
+ channel->hw_value, band, width, sub_ch);
+
+ priv->powinited |= MWL_POWER_INIT_1;
+ }
+
+ for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) {
+ if (found) {
+ if ((priv->tx_pwr_tbl[index].setcap) &&
+ (priv->tx_pwr_tbl[index].tx_power[i] >
+ priv->max_tx_pow[i]))
+ tmp = priv->max_tx_pow[i];
+ else
+ tmp = priv->tx_pwr_tbl[index].tx_power[i];
+ } else {
+ if (priv->target_powers[i] > priv->max_tx_pow[i])
+ tmp = priv->max_tx_pow[i];
+ else
+ tmp = priv->target_powers[i];
+ }
+
+ txpow[i] = ((tmp - reduce_val) > 0) ? (tmp - reduce_val) : 0;
+ }
+
+ rc = mwl_fwcmd_set_tx_powers(priv, txpow, HOSTCMD_ACT_GEN_SET_LIST,
+ channel->hw_value, band, width, sub_ch);
+
+ return rc;
+}
+
+int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int dir, int antenna)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_802_11_rf_antenna *pcmd;
+
+ pcmd = (struct hostcmd_cmd_802_11_rf_antenna *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RF_ANTENNA);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+
+ pcmd->action = cpu_to_le16(dir);
+
+ if (dir == WL_ANTENNATYPE_RX) {
+ u8 rx_antenna = 4; /* if auto, set 4 rx antennas in SC2 */
+
+ if (antenna != 0)
+ pcmd->antenna_mode = cpu_to_le16(antenna);
+ else
+ pcmd->antenna_mode = cpu_to_le16(rx_antenna);
+ } else {
+ u8 tx_antenna = 0xf; /* if auto, set 4 tx antennas in SC2 */
+
+ if (antenna != 0)
+ pcmd->antenna_mode = cpu_to_le16(antenna);
+ else
+ pcmd->antenna_mode = cpu_to_le16(tx_antenna);
+ }
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RF_ANTENNA)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, bool enable)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_broadcast_ssid_enable *pcmd;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_broadcast_ssid_enable *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BROADCAST_SSID_ENABLE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+ pcmd->enable = cpu_to_le32(enable);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BROADCAST_SSID_ENABLE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw,
+ struct ieee80211_conf *conf)
+{
+ struct ieee80211_channel *channel = conf->chandef.chan;
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_rf_channel *pcmd;
+ u32 chnl_flags, freq_band, chnl_width, act_primary;
+
+ pcmd = (struct hostcmd_cmd_set_rf_channel *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RF_CHANNEL);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(WL_SET);
+ pcmd->curr_chnl = channel->hw_value;
+
+ if (channel->band == NL80211_BAND_2GHZ) {
+ freq_band = FREQ_BAND_2DOT4GHZ;
+ } else if (channel->band == NL80211_BAND_5GHZ) {
+ freq_band = FREQ_BAND_5GHZ;
+ } else {
+ mutex_unlock(&priv->fwcmd_mutex);
+ return -EINVAL;
+ }
+
+ switch (conf->chandef.width) {
+ case NL80211_CHAN_WIDTH_20_NOHT:
+ case NL80211_CHAN_WIDTH_20:
+ chnl_width = CH_20_MHZ_WIDTH;
+ act_primary = ACT_PRIMARY_CHAN_0;
+ break;
+ case NL80211_CHAN_WIDTH_40:
+ chnl_width = CH_40_MHZ_WIDTH;
+ if (conf->chandef.center_freq1 > channel->center_freq)
+ act_primary = ACT_PRIMARY_CHAN_0;
+ else
+ act_primary = ACT_PRIMARY_CHAN_1;
+ break;
+ case NL80211_CHAN_WIDTH_80:
+ chnl_width = CH_80_MHZ_WIDTH;
+ act_primary =
+ mwl_fwcmd_get_80m_pri_chnl(pcmd->curr_chnl);
+ break;
+ default:
+ mutex_unlock(&priv->fwcmd_mutex);
+ return -EINVAL;
+ }
+
+ chnl_flags = (freq_band & FREQ_BAND_MASK) |
+ ((chnl_width << CHNL_WIDTH_SHIFT) & CHNL_WIDTH_MASK) |
+ ((act_primary << ACT_PRIMARY_SHIFT) & ACT_PRIMARY_MASK);
+
+ pcmd->chnl_flags = cpu_to_le32(chnl_flags);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_RF_CHANNEL)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ if (pcmd->cmd_hdr.result != 0) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_aid(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *bssid, u16 aid)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_set_aid *pcmd;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_set_aid *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_AID);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+ pcmd->aid = cpu_to_le16(aid);
+ ether_addr_copy(pcmd->mac_addr, bssid);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_AID)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_infra_mode(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_set_infra_mode *pcmd;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_set_infra_mode *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_INFRA_MODE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_INFRA_MODE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw, int threshold)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_802_11_rts_thsd *pcmd;
+
+ pcmd = (struct hostcmd_cmd_802_11_rts_thsd *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RTS_THSD);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(WL_SET);
+ pcmd->threshold = cpu_to_le16(threshold);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RTS_THSD)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, u8 index,
+ u16 cw_min, u16 cw_max, u8 aifs, u16 txop)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_edca_params *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_edca_params *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_EDCA_PARAMS);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+
+ pcmd->action = cpu_to_le16(0xffff);
+ pcmd->txop = cpu_to_le16(txop);
+ pcmd->cw_max = cpu_to_le32(ilog2(cw_max + 1));
+ pcmd->cw_min = cpu_to_le32(ilog2(cw_min + 1));
+ pcmd->aifsn = aifs;
+ pcmd->txq_num = index;
+
+ /* The array index defined in qos.h has a reversed bk and be.
+ * The HW queue was not used this way; the qos code needs to
+ * be changed or checked
+ */
+ if (index == 0)
+ pcmd->txq_num = 1;
+ else if (index == 1)
+ pcmd->txq_num = 0;
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_EDCA_PARAMS)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_radar_detect(struct ieee80211_hw *hw, u16 action)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_802_11h_detect_radar *pcmd;
+ u16 radar_type = RADAR_TYPE_CODE_0;
+ u8 channel = hw->conf.chandef.chan->hw_value;
+
+ pcmd = (struct hostcmd_cmd_802_11h_detect_radar *)&priv->pcmd_buf[0];
+
+ if (priv->dfs_region == NL80211_DFS_JP) {
+ if (channel >= 52 && channel <= 64)
+ radar_type = RADAR_TYPE_CODE_53;
+ else if (channel >= 100 && channel <= 140)
+ radar_type = RADAR_TYPE_CODE_56;
+ else
+ radar_type = RADAR_TYPE_CODE_0;
+ } else if (priv->dfs_region == NL80211_DFS_ETSI) {
+ radar_type = RADAR_TYPE_CODE_ETSI;
+ }
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11H_DETECT_RADAR);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(action);
+ pcmd->radar_type_code = cpu_to_le16(radar_type);
+ pcmd->min_chirp_cnt = cpu_to_le16(priv->dfs_chirp_count_min);
+ pcmd->chirp_time_intvl = cpu_to_le16(priv->dfs_chirp_time_interval);
+ pcmd->pw_filter = cpu_to_le16(priv->dfs_pw_filter);
+ pcmd->min_num_radar = cpu_to_le16(priv->dfs_min_num_radar);
+ pcmd->pri_min_num = cpu_to_le16(priv->dfs_min_pri_count);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11H_DETECT_RADAR)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, bool enable)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_wmm_mode *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_wmm_mode *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WMM_MODE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(enable ? WL_ENABLE : WL_DISABLE);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WMM_MODE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_ht_guard_interval(struct ieee80211_hw *hw, u32 gi_type)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_ht_guard_interval *pcmd;
+
+ pcmd = (struct hostcmd_cmd_ht_guard_interval *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_HT_GUARD_INTERVAL);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le32(WL_SET);
+ pcmd->gi_type = cpu_to_le32(gi_type);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_HT_GUARD_INTERVAL)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw, int mcast, int mgmt)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_fixed_rate *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_fixed_rate *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_FIXED_RATE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+
+ pcmd->action = cpu_to_le32(HOSTCMD_ACT_NOT_USE_FIXED_RATE);
+ pcmd->multicast_rate = mcast;
+ pcmd->management_rate = mgmt;
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_FIXED_RATE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_linkadapt_cs_mode(struct ieee80211_hw *hw, u16 cs_mode)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_linkadapt_cs_mode *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_linkadapt_cs_mode *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_LINKADAPT_CS_MODE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET);
+ pcmd->cs_mode = cpu_to_le16(cs_mode);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_LINKADAPT_CS_MODE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw, u16 mode)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_rate_adapt_mode *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_rate_adapt_mode *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RATE_ADAPT_MODE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(WL_SET);
+ pcmd->rate_adapt_mode = cpu_to_le16(mode);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_RATE_ADAPT_MODE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_mac_addr_client(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *mac_addr)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_set_mac_addr *pcmd;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_set_mac_addr *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_MAC_ADDR);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+ pcmd->mac_type = cpu_to_le16(WL_MAC_TYPE_SECONDARY_CLIENT);
+ ether_addr_copy(pcmd->mac_addr, mac_addr);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_MAC_ADDR)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw, u8 *bitmap)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_get_watchdog_bitmap *pcmd;
+
+ pcmd = (struct hostcmd_cmd_get_watchdog_bitmap *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_WATCHDOG_BITMAP);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_WATCHDOG_BITMAP)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ *bitmap = pcmd->watchdog_bitmap;
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_remove_mac_addr(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *mac_addr)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_set_mac_addr *pcmd;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_set_mac_addr *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DEL_MAC_ADDR);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+ ether_addr_copy(pcmd->mac_addr, mac_addr);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_DEL_MAC_ADDR)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_bss_start(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, bool enable)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_bss_start *pcmd;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ if (enable && (priv->running_bsses & (1 << mwl_vif->macid)))
+ return 0;
+
+ if (!enable && !(priv->running_bsses & (1 << mwl_vif->macid)))
+ return 0;
+
+ pcmd = (struct hostcmd_cmd_bss_start *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BSS_START);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ if (enable) {
+ pcmd->enable = cpu_to_le32(WL_ENABLE);
+ } else {
+ if (mwl_vif->macid == 0)
+ pcmd->enable = cpu_to_le32(WL_DISABLE);
+ else
+ pcmd->enable = cpu_to_le32(WL_DISABLE_VMAC);
+ }
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BSS_START)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ if (enable)
+ priv->running_bsses |= (1 << mwl_vif->macid);
+ else
+ priv->running_bsses &= ~(1 << mwl_vif->macid);
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *beacon, int len)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct beacon_info *b_inf;
+ int rc;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+ b_inf = &mwl_vif->beacon_info;
+
+ mwl_fwcmd_parse_beacon(priv, mwl_vif, beacon, len);
+
+ if (!b_inf->valid)
+ goto err;
+
+ if (mwl_fwcmd_set_ies(priv, mwl_vif))
+ goto err;
+
+ if (mwl_fwcmd_set_wsc_ie(hw, b_inf->ie_wsc_len, b_inf->ie_wsc_ptr))
+ goto err;
+
+ if (mwl_fwcmd_set_ap_beacon(priv, mwl_vif, &vif->bss_conf))
+ goto err;
+
+ if (mwl_fwcmd_bss_start(hw, vif, true))
+ goto err;
+
+ if (b_inf->cap_info & WLAN_CAPABILITY_SPECTRUM_MGMT)
+ rc = mwl_fwcmd_set_spectrum_mgmt(priv, true);
+ else
+ rc = mwl_fwcmd_set_spectrum_mgmt(priv, false);
+ if (rc)
+ goto err;
+
+ if (b_inf->power_constraint)
+ rc = mwl_fwcmd_set_power_constraint(priv,
+ b_inf->power_constraint);
+ if (rc)
+ goto err;
+
+ if (mwl_fwcmd_set_country_code(priv, mwl_vif, &vif->bss_conf))
+ goto err;
+
+ b_inf->valid = false;
+
+ return 0;
+
+err:
+
+ b_inf->valid = false;
+
+ return -EIO;
+}
+
+int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_set_new_stn *pcmd;
+ u32 rates;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_ADD);
+ if (vif->type == NL80211_IFTYPE_STATION) {
+ pcmd->aid = 0;
+ pcmd->stn_id = 0;
+ } else {
+ pcmd->aid = cpu_to_le16(sta->aid);
+ pcmd->stn_id = cpu_to_le16(sta->aid);
+ }
+ ether_addr_copy(pcmd->mac_addr, sta->addr);
+
+ if (hw->conf.chandef.chan->band == NL80211_BAND_2GHZ)
+ rates = sta->supp_rates[NL80211_BAND_2GHZ];
+ else
+ rates = sta->supp_rates[NL80211_BAND_5GHZ] << 5;
+ pcmd->peer_info.legacy_rate_bitmap = cpu_to_le32(rates);
+
+ if (sta->ht_cap.ht_supported) {
+ pcmd->peer_info.ht_rates[0] = sta->ht_cap.mcs.rx_mask[0];
+ pcmd->peer_info.ht_rates[1] = sta->ht_cap.mcs.rx_mask[1];
+ pcmd->peer_info.ht_rates[2] = sta->ht_cap.mcs.rx_mask[2];
+ pcmd->peer_info.ht_rates[3] = sta->ht_cap.mcs.rx_mask[3];
+ pcmd->peer_info.ht_cap_info = cpu_to_le16(sta->ht_cap.cap);
+ pcmd->peer_info.mac_ht_param_info =
+ (sta->ht_cap.ampdu_factor & 3) |
+ ((sta->ht_cap.ampdu_density & 7) << 2);
+ }
+
+ if (sta->vht_cap.vht_supported) {
+ pcmd->peer_info.vht_max_rx_mcs =
+ cpu_to_le32(*((u32 *)
+ &sta->vht_cap.vht_mcs.rx_mcs_map));
+ pcmd->peer_info.vht_cap = cpu_to_le32(sta->vht_cap.cap);
+ pcmd->peer_info.vht_rx_channel_width = sta->bandwidth;
+ }
+
+ pcmd->is_qos_sta = sta->wme;
+ pcmd->qos_info = ((sta->uapsd_queues << 4) | (sta->max_sp << 1));
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ if (vif->type == NL80211_IFTYPE_STATION) {
+ ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_set_new_stn *pcmd;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_ADD);
+ ether_addr_copy(pcmd->mac_addr, vif->addr);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *addr)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_set_new_stn *pcmd;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_REMOVE);
+ ether_addr_copy(pcmd->mac_addr, addr);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ if (vif->type == NL80211_IFTYPE_STATION) {
+ ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_apmode *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_apmode *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_APMODE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->apmode = apmode;
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_APMODE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_switch_channel(struct mwl_priv *priv,
+ struct ieee80211_channel_switch *ch_switch)
+{
+ struct hostcmd_cmd_set_switch_channel *pcmd;
+ struct cfg80211_chan_def *chandef = &ch_switch->chandef;
+ struct ieee80211_channel *channel = chandef->chan;
+ u32 chnl_flags, freq_band, chnl_width, act_primary, sec_chnl_offset;
+
+ if (priv->csa_active)
+ return 0;
+
+ if (channel->band == NL80211_BAND_2GHZ)
+ freq_band = FREQ_BAND_2DOT4GHZ;
+ else if (channel->band == NL80211_BAND_5GHZ)
+ freq_band = FREQ_BAND_5GHZ;
+ else
+ return -EINVAL;
+
+ switch (chandef->width) {
+ case NL80211_CHAN_WIDTH_20_NOHT:
+ case NL80211_CHAN_WIDTH_20:
+ chnl_width = CH_20_MHZ_WIDTH;
+ act_primary = ACT_PRIMARY_CHAN_0;
+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE;
+ break;
+ case NL80211_CHAN_WIDTH_40:
+ chnl_width = CH_40_MHZ_WIDTH;
+ if (chandef->center_freq1 > channel->center_freq) {
+ act_primary = ACT_PRIMARY_CHAN_0;
+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
+ } else {
+ act_primary = ACT_PRIMARY_CHAN_1;
+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
+ }
+ break;
+ case NL80211_CHAN_WIDTH_80:
+ chnl_width = CH_80_MHZ_WIDTH;
+ act_primary =
+ mwl_fwcmd_get_80m_pri_chnl(channel->hw_value);
+ if ((act_primary == ACT_PRIMARY_CHAN_0) ||
+ (act_primary == ACT_PRIMARY_CHAN_2))
+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
+ else
+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ chnl_flags = (freq_band & FREQ_BAND_MASK) |
+ ((chnl_width << CHNL_WIDTH_SHIFT) & CHNL_WIDTH_MASK) |
+ ((act_primary << ACT_PRIMARY_SHIFT) & ACT_PRIMARY_MASK);
+
+ pcmd = (struct hostcmd_cmd_set_switch_channel *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_SWITCH_CHANNEL);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->next_11h_chnl = cpu_to_le32(channel->hw_value);
+ pcmd->mode = cpu_to_le32(ch_switch->block_tx);
+ pcmd->init_count = cpu_to_le32(ch_switch->count + 1);
+ pcmd->chnl_flags = cpu_to_le32(chnl_flags);
+ pcmd->next_ht_extchnl_offset = cpu_to_le32(sec_chnl_offset);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_SWITCH_CHANNEL)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ priv->csa_active = true;
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ u8 *addr, u8 encr_type)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_update_encryption *pcmd;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_update_encryption *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ pcmd->action_type = cpu_to_le32(ENCR_ACTION_ENABLE_HW_ENCR);
+ ether_addr_copy(pcmd->mac_addr, addr);
+ pcmd->action_data[0] = encr_type;
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ if (vif->type == NL80211_IFTYPE_STATION) {
+ if (ether_addr_equal(mwl_vif->bssid, addr))
+ ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac);
+ else
+ ether_addr_copy(pcmd->mac_addr, mwl_vif->bssid);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *addr,
+ struct ieee80211_key_conf *key)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_set_key *pcmd;
+ int rc;
+ int keymlen;
+ u32 action;
+ u8 idx;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, addr, key);
+ if (rc) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ if (rc != 1)
+ wiphy_err(hw->wiphy, "encryption not support\n");
+ return rc;
+ }
+
+ idx = key->keyidx;
+
+ if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE)
+ action = ENCR_ACTION_TYPE_SET_KEY;
+ else
+ action = ENCR_ACTION_TYPE_SET_GROUP_KEY;
+
+ switch (key->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ if (!mwl_vif->wep_key_conf[idx].enabled) {
+ memcpy(mwl_vif->wep_key_conf[idx].key, key,
+ sizeof(*key) + key->keylen);
+ mwl_vif->wep_key_conf[idx].enabled = 1;
+ }
+
+ keymlen = key->keylen;
+ action = ENCR_ACTION_TYPE_SET_KEY;
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ keymlen = MAX_ENCR_KEY_LENGTH + 2 * MIC_KEY_LENGTH;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ keymlen = key->keylen;
+ break;
+ default:
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "encryption not support\n");
+ return -ENOTSUPP;
+ }
+
+ memcpy((void *)&pcmd->key_param.key, key->key, keymlen);
+ pcmd->action_type = cpu_to_le32(action);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ if (vif->type == NL80211_IFTYPE_STATION) {
+ if (ether_addr_equal(mwl_vif->bssid, addr))
+ ether_addr_copy(pcmd->key_param.mac_addr,
+ mwl_vif->sta_mac);
+ else
+ ether_addr_copy(pcmd->key_param.mac_addr,
+ mwl_vif->bssid);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *addr,
+ struct ieee80211_key_conf *key)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_set_key *pcmd;
+ int rc;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+
+ rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, addr, key);
+ if (rc) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ if (rc != 1)
+ wiphy_err(hw->wiphy, "encryption not support\n");
+ return rc;
+ }
+
+ pcmd->action_type = cpu_to_le32(ENCR_ACTION_TYPE_REMOVE_KEY);
+
+ if (key->cipher == WLAN_CIPHER_SUITE_WEP40 ||
+ key->cipher == WLAN_CIPHER_SUITE_WEP104)
+ mwl_vif->wep_key_conf[key->keyidx].enabled = 0;
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_check_ba(struct ieee80211_hw *hw,
+ struct mwl_ampdu_stream *stream,
+ struct ieee80211_vif *vif)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_bastream *pcmd;
+ u32 ba_flags, ba_type, ba_direction;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+ pcmd->cmd_hdr.result = cpu_to_le16(0xffff);
+
+ pcmd->action_type = cpu_to_le32(BA_CHECK_STREAM);
+ ether_addr_copy(&pcmd->ba_info.create_params.peer_mac_addr[0],
+ stream->sta->addr);
+ pcmd->ba_info.create_params.tid = stream->tid;
+ ba_type = BASTREAM_FLAG_IMMEDIATE_TYPE;
+ ba_direction = BASTREAM_FLAG_DIRECTION_UPSTREAM;
+ ba_flags = (ba_type & BA_TYPE_MASK) |
+ ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK);
+ pcmd->ba_info.create_params.flags = cpu_to_le32(ba_flags);
+ pcmd->ba_info.create_params.queue_id = stream->idx;
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "check ba failed execution\n");
+ return -EIO;
+ }
+
+ if (pcmd->cmd_hdr.result != 0) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_create_ba(struct ieee80211_hw *hw,
+ struct mwl_ampdu_stream *stream,
+ u8 buf_size, struct ieee80211_vif *vif)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct hostcmd_cmd_bastream *pcmd;
+ u32 ba_flags, ba_type, ba_direction;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->cmd_hdr.macid = mwl_vif->macid;
+ pcmd->cmd_hdr.result = cpu_to_le16(0xffff);
+
+ pcmd->action_type = cpu_to_le32(BA_CREATE_STREAM);
+ pcmd->ba_info.create_params.bar_thrs = cpu_to_le32(buf_size);
+ pcmd->ba_info.create_params.window_size = cpu_to_le32(buf_size);
+ ether_addr_copy(&pcmd->ba_info.create_params.peer_mac_addr[0],
+ stream->sta->addr);
+ pcmd->ba_info.create_params.tid = stream->tid;
+ ba_type = BASTREAM_FLAG_IMMEDIATE_TYPE;
+ ba_direction = BASTREAM_FLAG_DIRECTION_UPSTREAM;
+ ba_flags = (ba_type & BA_TYPE_MASK) |
+ ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK);
+ pcmd->ba_info.create_params.flags = cpu_to_le32(ba_flags);
+ pcmd->ba_info.create_params.queue_id = stream->idx;
+ pcmd->ba_info.create_params.param_info =
+ (stream->sta->ht_cap.ampdu_factor &
+ IEEE80211_HT_AMPDU_PARM_FACTOR) |
+ ((stream->sta->ht_cap.ampdu_density << 2) &
+ IEEE80211_HT_AMPDU_PARM_DENSITY);
+ pcmd->ba_info.create_params.reset_seq_no = 1;
+ pcmd->ba_info.create_params.current_seq = cpu_to_le16(0);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "create ba failed execution\n");
+ return -EIO;
+ }
+
+ if (pcmd->cmd_hdr.result != 0) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "create ba result error %d\n",
+ le16_to_cpu(pcmd->cmd_hdr.result));
+ return -EINVAL;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw,
+ u8 idx)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_bastream *pcmd;
+ u32 ba_flags, ba_type, ba_direction;
+
+ pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+
+ pcmd->action_type = cpu_to_le32(BA_DESTROY_STREAM);
+ ba_type = BASTREAM_FLAG_IMMEDIATE_TYPE;
+ ba_direction = BASTREAM_FLAG_DIRECTION_UPSTREAM;
+ ba_flags = (ba_type & BA_TYPE_MASK) |
+ ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK);
+ pcmd->ba_info.destroy_params.flags = cpu_to_le32(ba_flags);
+ pcmd->ba_info.destroy_params.fw_ba_context.context = cpu_to_le32(idx);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "destroy ba failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+/* caller must hold priv->stream_lock when calling the stream functions */
+struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta,
+ u8 tid)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_ampdu_stream *stream;
+ int i;
+
+ for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) {
+ stream = &priv->ampdu[i];
+
+ if (stream->state == AMPDU_NO_STREAM) {
+ stream->sta = sta;
+ stream->state = AMPDU_STREAM_NEW;
+ stream->tid = tid;
+ stream->idx = i;
+ return stream;
+ }
+ }
+
+ return NULL;
+}
+
+void mwl_fwcmd_del_sta_streams(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_ampdu_stream *stream;
+ int i;
+
+ for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) {
+ stream = &priv->ampdu[i];
+
+ if (stream->sta == sta) {
+ mwl_fwcmd_destroy_ba(hw, stream->idx);
+ mwl_fwcmd_remove_stream(hw, stream);
+ }
+ }
+}
+
+int mwl_fwcmd_start_stream(struct ieee80211_hw *hw,
+ struct mwl_ampdu_stream *stream)
+{
+ /* if the stream has already been started, don't start it again */
+ if (stream->state != AMPDU_STREAM_NEW)
+ return 0;
+
+ return ieee80211_start_tx_ba_session(stream->sta, stream->tid, 0);
+}
+
+void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw,
+ struct mwl_ampdu_stream *stream)
+{
+ memset(stream, 0, sizeof(*stream));
+}
+
+struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw,
+ u8 *addr, u8 tid)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_ampdu_stream *stream;
+ int i;
+
+ for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) {
+ stream = &priv->ampdu[i];
+
+ if (stream->state == AMPDU_NO_STREAM)
+ continue;
+
+ if (ether_addr_equal(stream->sta->addr, addr) &&
+ stream->tid == tid)
+ return stream;
+ }
+
+ return NULL;
+}
+
+bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid)
+{
+ struct mwl_sta *sta_info;
+ struct mwl_tx_info *tx_stats;
+
+ if (WARN_ON(tid >= SYSADPT_MAX_TID))
+ return false;
+
+ sta_info = mwl_dev_get_sta(sta);
+
+ tx_stats = &sta_info->tx_stats[tid];
+
+ return (sta_info->is_ampdu_allowed &&
+ tx_stats->pkts > SYSADPT_AMPDU_PACKET_THRESHOLD);
+}
+
+int mwl_fwcmd_set_optimization_level(struct ieee80211_hw *hw, u8 opt_level)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_optimization_level *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_optimization_level *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->opt_level = opt_level;
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_wsc_ie(struct ieee80211_hw *hw, u8 len, u8 *data)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_wsc_ie *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_wsc_ie *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WSC_IE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->len = cpu_to_le16(len);
+ memcpy(pcmd->data, data, len);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WSC_IE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ pcmd->ie_type = cpu_to_le16(WSC_IE_SET_PROBE_RESPONSE);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WSC_IE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_dwds_enable *pcmd;
+
+ pcmd = (struct hostcmd_cmd_dwds_enable *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DWDS_ENABLE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->enable = cpu_to_le32(enable);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_DWDS_ENABLE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_fw_flush_timer *pcmd;
+
+ pcmd = (struct hostcmd_cmd_fw_flush_timer *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_FW_FLUSH_TIMER);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->value = cpu_to_le32(value);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_FW_FLUSH_TIMER)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_set_cdd *pcmd;
+
+ pcmd = (struct hostcmd_cmd_set_cdd *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_CDD);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->enable = cpu_to_le32(priv->cdd);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_CDD)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_get_temp(struct ieee80211_hw *hw, u32 *temp)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_get_temp *pcmd;
+
+ pcmd = (struct hostcmd_cmd_get_temp *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_TEMP);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_TEMP)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ *temp = le32_to_cpu(pcmd->celcius);
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_get_fw_region_code(struct ieee80211_hw *hw,
+ u32 *fw_region_code)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_get_fw_region_code *pcmd;
+ int status;
+
+ pcmd = (struct hostcmd_cmd_get_fw_region_code *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_FW_REGION_CODE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_FW_REGION_CODE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ if (pcmd->cmd_hdr.result != 0) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ return -EINVAL;
+ }
+
+ status = le32_to_cpu(pcmd->status);
+
+ if (!status)
+ *fw_region_code = le32_to_cpu(pcmd->fw_region_code);
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_get_device_pwr_tbl(struct ieee80211_hw *hw,
+ struct mwl_device_pwr_tbl *device_ch_pwrtbl,
+ u8 *region_code,
+ u8 *number_of_channels,
+ u32 channel_index)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_get_device_pwr_tbl *pcmd;
+ int status;
+
+ pcmd = (struct hostcmd_cmd_get_device_pwr_tbl *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_DEVICE_PWR_TBL);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->status = cpu_to_le16(HOSTCMD_CMD_GET_DEVICE_PWR_TBL);
+ pcmd->current_channel_index = cpu_to_le32(channel_index);
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_DEVICE_PWR_TBL)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ device_ch_pwrtbl->channel = pcmd->channel_pwr_tbl.channel;
+ memcpy(device_ch_pwrtbl->tx_pwr, pcmd->channel_pwr_tbl.tx_pwr,
+ SYSADPT_TX_POWER_LEVEL_TOTAL);
+ device_ch_pwrtbl->dfs_capable = pcmd->channel_pwr_tbl.dfs_capable;
+ device_ch_pwrtbl->ax_ant = pcmd->channel_pwr_tbl.ax_ant;
+ device_ch_pwrtbl->cdd = pcmd->channel_pwr_tbl.cdd;
+ *region_code = pcmd->region_code;
+ *number_of_channels = pcmd->number_of_channels;
+ status = le16_to_cpu(pcmd->status);
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return status;
+}
+
+int mwl_fwcmd_quiet_mode(struct ieee80211_hw *hw, bool enable, u32 period,
+ u32 duration, u32 next_offset)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct hostcmd_cmd_quiet_mode *pcmd;
+
+ pcmd = (struct hostcmd_cmd_quiet_mode *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ memset(pcmd, 0x00, sizeof(*pcmd));
+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_QUIET_MODE);
+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd));
+ pcmd->action = cpu_to_le16(WL_SET);
+ pcmd->enable = cpu_to_le32(enable);
+ if (enable) {
+ pcmd->period = cpu_to_le32(period);
+ pcmd->duration = cpu_to_le32(duration);
+ pcmd->next_offset = cpu_to_le32(next_offset);
+ }
+
+ if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_QUIET_MODE)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(hw->wiphy, "failed execution\n");
+ return -EIO;
+ }
+
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
+
+int mwl_fwcmd_send_mfg_cmd(struct mwl_priv *priv, char *mfgcmd)
+{
+ struct hostcmd_header *pcmd;
+ struct cmd_header *cmd_hd = (struct cmd_header *)(mfgcmd + 4);
+ u16 len;
+ u16 cmd;
+
+ pcmd = (struct hostcmd_header *)&priv->pcmd_buf[0];
+
+ mutex_lock(&priv->fwcmd_mutex);
+
+ len = le16_to_cpu(cmd_hd->len);
+ memset(pcmd, 0x00, len + 4);
+ memcpy((char *)pcmd, mfgcmd, len + 4);
+ cmd = le16_to_cpu(cmd_hd->command);
+ if (mwl_fwcmd_exec_cmd(priv, cmd)) {
+ mutex_unlock(&priv->fwcmd_mutex);
+ wiphy_err(priv->hw->wiphy, "failed execution");
+ return -EIO;
+ }
+ cmd_hd = (struct cmd_header *)&priv->pcmd_buf[2];
+ len = le16_to_cpu(cmd_hd->len);
+ memcpy(mfgcmd, (char *)&priv->pcmd_buf[2], len);
+ mutex_unlock(&priv->fwcmd_mutex);
+
+ return 0;
+}
diff --git a/drivers/net/wireless/marvell/mwlwifi/fwcmd.h b/drivers/net/wireless/marvell/mwlwifi/fwcmd.h
new file mode 100644
index 0000000..74a21d3
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/fwcmd.h
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines firmware host command related
+ * functions.
+ */
+
+#ifndef _FWCMD_H_
+#define _FWCMD_H_
+
+/* Define OpMode for SoftAP/Station mode
+ *
+ * The following mode signature has to be written to PCI scratch register#0
+ * right after successfully downloading the last block of firmware and
+ * before waiting for firmware ready signature
+ */
+
+#define HOSTCMD_STA_MODE 0x5A
+#define HOSTCMD_SOFTAP_MODE 0xA5
+
+#define HOSTCMD_STA_FWRDY_SIGNATURE 0xF0F1F2F4
+#define HOSTCMD_SOFTAP_FWRDY_SIGNATURE 0xF1F2F4A5
+
+#define GUARD_INTERVAL_STANDARD 1
+#define GUARD_INTERVAL_SHORT 2
+#define GUARD_INTERVAL_AUTO 3
+
+#define LINK_CS_STATE_CONSERV 0
+#define LINK_CS_STATE_AGGR 1
+#define LINK_CS_STATE_AUTO 2
+#define LINK_CS_STATE_AUTO_DISABLED 3
+
+#define STOP_DETECT_RADAR 0
+#define CAC_START 1
+#define MONITOR_START 3
+
+enum {
+ WL_ANTENNATYPE_RX = 1,
+ WL_ANTENNATYPE_TX = 2,
+};
+
+enum encr_type {
+ ENCR_TYPE_WEP = 0,
+ ENCR_TYPE_DISABLE = 1,
+ ENCR_TYPE_TKIP = 4,
+ ENCR_TYPE_AES = 6,
+ ENCR_TYPE_MIX = 7,
+};
+
+void mwl_fwcmd_reset(struct ieee80211_hw *hw);
+
+void mwl_fwcmd_int_enable(struct ieee80211_hw *hw);
+
+void mwl_fwcmd_int_disable(struct ieee80211_hw *hw);
+
+int mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw);
+
+int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw);
+
+int mwl_fwcmd_get_stat(struct ieee80211_hw *hw,
+ struct ieee80211_low_level_stats *stats);
+
+int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw);
+
+int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw);
+
+int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw,
+ bool short_preamble);
+
+int mwl_fwcmd_get_addr_value(struct ieee80211_hw *hw, u32 addr, u32 len,
+ u32 *val, u16 set);
+
+int mwl_fwcmd_max_tx_power(struct ieee80211_hw *hw,
+ struct ieee80211_conf *conf, u8 fraction);
+
+int mwl_fwcmd_tx_power(struct ieee80211_hw *hw,
+ struct ieee80211_conf *conf, u8 fraction);
+
+int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int dir, int antenna);
+
+int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, bool enable);
+
+int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw,
+ struct ieee80211_conf *conf);
+
+int mwl_fwcmd_set_aid(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *bssid, u16 aid);
+
+int mwl_fwcmd_set_infra_mode(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif);
+
+int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw,
+ int threshold);
+
+int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, u8 index,
+ u16 cw_min, u16 cw_max, u8 aifs, u16 txop);
+
+int mwl_fwcmd_set_radar_detect(struct ieee80211_hw *hw, u16 action);
+
+int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, bool enable);
+
+int mwl_fwcmd_ht_guard_interval(struct ieee80211_hw *hw, u32 gi_type);
+
+int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw,
+ int mcast, int mgmt);
+
+int mwl_fwcmd_set_linkadapt_cs_mode(struct ieee80211_hw *hw,
+ u16 cs_mode);
+
+int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw,
+ u16 mode);
+
+int mwl_fwcmd_set_mac_addr_client(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *mac_addr);
+
+int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw,
+ u8 *bitmap);
+
+int mwl_fwcmd_remove_mac_addr(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *mac_addr);
+
+int mwl_fwcmd_bss_start(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, bool enable);
+
+int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *beacon, int len);
+
+int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta);
+
+int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif);
+
+int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *addr);
+
+int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode);
+
+int mwl_fwcmd_set_switch_channel(struct mwl_priv *priv,
+ struct ieee80211_channel_switch *ch_switch);
+
+int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ u8 *addr, u8 encr_type);
+
+int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *addr,
+ struct ieee80211_key_conf *key);
+
+int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u8 *addr,
+ struct ieee80211_key_conf *key);
+
+int mwl_fwcmd_check_ba(struct ieee80211_hw *hw,
+ struct mwl_ampdu_stream *stream,
+ struct ieee80211_vif *vif);
+
+int mwl_fwcmd_create_ba(struct ieee80211_hw *hw,
+ struct mwl_ampdu_stream *stream,
+ u8 buf_size, struct ieee80211_vif *vif);
+
+int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw,
+ u8 idx);
+
+struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta,
+ u8 tid);
+
+void mwl_fwcmd_del_sta_streams(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta);
+
+int mwl_fwcmd_start_stream(struct ieee80211_hw *hw,
+ struct mwl_ampdu_stream *stream);
+
+void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw,
+ struct mwl_ampdu_stream *stream);
+
+struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw,
+ u8 *addr, u8 tid);
+
+bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid);
+
+int mwl_fwcmd_set_optimization_level(struct ieee80211_hw *hw, u8 opt_level);
+
+int mwl_fwcmd_set_wsc_ie(struct ieee80211_hw *hw, u8 len, u8 *data);
+
+int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable);
+
+int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value);
+
+int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw);
+
+int mwl_fwcmd_get_temp(struct ieee80211_hw *hw, u32 *temp);
+
+int mwl_fwcmd_get_fw_region_code(struct ieee80211_hw *hw,
+ u32 *fw_region_code);
+
+int mwl_fwcmd_get_device_pwr_tbl(struct ieee80211_hw *hw,
+ struct mwl_device_pwr_tbl *device_ch_pwrtbl,
+ u8 *region_code,
+ u8 *number_of_channels,
+ u32 channel_index);
+
+int mwl_fwcmd_quiet_mode(struct ieee80211_hw *hw, bool enable, u32 period,
+ u32 duration, u32 next_offset);
+
+int mwl_fwcmd_send_mfg_cmd(struct mwl_priv *priv, char *mfgcmd);
+
+#endif /* _FWCMD_H_ */
diff --git a/drivers/net/wireless/marvell/mwlwifi/fwdl.c b/drivers/net/wireless/marvell/mwlwifi/fwdl.c
new file mode 100644
index 0000000..f4d5fa1
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/fwdl.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file implements firmware download related
+ * functions.
+ */
+
+#include <linux/io.h>
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "fwcmd.h"
+#include "fwdl.h"
+
+#define FW_DOWNLOAD_BLOCK_SIZE 256
+#define FW_CHECK_MSECS 3
+
+#define FW_MAX_NUM_CHECKS 0xffff
+
+static void mwl_fwdl_trig_pcicmd(struct mwl_priv *priv)
+{
+ writel(priv->pphys_cmd_buf, priv->iobase1 + MACREG_REG_GEN_PTR);
+
+ writel(0x00, priv->iobase1 + MACREG_REG_INT_CODE);
+
+ writel(MACREG_H2ARIC_BIT_DOOR_BELL,
+ priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS);
+}
+
+static void mwl_fwdl_trig_pcicmd_bootcode(struct mwl_priv *priv)
+{
+ writel(priv->pphys_cmd_buf, priv->iobase1 + MACREG_REG_GEN_PTR);
+
+ writel(0x00, priv->iobase1 + MACREG_REG_INT_CODE);
+
+ writel(MACREG_H2ARIC_BIT_DOOR_BELL,
+ priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS);
+}
+
+int mwl_fwdl_download_firmware(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+ const struct firmware *fw;
+ u32 curr_iteration = 0;
+ u32 size_fw_downloaded = 0;
+ u32 int_code = 0;
+ u32 len = 0;
+ u32 fwreadysignature = HOSTCMD_SOFTAP_FWRDY_SIGNATURE;
+
+ fw = priv->fw_ucode;
+
+ mwl_fwcmd_reset(hw);
+
+ /* FW before jumping to boot rom, it will enable PCIe transaction retry,
+ * wait for boot code to stop it.
+ */
+ usleep_range(FW_CHECK_MSECS * 1000, FW_CHECK_MSECS * 2000);
+
+ writel(MACREG_A2HRIC_BIT_MASK,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CLEAR_SEL);
+ writel(0x00, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE);
+ writel(0x00, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK);
+ writel(MACREG_A2HRIC_BIT_MASK,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+
+ /* this routine interacts with SC2 bootrom to download firmware binary
+ * to the device. After DMA'd to SC2, the firmware could be deflated to
+ * reside on its respective blocks such as ITCM, DTCM, SQRAM,
+ * (or even DDR, AFTER DDR is init'd before fw download
+ */
+ wiphy_debug(hw->wiphy, "fw download start\n");
+
+ /* Disable PFU before FWDL */
+ writel(0x100, priv->iobase1 + 0xE0E4);
+
+ /* make sure SCRATCH2 C40 is clear, in case we are too quick */
+ while (readl(priv->iobase1 + 0xc40) == 0)
+ cond_resched();
+
+ while (size_fw_downloaded < fw->size) {
+ len = readl(priv->iobase1 + 0xc40);
+
+ if (!len)
+ break;
+
+ /* this copies the next chunk of fw binary to be delivered */
+ memcpy((char *)&priv->pcmd_buf[0],
+ (fw->data + size_fw_downloaded), len);
+
+ /* this function writes pdata to c10, then write 2 to c18 */
+ mwl_fwdl_trig_pcicmd_bootcode(priv);
+
+ /* this is arbitrary per your platform; we use 0xffff */
+ curr_iteration = FW_MAX_NUM_CHECKS;
+
+ /* NOTE: the following back to back checks on C1C is time
+ * sensitive, hence may need to be tweaked dependent on host
+ * processor. Time for SC2 to go from the write of event 2 to
+ * C1C == 2 is ~1300 nSec. Hence the checkings on host has to
+ * consider how efficient your code can be to meet this timing,
+ * or you can alternatively tweak this routines to fit your
+ * platform
+ */
+ do {
+ int_code = readl(priv->iobase1 + 0xc1c);
+ if (int_code != 0)
+ break;
+ cond_resched();
+ curr_iteration--;
+ } while (curr_iteration);
+
+ do {
+ int_code = readl(priv->iobase1 + 0xc1c);
+ if ((int_code & MACREG_H2ARIC_BIT_DOOR_BELL) !=
+ MACREG_H2ARIC_BIT_DOOR_BELL)
+ break;
+ cond_resched();
+ curr_iteration--;
+ } while (curr_iteration);
+
+ if (curr_iteration == 0) {
+ /* This limited loop check allows you to exit gracefully
+ * without locking up your entire system just because fw
+ * download failed
+ */
+ wiphy_err(hw->wiphy,
+ "Exhausted curr_iteration for fw download\n");
+ goto err_download;
+ }
+
+ size_fw_downloaded += len;
+ }
+
+ wiphy_debug(hw->wiphy,
+ "FwSize = %d downloaded Size = %d curr_iteration %d\n",
+ (int)fw->size, size_fw_downloaded, curr_iteration);
+
+ /* Now firware is downloaded successfully, so this part is to check
+ * whether fw can properly execute to an extent that write back
+ * signature to indicate its readiness to the host. NOTE: if your
+ * downloaded fw crashes, this signature checking will fail. This
+ * part is similar as SC1
+ */
+ *((u32 *)&priv->pcmd_buf[1]) = 0;
+ mwl_fwdl_trig_pcicmd(priv);
+ curr_iteration = FW_MAX_NUM_CHECKS;
+ do {
+ curr_iteration--;
+ writel(HOSTCMD_SOFTAP_MODE,
+ priv->iobase1 + MACREG_REG_GEN_PTR);
+ usleep_range(FW_CHECK_MSECS * 1000, FW_CHECK_MSECS * 2000);
+ int_code = readl(priv->iobase1 + MACREG_REG_INT_CODE);
+ if (!(curr_iteration % 0xff) && (int_code != 0))
+ wiphy_err(hw->wiphy, "%x;", int_code);
+ } while ((curr_iteration) &&
+ (int_code != fwreadysignature));
+
+ if (curr_iteration == 0) {
+ wiphy_err(hw->wiphy,
+ "Exhausted curr_iteration for fw signature\n");
+ goto err_download;
+ }
+
+ wiphy_debug(hw->wiphy, "fw download complete\n");
+ writel(0x00, priv->iobase1 + MACREG_REG_INT_CODE);
+
+ return 0;
+
+err_download:
+
+ mwl_fwcmd_reset(hw);
+
+ return -EIO;
+}
diff --git a/drivers/net/wireless/marvell/mwlwifi/fwdl.h b/drivers/net/wireless/marvell/mwlwifi/fwdl.h
new file mode 100644
index 0000000..eedf4dd
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/fwdl.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines firmware download related
+ * functions.
+ */
+
+#ifndef _FWDL_H_
+#define _FWDL_H_
+
+int mwl_fwdl_download_firmware(struct ieee80211_hw *hw);
+
+#endif /* _FWDL_H_ */
diff --git a/drivers/net/wireless/marvell/mwlwifi/hostcmd.h b/drivers/net/wireless/marvell/mwlwifi/hostcmd.h
new file mode 100644
index 0000000..b163a94
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/hostcmd.h
@@ -0,0 +1,913 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines firmware host command related
+ * structure.
+ */
+
+#ifndef _HOSTCMD_H_
+#define _HOSTCMD_H_
+
+/* 16 bit host command code */
+#define HOSTCMD_CMD_GET_HW_SPEC 0x0003
+#define HOSTCMD_CMD_SET_HW_SPEC 0x0004
+#define HOSTCMD_CMD_802_11_GET_STAT 0x0014
+#define HOSTCMD_CMD_802_11_RADIO_CONTROL 0x001c
+#define HOSTCMD_CMD_MEM_ADDR_ACCESS 0x001d
+#define HOSTCMD_CMD_802_11_TX_POWER 0x001f
+#define HOSTCMD_CMD_802_11_RF_ANTENNA 0x0020
+#define HOSTCMD_CMD_BROADCAST_SSID_ENABLE 0x0050 /* per-vif */
+#define HOSTCMD_CMD_SET_RF_CHANNEL 0x010a
+#define HOSTCMD_CMD_SET_AID 0x010d /* per-vif */
+#define HOSTCMD_CMD_SET_INFRA_MODE 0x010e /* per-vif */
+#define HOSTCMD_CMD_802_11_RTS_THSD 0x0113
+#define HOSTCMD_CMD_SET_EDCA_PARAMS 0x0115
+#define HOSTCMD_CMD_802_11H_DETECT_RADAR 0x0120
+#define HOSTCMD_CMD_SET_WMM_MODE 0x0123
+#define HOSTCMD_CMD_HT_GUARD_INTERVAL 0x0124
+#define HOSTCMD_CMD_SET_FIXED_RATE 0x0126
+#define HOSTCMD_CMD_SET_IES 0x0127
+#define HOSTCMD_CMD_SET_LINKADAPT_CS_MODE 0x0129
+#define HOSTCMD_CMD_SET_MAC_ADDR 0x0202 /* per-vif */
+#define HOSTCMD_CMD_SET_RATE_ADAPT_MODE 0x0203
+#define HOSTCMD_CMD_GET_WATCHDOG_BITMAP 0x0205
+#define HOSTCMD_CMD_DEL_MAC_ADDR 0x0206 /* per-vif */
+#define HOSTCMD_CMD_BSS_START 0x1100 /* per-vif */
+#define HOSTCMD_CMD_AP_BEACON 0x1101 /* per-vif */
+#define HOSTCMD_CMD_SET_NEW_STN 0x1111 /* per-vif */
+#define HOSTCMD_CMD_SET_APMODE 0x1114
+#define HOSTCMD_CMD_SET_SWITCH_CHANNEL 0x1121
+#define HOSTCMD_CMD_UPDATE_ENCRYPTION 0x1122 /* per-vif */
+#define HOSTCMD_CMD_BASTREAM 0x1125
+#define HOSTCMD_CMD_SET_SPECTRUM_MGMT 0x1128
+#define HOSTCMD_CMD_SET_POWER_CONSTRAINT 0x1129
+#define HOSTCMD_CMD_SET_COUNTRY_CODE 0x1130
+#define HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL 0x1133
+#define HOSTCMD_CMD_SET_WSC_IE 0x1136 /* per-vif */
+#define HOSTCMD_CMD_DWDS_ENABLE 0x1144
+#define HOSTCMD_CMD_FW_FLUSH_TIMER 0x1148
+#define HOSTCMD_CMD_SET_CDD 0x1150
+#define HOSTCMD_CMD_GET_TEMP 0x1159
+#define HOSTCMD_CMD_GET_FW_REGION_CODE 0x116A
+#define HOSTCMD_CMD_GET_DEVICE_PWR_TBL 0x116B
+#define HOSTCMD_CMD_QUIET_MODE 0x1201
+
+/* Define general result code for each command */
+#define HOSTCMD_RESULT_OK 0x0000
+/* General error */
+#define HOSTCMD_RESULT_ERROR 0x0001
+/* Command is not valid */
+#define HOSTCMD_RESULT_NOT_SUPPORT 0x0002
+/* Command is pending (will be processed) */
+#define HOSTCMD_RESULT_PENDING 0x0003
+/* System is busy (command ignored) */
+#define HOSTCMD_RESULT_BUSY 0x0004
+/* Data buffer is not big enough */
+#define HOSTCMD_RESULT_PARTIAL_DATA 0x0005
+
+/* Define channel related constants */
+#define FREQ_BAND_2DOT4GHZ 0x1
+#define FREQ_BAND_4DOT9GHZ 0x2
+#define FREQ_BAND_5GHZ 0x4
+#define FREQ_BAND_5DOT2GHZ 0x8
+#define CH_AUTO_WIDTH 0
+#define CH_10_MHZ_WIDTH 0x1
+#define CH_20_MHZ_WIDTH 0x2
+#define CH_40_MHZ_WIDTH 0x4
+#define CH_80_MHZ_WIDTH 0x5
+#define EXT_CH_ABOVE_CTRL_CH 0x1
+#define EXT_CH_AUTO 0x2
+#define EXT_CH_BELOW_CTRL_CH 0x3
+#define NO_EXT_CHANNEL 0x0
+
+#define ACT_PRIMARY_CHAN_0 0
+#define ACT_PRIMARY_CHAN_1 1
+#define ACT_PRIMARY_CHAN_2 2
+#define ACT_PRIMARY_CHAN_3 3
+
+/* Define rate related constants */
+#define HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002
+
+/* Define station related constants */
+#define HOSTCMD_ACT_STA_ACTION_ADD 0
+#define HOSTCMD_ACT_STA_ACTION_REMOVE 2
+
+/* Define key related constants */
+#define MAX_ENCR_KEY_LENGTH 16
+#define MIC_KEY_LENGTH 8
+
+#define KEY_TYPE_ID_WEP 0x00
+#define KEY_TYPE_ID_TKIP 0x01
+#define KEY_TYPE_ID_AES 0x02
+
+/* Group key for RX only */
+#define ENCR_KEY_FLAG_RXGROUPKEY 0x00000002
+#define ENCR_KEY_FLAG_TXGROUPKEY 0x00000004
+#define ENCR_KEY_FLAG_PAIRWISE 0x00000008
+#define ENCR_KEY_FLAG_TSC_VALID 0x00000040
+#define ENCR_KEY_FLAG_WEP_TXKEY 0x01000000
+#define ENCR_KEY_FLAG_MICKEY_VALID 0x02000000
+
+/* Define block ack related constants */
+#define BASTREAM_FLAG_IMMEDIATE_TYPE 1
+#define BASTREAM_FLAG_DIRECTION_UPSTREAM 0
+
+/* Define general purpose action */
+#define HOSTCMD_ACT_GEN_SET 0x0001
+#define HOSTCMD_ACT_GEN_SET_LIST 0x0002
+#define HOSTCMD_ACT_GEN_GET_LIST 0x0003
+
+/* Misc */
+#define WSC_IE_MAX_LENGTH 251
+#define WSC_IE_SET_BEACON 0
+#define WSC_IE_SET_PROBE_RESPONSE 1
+
+enum {
+ WL_DISABLE = 0,
+ WL_ENABLE = 1,
+ WL_DISABLE_VMAC = 0x80,
+};
+
+enum {
+ WL_GET = 0,
+ WL_SET = 1,
+ WL_RESET = 2,
+};
+
+enum {
+ WL_LONG_PREAMBLE = 1,
+ WL_SHORT_PREAMBLE = 3,
+ WL_AUTO_PREAMBLE = 5,
+};
+
+enum encr_action_type {
+ /* request to enable/disable HW encryption */
+ ENCR_ACTION_ENABLE_HW_ENCR,
+ /* request to set encryption key */
+ ENCR_ACTION_TYPE_SET_KEY,
+ /* request to remove one or more keys */
+ ENCR_ACTION_TYPE_REMOVE_KEY,
+ ENCR_ACTION_TYPE_SET_GROUP_KEY,
+};
+
+enum ba_action_type {
+ BA_CREATE_STREAM,
+ BA_UPDATE_STREAM,
+ BA_DESTROY_STREAM,
+ BA_FLUSH_STREAM,
+ BA_CHECK_STREAM,
+};
+
+enum mac_type {
+ WL_MAC_TYPE_PRIMARY_CLIENT,
+ WL_MAC_TYPE_SECONDARY_CLIENT,
+ WL_MAC_TYPE_PRIMARY_AP,
+ WL_MAC_TYPE_SECONDARY_AP,
+};
+
+/* General host command header */
+struct hostcmd_header {
+ __le16 cmd;
+ __le16 len;
+ u8 seq_num;
+ u8 macid;
+ __le16 result;
+} __packed;
+
+/* HOSTCMD_CMD_GET_HW_SPEC */
+struct hostcmd_cmd_get_hw_spec {
+ struct hostcmd_header cmd_hdr;
+ u8 version; /* version of the HW */
+ u8 host_if; /* host interface */
+ __le16 num_wcb; /* Max. number of WCB FW can handle */
+ __le16 num_mcast_addr; /* MaxNbr of MC addresses FW can handle */
+ u8 permanent_addr[ETH_ALEN]; /* MAC address programmed in HW */
+ __le16 region_code;
+ __le16 num_antenna; /* Number of antenna used */
+ __le32 fw_release_num; /* 4 byte of FW release number */
+ __le32 wcb_base0;
+ __le32 rxpd_wr_ptr;
+ __le32 rxpd_rd_ptr;
+ __le32 fw_awake_cookie;
+ __le32 wcb_base[SYSADPT_TOTAL_TX_QUEUES - 1];
+} __packed;
+
+/* HOSTCMD_CMD_SET_HW_SPEC */
+struct hostcmd_cmd_set_hw_spec {
+ struct hostcmd_header cmd_hdr;
+ /* HW revision */
+ u8 version;
+ /* Host interface */
+ u8 host_if;
+ /* Max. number of Multicast address FW can handle */
+ __le16 num_mcast_addr;
+ /* MAC address */
+ u8 permanent_addr[ETH_ALEN];
+ /* Region Code */
+ __le16 region_code;
+ /* 4 byte of FW release number, example 0x1234=1.2.3.4 */
+ __le32 fw_release_num;
+ /* Firmware awake cookie - used to ensure that the device
+ * is not in sleep mode
+ */
+ __le32 fw_awake_cookie;
+ /* Device capabilities (see above) */
+ __le32 device_caps;
+ /* Rx shared memory queue */
+ __le32 rxpd_wr_ptr;
+ /* Actual number of TX queues in WcbBase array */
+ __le32 num_tx_queues;
+ /* TX WCB Rings */
+ __le32 wcb_base[SYSADPT_NUM_OF_DESC_DATA];
+ /* Max AMSDU size (00 - AMSDU Disabled,
+ * 01 - 4K, 10 - 8K, 11 - not defined)
+ */
+ __le32 features;
+ __le32 tx_wcb_num_per_queue;
+ __le32 total_rx_wcb;
+} __packed;
+
+/* HOSTCMD_CMD_802_11_GET_STAT */
+struct hostcmd_cmd_802_11_get_stat {
+ struct hostcmd_header cmd_hdr;
+ __le32 tx_retry_successes;
+ __le32 tx_multiple_retry_successes;
+ __le32 tx_failures;
+ __le32 rts_successes;
+ __le32 rts_failures;
+ __le32 ack_failures;
+ __le32 rx_duplicate_frames;
+ __le32 rx_fcs_errors;
+ __le32 tx_watchdog_timeouts;
+ __le32 rx_overflows;
+ __le32 rx_frag_errors;
+ __le32 rx_mem_errors;
+ __le32 pointer_errors;
+ __le32 tx_underflows;
+ __le32 tx_done;
+ __le32 tx_done_buf_try_put;
+ __le32 tx_done_buf_put;
+ /* Put size of requested buffer in here */
+ __le32 wait_for_tx_buf;
+ __le32 tx_attempts;
+ __le32 tx_successes;
+ __le32 tx_fragments;
+ __le32 tx_multicasts;
+ __le32 rx_non_ctl_pkts;
+ __le32 rx_multicasts;
+ __le32 rx_undecryptable_frames;
+ __le32 rx_icv_errors;
+ __le32 rx_excluded_frames;
+ __le32 rx_weak_iv_count;
+ __le32 rx_unicasts;
+ __le32 rx_bytes;
+ __le32 rx_errors;
+ __le32 rx_rts_count;
+ __le32 tx_cts_count;
+} __packed;
+
+/* HOSTCMD_CMD_802_11_RADIO_CONTROL */
+struct hostcmd_cmd_802_11_radio_control {
+ struct hostcmd_header cmd_hdr;
+ __le16 action;
+ /* @bit0: 1/0,on/off, @bit1: 1/0, long/short @bit2: 1/0,auto/fix */
+ __le16 control;
+ __le16 radio_on;
+} __packed;
+
+/* HOSTCMD_CMD_MEM_ADDR_ACCESS */
+struct hostcmd_cmd_mem_addr_access {
+ struct hostcmd_header cmd_hdr;
+ __le32 address;
+ __le16 length;
+ __le16 reserved;
+ __le32 value[64];
+} __packed;
+
+/* HOSTCMD_CMD_802_11_TX_POWER */
+struct hostcmd_cmd_802_11_tx_power {
+ struct hostcmd_header cmd_hdr;
+ __le16 action;
+ __le16 band;
+ __le16 ch;
+ __le16 bw;
+ __le16 sub_ch;
+ __le16 power_level_list[SYSADPT_TX_POWER_LEVEL_TOTAL];
+} __packed;
+
+/* HOSTCMD_CMD_802_11_RF_ANTENNA */
+struct hostcmd_cmd_802_11_rf_antenna {
+ struct hostcmd_header cmd_hdr;
+ __le16 action;
+ __le16 antenna_mode; /* Number of antennas or 0xffff(diversity) */
+} __packed;
+
+/* HOSTCMD_CMD_BROADCAST_SSID_ENABLE */
+struct hostcmd_cmd_broadcast_ssid_enable {
+ struct hostcmd_header cmd_hdr;
+ __le32 enable;
+} __packed;
+
+/* HOSTCMD_CMD_SET_RF_CHANNEL */
+#define FREQ_BAND_MASK 0x0000003f
+#define CHNL_WIDTH_MASK 0x000007c0
+#define CHNL_WIDTH_SHIFT 6
+#define ACT_PRIMARY_MASK 0x00003800
+#define ACT_PRIMARY_SHIFT 11
+
+struct hostcmd_cmd_set_rf_channel {
+ struct hostcmd_header cmd_hdr;
+ __le16 action;
+ u8 curr_chnl;
+ __le32 chnl_flags;
+} __packed;
+
+/* HOSTCMD_CMD_SET_AID */
+struct hostcmd_cmd_set_aid {
+ struct hostcmd_header cmd_hdr;
+ __le16 aid;
+ u8 mac_addr[ETH_ALEN]; /* AP's Mac Address(BSSID) */
+ __le32 gprotect;
+ u8 ap_rates[SYSADPT_MAX_DATA_RATES_G];
+} __packed;
+
+/* HOSTCMD_CMD_SET_INFRA_MODE */
+struct hostcmd_cmd_set_infra_mode {
+ struct hostcmd_header cmd_hdr;
+} __packed;
+
+/* HOSTCMD_CMD_802_11_RTS_THSD */
+struct hostcmd_cmd_802_11_rts_thsd {
+ struct hostcmd_header cmd_hdr;
+ __le16 action;
+ __le16 threshold;
+} __packed;
+
+/* HOSTCMD_CMD_SET_EDCA_PARAMS */
+struct hostcmd_cmd_set_edca_params {
+ struct hostcmd_header cmd_hdr;
+ /* 0 = get all, 0x1 =set CWMin/Max, 0x2 = set TXOP , 0x4 =set AIFSN */
+ __le16 action;
+ __le16 txop; /* in unit of 32 us */
+ __le32 cw_max; /* 0~15 */
+ __le32 cw_min; /* 0~15 */
+ u8 aifsn;
+ u8 txq_num; /* Tx Queue number. */
+} __packed;
+
+/* HOSTCMD_CMD_802_11H_DETECT_RADAR */
+#define RADAR_TYPE_CODE_0 0
+#define RADAR_TYPE_CODE_53 53
+#define RADAR_TYPE_CODE_56 56
+#define RADAR_TYPE_CODE_ETSI 151
+
+struct hostcmd_cmd_802_11h_detect_radar {
+ struct hostcmd_header cmd_hdr;
+ __le16 action;
+ __le16 radar_type_code;
+ __le16 min_chirp_cnt;
+ __le16 chirp_time_intvl;
+ __le16 pw_filter;
+ __le16 min_num_radar;
+ __le16 pri_min_num;
+} __packed;
+
+/* HOSTCMD_CMD_SET_WMM_MODE */
+struct hostcmd_cmd_set_wmm_mode {
+ struct hostcmd_header cmd_hdr;
+ __le16 action; /* 0->unset, 1->set */
+} __packed;
+
+/* HOSTCMD_CMD_HT_GUARD_INTERVAL */
+struct hostcmd_cmd_ht_guard_interval {
+ struct hostcmd_header cmd_hdr;
+ __le32 action;
+ __le32 gi_type;
+} __packed;
+
+/* HOSTCMD_CMD_SET_FIXED_RATE */
+struct fix_rate_flag { /* lower rate after the retry count */
+ /* 0: legacy, 1: HT */
+ __le32 fix_rate_type;
+ /* 0: retry count is not valid, 1: use retry count specified */
+ __le32 retry_count_valid;
+} __packed;
+
+struct fix_rate_entry {
+ struct fix_rate_flag fix_rate_type_flags;
+ /* depending on the flags above, this can be either a legacy
+ * rate(not index) or an MCS code.
+ */
+ __le32 fixed_rate;
+ __le32 retry_count;
+} __packed;
+
+struct hostcmd_cmd_set_fixed_rate {
+ struct hostcmd_header cmd_hdr;
+ /* HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002 */
+ __le32 action;
+ /* use fixed rate specified but firmware can drop to */
+ __le32 allow_rate_drop;
+ __le32 entry_count;
+ struct fix_rate_entry fixed_rate_table[4];
+ u8 multicast_rate;
+ u8 multi_rate_tx_type;
+ u8 management_rate;
+} __packed;
+
+/* HOSTCMD_CMD_SET_IES */
+struct hostcmd_cmd_set_ies {
+ struct hostcmd_header cmd_hdr;
+ __le16 action; /* 0->unset, 1->set */
+ __le16 ie_list_len_ht;
+ __le16 ie_list_len_vht;
+ __le16 ie_list_len_proprietary;
+ /*Buffer size same as Generic_Beacon*/
+ u8 ie_list_ht[148];
+ u8 ie_list_vht[24];
+ u8 ie_list_proprietary[112];
+} __packed;
+
+/* HOSTCMD_CMD_SET_LINKADAPT_CS_MODE */
+struct hostcmd_cmd_set_linkadapt_cs_mode {
+ struct hostcmd_header cmd_hdr;
+ __le16 action;
+ __le16 cs_mode;
+} __packed;
+
+/* HOSTCMD_CMD_SET_MAC_ADDR, HOSTCMD_CMD_DEL_MAC_ADDR */
+struct hostcmd_cmd_set_mac_addr {
+ struct hostcmd_header cmd_hdr;
+ __le16 mac_type;
+ u8 mac_addr[ETH_ALEN];
+} __packed;
+
+/* HOSTCMD_CMD_SET_RATE_ADAPT_MODE */
+struct hostcmd_cmd_set_rate_adapt_mode {
+ struct hostcmd_header cmd_hdr;
+ __le16 action;
+ __le16 rate_adapt_mode; /* 0:Indoor, 1:Outdoor */
+} __packed;
+
+/* HOSTCMD_CMD_GET_WATCHDOG_BITMAP */
+struct hostcmd_cmd_get_watchdog_bitmap {
+ struct hostcmd_header cmd_hdr;
+ u8 watchdog_bitmap; /* for SW/BA */
+} __packed;
+
+/* HOSTCMD_CMD_BSS_START */
+struct hostcmd_cmd_bss_start {
+ struct hostcmd_header cmd_hdr;
+ __le32 enable; /* FALSE: Disable or TRUE: Enable */
+} __packed;
+
+/* HOSTCMD_CMD_AP_BEACON */
+struct cf_params {
+ u8 elem_id;
+ u8 len;
+ u8 cfp_cnt;
+ u8 cfp_period;
+ __le16 cfp_max_duration;
+ __le16 cfp_duration_remaining;
+} __packed;
+
+struct ibss_params {
+ u8 elem_id;
+ u8 len;
+ __le16 atim_window;
+} __packed;
+
+union ss_params {
+ struct cf_params cf_param_set;
+ struct ibss_params ibss_param_set;
+} __packed;
+
+struct fh_params {
+ u8 elem_id;
+ u8 len;
+ __le16 dwell_time;
+ u8 hop_set;
+ u8 hop_pattern;
+ u8 hop_index;
+} __packed;
+
+struct ds_params {
+ u8 elem_id;
+ u8 len;
+ u8 current_chnl;
+} __packed;
+
+union phy_params {
+ struct fh_params fh_param_set;
+ struct ds_params ds_param_set;
+} __packed;
+
+struct rsn_ie {
+ u8 elem_id;
+ u8 len;
+ u8 oui_type[4]; /* 00:50:f2:01 */
+ u8 ver[2];
+ u8 grp_key_cipher[4];
+ u8 pws_key_cnt[2];
+ u8 pws_key_cipher_list[4];
+ u8 auth_key_cnt[2];
+ u8 auth_key_list[4];
+} __packed;
+
+struct rsn48_ie {
+ u8 elem_id;
+ u8 len;
+ u8 ver[2];
+ u8 grp_key_cipher[4];
+ u8 pws_key_cnt[2];
+ u8 pws_key_cipher_list[4];
+ u8 auth_key_cnt[2];
+ u8 auth_key_list[4];
+ u8 rsn_cap[2];
+ u8 pmk_id_cnt[2];
+ u8 pmk_id_list[16]; /* Should modify to 16 * S */
+ u8 reserved[8];
+} __packed;
+
+struct ac_param_rcd {
+ u8 aci_aifsn;
+ u8 ecw_min_max;
+ __le16 txop_lim;
+} __packed;
+
+struct wmm_param_elem {
+ u8 elem_id;
+ u8 len;
+ u8 oui[3];
+ u8 type;
+ u8 sub_type;
+ u8 version;
+ u8 rsvd;
+ struct ac_param_rcd ac_be;
+ struct ac_param_rcd ac_bk;
+ struct ac_param_rcd ac_vi;
+ struct ac_param_rcd ac_vo;
+} __packed;
+
+struct channel_info {
+ u8 first_channel_num;
+ u8 num_channels;
+ u8 max_tx_pwr_level;
+} __packed;
+
+struct country {
+ u8 elem_id;
+ u8 len;
+ u8 country_str[3];
+ struct channel_info channel_info[40];
+} __packed;
+
+struct start_cmd {
+ u8 sta_mac_addr[ETH_ALEN];
+ u8 ssid[IEEE80211_MAX_SSID_LEN];
+ u8 bss_type;
+ __le16 bcn_period;
+ u8 dtim_period;
+ union ss_params ss_param_set;
+ union phy_params phy_param_set;
+ __le16 probe_delay;
+ __le16 cap_info;
+ u8 b_rate_set[SYSADPT_MAX_DATA_RATES_G];
+ u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G];
+ struct rsn_ie rsn_ie;
+ struct rsn48_ie rsn48_ie;
+ struct wmm_param_elem wmm_param;
+ struct country country;
+ __le32 ap_rf_type; /* 0->B, 1->G, 2->Mixed, 3->A, 4->11J */
+} __packed;
+
+struct hostcmd_cmd_ap_beacon {
+ struct hostcmd_header cmd_hdr;
+ struct start_cmd start_cmd;
+} __packed;
+
+/* HOSTCMD_CMD_SET_NEW_STN */
+struct add_ht_info {
+ u8 control_chnl;
+ u8 add_chnl;
+ __le16 op_mode;
+ __le16 stbc;
+} __packed;
+
+struct peer_info {
+ __le32 legacy_rate_bitmap;
+ u8 ht_rates[4];
+ __le16 cap_info;
+ __le16 ht_cap_info;
+ u8 mac_ht_param_info;
+ u8 mrvl_sta;
+ struct add_ht_info add_ht_info;
+ __le32 tx_bf_capabilities; /* EXBF_SUPPORT */
+ __le32 vht_max_rx_mcs;
+ __le32 vht_cap;
+ /* 0:20Mhz, 1:40Mhz, 2:80Mhz, 3:160 or 80+80Mhz */
+ u8 vht_rx_channel_width;
+} __packed;
+
+struct hostcmd_cmd_set_new_stn {
+ struct hostcmd_header cmd_hdr;
+ __le16 aid;
+ u8 mac_addr[ETH_ALEN];
+ __le16 stn_id;
+ __le16 action;
+ __le16 reserved;
+ struct peer_info peer_info;
+ /* UAPSD_SUPPORT */
+ u8 qos_info;
+ u8 is_qos_sta;
+ __le32 fw_sta_ptr;
+} __packed;
+
+/* HOSTCMD_CMD_SET_APMODE */
+struct hostcmd_cmd_set_apmode {
+ struct hostcmd_header cmd_hdr;
+ u8 apmode;
+} __packed;
+
+/* HOSTCMD_CMD_SET_SWITCH_CHANNEL */
+struct hostcmd_cmd_set_switch_channel {
+ struct hostcmd_header cmd_hdr;
+ __le32 next_11h_chnl;
+ __le32 mode;
+ __le32 init_count;
+ __le32 chnl_flags;
+ __le32 next_ht_extchnl_offset;
+ __le32 dfs_test_mode;
+} __packed;
+
+/* HOSTCMD_CMD_UPDATE_ENCRYPTION */
+struct hostcmd_cmd_update_encryption {
+ struct hostcmd_header cmd_hdr;
+ /* Action type - see encr_action_type */
+ __le32 action_type; /* encr_action_type */
+ /* size of the data buffer attached. */
+ __le32 data_length;
+ u8 mac_addr[ETH_ALEN];
+ u8 action_data[1];
+} __packed;
+
+struct wep_type_key {
+ /* WEP key material (max 128bit) */
+ u8 key_material[MAX_ENCR_KEY_LENGTH];
+} __packed;
+
+struct encr_tkip_seqcnt {
+ __le16 low;
+ __le32 high;
+} __packed;
+
+struct tkip_type_key {
+ /* TKIP Key material. Key type (group or pairwise key) is
+ * determined by flags
+ */
+ /* in KEY_PARAM_SET structure. */
+ u8 key_material[MAX_ENCR_KEY_LENGTH];
+ /* MIC keys */
+ u8 tkip_tx_mic_key[MIC_KEY_LENGTH];
+ u8 tkip_rx_mic_key[MIC_KEY_LENGTH];
+ struct encr_tkip_seqcnt tkip_rsc;
+ struct encr_tkip_seqcnt tkip_tsc;
+} __packed;
+
+struct aes_type_key {
+ /* AES Key material */
+ u8 key_material[MAX_ENCR_KEY_LENGTH];
+} __packed;
+
+union mwl_key_type {
+ struct wep_type_key wep_key;
+ struct tkip_type_key tkip_key;
+ struct aes_type_key aes_key;
+} __packed;
+
+struct key_param_set {
+ /* Total length of this structure (Key is variable size array) */
+ __le16 length;
+ /* Key type - WEP, TKIP or AES-CCMP. */
+ /* See definitions above */
+ __le16 key_type_id;
+ /* key flags (ENCR_KEY_FLAG_XXX_ */
+ __le32 key_info;
+ /* For WEP only - actual key index */
+ __le32 key_index;
+ /* Size of the key */
+ __le16 key_len;
+ /* Key material (variable size array) */
+ union mwl_key_type key;
+ u8 mac_addr[ETH_ALEN];
+} __packed;
+
+struct hostcmd_cmd_set_key {
+ struct hostcmd_header cmd_hdr;
+ /* Action type - see encr_action_type */
+ __le32 action_type; /* encr_action_type */
+ /* size of the data buffer attached. */
+ __le32 data_length;
+ /* data buffer - maps to one KEY_PARAM_SET structure */
+ struct key_param_set key_param;
+} __packed;
+
+/* HOSTCMD_CMD_BASTREAM */
+#define BA_TYPE_MASK 0x00000001
+#define BA_DIRECTION_MASK 0x00000006
+#define BA_DIRECTION_SHIFT 1
+
+struct ba_context {
+ __le32 context;
+} __packed;
+
+/* parameters for block ack creation */
+struct create_ba_params {
+ /* BA Creation flags - see above */
+ __le32 flags;
+ /* idle threshold */
+ __le32 idle_thrs;
+ /* block ack transmit threshold (after how many pkts should we
+ * send BAR?)
+ */
+ __le32 bar_thrs;
+ /* receiver window size */
+ __le32 window_size;
+ /* MAC Address of the BA partner */
+ u8 peer_mac_addr[ETH_ALEN];
+ /* Dialog Token */
+ u8 dialog_token;
+ /* TID for the traffic stream in this BA */
+ u8 tid;
+ /* shared memory queue ID (not sure if this is required) */
+ u8 queue_id;
+ u8 param_info;
+ /* returned by firmware - firmware context pointer. */
+ /* this context pointer will be passed to firmware for all
+ * future commands.
+ */
+ struct ba_context fw_ba_context;
+ u8 reset_seq_no; /** 0 or 1**/
+ __le16 current_seq;
+ /* This is for virtual station in Sta proxy mode for V6FW */
+ u8 sta_src_mac_addr[ETH_ALEN];
+} __packed;
+
+/* new transmit sequence number information */
+struct ba_update_seq_num {
+ /* BA flags - see above */
+ __le32 flags;
+ /* returned by firmware in the create ba stream response */
+ struct ba_context fw_ba_context;
+ /* new sequence number for this block ack stream */
+ __le16 ba_seq_num;
+} __packed;
+
+struct ba_stream_context {
+ /* BA Stream flags */
+ __le32 flags;
+ /* returned by firmware in the create ba stream response */
+ struct ba_context fw_ba_context;
+} __packed;
+
+union ba_info {
+ /* information required to create BA Stream... */
+ struct create_ba_params create_params;
+ /* update starting/new sequence number etc. */
+ struct ba_update_seq_num updt_seq_num;
+ /* destroy an existing stream... */
+ struct ba_stream_context destroy_params;
+ /* destroy an existing stream... */
+ struct ba_stream_context flush_params;
+} __packed;
+
+struct hostcmd_cmd_bastream {
+ struct hostcmd_header cmd_hdr;
+ __le32 action_type;
+ union ba_info ba_info;
+} __packed;
+
+/* HOSTCMD_CMD_SET_SPECTRUM_MGMT */
+struct hostcmd_cmd_set_spectrum_mgmt {
+ struct hostcmd_header cmd_hdr;
+ __le32 spectrum_mgmt;
+} __packed;
+
+/* HOSTCMD_CMD_SET_POWER_CONSTRAINT */
+struct hostcmd_cmd_set_power_constraint {
+ struct hostcmd_header cmd_hdr;
+ __le32 power_constraint;
+} __packed;
+
+/* HOSTCMD_CMD_SET_COUNTRY_CODE */
+struct domain_chnl_entry {
+ u8 first_chnl_num;
+ u8 chnl_num;
+ u8 max_transmit_pw;
+} __packed;
+
+struct domain_country_info {
+ u8 country_string[3];
+ u8 g_chnl_len;
+ struct domain_chnl_entry domain_entry_g[1];
+ u8 a_chnl_len;
+ struct domain_chnl_entry domain_entry_a[20];
+} __packed;
+
+struct hostcmd_cmd_set_country_code {
+ struct hostcmd_header cmd_hdr;
+ __le32 action ; /* 0 -> unset, 1 ->set */
+ struct domain_country_info domain_info;
+} __packed;
+
+/* HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL */
+struct hostcmd_cmd_set_optimization_level {
+ struct hostcmd_header cmd_hdr;
+ u8 opt_level;
+} __packed;
+
+/* HOSTCMD_CMD_SET_WSC_IE */
+struct hostcmd_cmd_set_wsc_ie {
+ struct hostcmd_header cmd_hdr;
+ __le16 ie_type; /* 0 -- beacon. or 1 -- probe response. */
+ __le16 len;
+ u8 data[WSC_IE_MAX_LENGTH];
+} __packed;
+
+/* HOSTCMD_CMD_DWDS_ENABLE */
+struct hostcmd_cmd_dwds_enable {
+ struct hostcmd_header cmd_hdr;
+ __le32 enable; /* 0 -- Disable. or 1 -- Enable. */
+} __packed;
+
+/* HOSTCMD_CMD_FW_FLUSH_TIMER */
+struct hostcmd_cmd_fw_flush_timer {
+ struct hostcmd_header cmd_hdr;
+ /* 0 -- Disable. > 0 -- holds time value in usecs. */
+ __le32 value;
+} __packed;
+
+/* HOSTCMD_CMD_SET_CDD */
+struct hostcmd_cmd_set_cdd {
+ struct hostcmd_header cmd_hdr;
+ __le32 enable;
+} __packed;
+
+/* HOSTCMD_CMD_GET_TEMP */
+struct hostcmd_cmd_get_temp {
+ struct hostcmd_header cmd_hdr;
+ __le32 celcius;
+ __le32 raw_data;
+} __packed;
+
+/* HOSTCMD_CMD_GET_FW_REGION_CODE */
+struct hostcmd_cmd_get_fw_region_code {
+ struct hostcmd_header cmd_hdr;
+ __le32 status; /* 0 = Found, 1 = Error */
+ __le32 fw_region_code;
+} __packed;
+
+/* HOSTCMD_CMD_GET_DEVICE_PWR_TBL */
+#define HAL_TRPC_ID_MAX 16
+
+struct channel_power_tbl {
+ u8 channel;
+ u8 tx_pwr[HAL_TRPC_ID_MAX];
+ u8 dfs_capable;
+ u8 ax_ant;
+ u8 cdd;
+} __packed;
+
+struct hostcmd_cmd_get_device_pwr_tbl {
+ struct hostcmd_header cmd_hdr;
+ __le16 status; /* 0 = Found, 1 = Error */
+ u8 region_code;
+ u8 number_of_channels;
+ __le32 current_channel_index;
+ /* Only for 1 channel, so, 1 channel at a time */
+ struct channel_power_tbl channel_pwr_tbl;
+} __packed;
+
+/* HOSTCMD_CMD_QUIET_MODE */
+struct hostcmd_cmd_quiet_mode {
+ struct hostcmd_header cmd_hdr;
+ __le16 action;
+ __le32 enable;
+ __le32 period;
+ __le32 duration;
+ __le32 next_offset;
+} __packed;
+
+#endif /* _HOSTCMD_H_ */
diff --git a/drivers/net/wireless/marvell/mwlwifi/isr.c b/drivers/net/wireless/marvell/mwlwifi/isr.c
new file mode 100644
index 0000000..881cea8
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/isr.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file implements interrupt related functions. */
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "fwcmd.h"
+#include "isr.h"
+
+#define INVALID_WATCHDOG 0xAA
+
+irqreturn_t mwl_isr(int irq, void *dev_id)
+{
+ struct ieee80211_hw *hw = dev_id;
+ struct mwl_priv *priv = hw->priv;
+ void __iomem *int_status_mask;
+ u32 int_status;
+ u32 status;
+
+ int_status_mask = priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK;
+
+ int_status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE);
+
+ if (int_status == 0x00000000)
+ return IRQ_NONE;
+
+ if (int_status == 0xffffffff) {
+ wiphy_warn(hw->wiphy, "card unplugged?\n");
+ } else {
+ writel(~int_status,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE);
+
+ if (int_status & MACREG_A2HRIC_BIT_TX_DONE) {
+ if (!priv->is_tx_done_schedule) {
+ status = readl(int_status_mask);
+ writel((status & ~MACREG_A2HRIC_BIT_TX_DONE),
+ int_status_mask);
+ tasklet_schedule(&priv->tx_done_task);
+ priv->is_tx_done_schedule = true;
+ }
+ }
+
+ if (int_status & MACREG_A2HRIC_BIT_RX_RDY) {
+ if (!priv->is_rx_schedule) {
+ status = readl(int_status_mask);
+ writel((status & ~MACREG_A2HRIC_BIT_RX_RDY),
+ int_status_mask);
+ tasklet_schedule(&priv->rx_task);
+ priv->is_rx_schedule = true;
+ }
+ }
+
+ if (int_status & MACREG_A2HRIC_BIT_RADAR_DETECT) {
+ wiphy_info(hw->wiphy, "radar detected by firmware\n");
+ ieee80211_radar_detected(hw);
+ }
+
+ if (int_status & MACREG_A2HRIC_BIT_QUE_EMPTY) {
+ if (!priv->is_qe_schedule) {
+ if (time_after(jiffies,
+ (priv->qe_trigger_time + 1))) {
+ status = readl(int_status_mask);
+ writel((status &
+ ~MACREG_A2HRIC_BIT_QUE_EMPTY),
+ int_status_mask);
+ tasklet_schedule(&priv->qe_task);
+ priv->qe_trigger_num++;
+ priv->is_qe_schedule = true;
+ priv->qe_trigger_time = jiffies;
+ }
+ }
+ }
+
+ if (int_status & MACREG_A2HRIC_BIT_CHAN_SWITCH)
+ ieee80211_queue_work(hw, &priv->chnl_switch_handle);
+
+ if (int_status & MACREG_A2HRIC_BA_WATCHDOG)
+ ieee80211_queue_work(hw, &priv->watchdog_ba_handle);
+ }
+
+ return IRQ_HANDLED;
+}
+
+void mwl_chnl_switch_event(struct work_struct *work)
+{
+ struct mwl_priv *priv =
+ container_of(work, struct mwl_priv, chnl_switch_handle);
+ struct mwl_vif *mwl_vif;
+ struct ieee80211_vif *vif;
+
+ if (!priv->csa_active) {
+ wiphy_err(priv->hw->wiphy,
+ "csa is not active (got channel switch event)\n");
+ return;
+ }
+
+ spin_lock_bh(&priv->vif_lock);
+ list_for_each_entry(mwl_vif, &priv->vif_list, list) {
+ vif = container_of((char *)mwl_vif, struct ieee80211_vif,
+ drv_priv[0]);
+
+ if (vif->csa_active)
+ ieee80211_csa_finish(vif);
+ }
+ spin_unlock_bh(&priv->vif_lock);
+
+ wiphy_info(priv->hw->wiphy, "channel switch is done\n");
+
+ priv->csa_active = false;
+}
+
+void mwl_watchdog_ba_events(struct work_struct *work)
+{
+ int rc;
+ u8 bitmap = 0, stream_index;
+ struct mwl_ampdu_stream *streams;
+ struct mwl_priv *priv =
+ container_of(work, struct mwl_priv, watchdog_ba_handle);
+
+ rc = mwl_fwcmd_get_watchdog_bitmap(priv->hw, &bitmap);
+
+ if (rc)
+ return;
+
+ spin_lock_bh(&priv->stream_lock);
+
+ /* the bitmap is the hw queue number. Map it to the ampdu queue. */
+ if (bitmap != INVALID_WATCHDOG) {
+ if (bitmap == SYSADPT_TX_AMPDU_QUEUES)
+ stream_index = 0;
+ else if (bitmap > SYSADPT_TX_AMPDU_QUEUES)
+ stream_index = bitmap - SYSADPT_TX_AMPDU_QUEUES;
+ else
+ stream_index = bitmap + 3; /** queue 0 is stream 3*/
+
+ if (bitmap != 0xFF) {
+ /* Check if the stream is in use before disabling it */
+ streams = &priv->ampdu[stream_index];
+
+ if (streams->state == AMPDU_STREAM_ACTIVE)
+ ieee80211_stop_tx_ba_session(streams->sta,
+ streams->tid);
+ } else {
+ for (stream_index = 0;
+ stream_index < SYSADPT_TX_AMPDU_QUEUES;
+ stream_index++) {
+ streams = &priv->ampdu[stream_index];
+
+ if (streams->state != AMPDU_STREAM_ACTIVE)
+ continue;
+
+ ieee80211_stop_tx_ba_session(streams->sta,
+ streams->tid);
+ }
+ }
+ }
+
+ spin_unlock_bh(&priv->stream_lock);
+}
diff --git a/drivers/net/wireless/marvell/mwlwifi/isr.h b/drivers/net/wireless/marvell/mwlwifi/isr.h
new file mode 100644
index 0000000..adcc67f
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/isr.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines interrupt related functions. */
+
+#ifndef _ISR_H_
+#define _ISR_H_
+
+#include <linux/interrupt.h>
+
+irqreturn_t mwl_isr(int irq, void *dev_id);
+void mwl_chnl_switch_event(struct work_struct *work);
+void mwl_watchdog_ba_events(struct work_struct *work);
+
+#endif /* _ISR_H_ */
diff --git a/drivers/net/wireless/marvell/mwlwifi/mac80211.c b/drivers/net/wireless/marvell/mwlwifi/mac80211.c
new file mode 100644
index 0000000..a600eda
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/mac80211.c
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file implements mac80211 related functions. */
+
+#include <linux/etherdevice.h>
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "fwcmd.h"
+#include "tx.h"
+
+#define MWL_DRV_NAME KBUILD_MODNAME
+
+#define MAX_AMPDU_ATTEMPTS 5
+
+static const struct ieee80211_rate mwl_rates_24[] = {
+ { .bitrate = 10, .hw_value = 2, },
+ { .bitrate = 20, .hw_value = 4, },
+ { .bitrate = 55, .hw_value = 11, },
+ { .bitrate = 110, .hw_value = 22, },
+ { .bitrate = 220, .hw_value = 44, },
+ { .bitrate = 60, .hw_value = 12, },
+ { .bitrate = 90, .hw_value = 18, },
+ { .bitrate = 120, .hw_value = 24, },
+ { .bitrate = 180, .hw_value = 36, },
+ { .bitrate = 240, .hw_value = 48, },
+ { .bitrate = 360, .hw_value = 72, },
+ { .bitrate = 480, .hw_value = 96, },
+ { .bitrate = 540, .hw_value = 108, },
+};
+
+static const struct ieee80211_rate mwl_rates_50[] = {
+ { .bitrate = 60, .hw_value = 12, },
+ { .bitrate = 90, .hw_value = 18, },
+ { .bitrate = 120, .hw_value = 24, },
+ { .bitrate = 180, .hw_value = 36, },
+ { .bitrate = 240, .hw_value = 48, },
+ { .bitrate = 360, .hw_value = 72, },
+ { .bitrate = 480, .hw_value = 96, },
+ { .bitrate = 540, .hw_value = 108, },
+};
+
+static void mwl_mac80211_tx(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ if (!priv->radio_on) {
+ wiphy_warn(hw->wiphy,
+ "dropped TX frame since radio is disabled\n");
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ mwl_tx_xmit(hw, control, skb);
+}
+
+static int mwl_mac80211_start(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+ int rc;
+
+ /* Enable TX and RX tasklets. */
+ tasklet_enable(&priv->tx_task);
+ tasklet_enable(&priv->tx_done_task);
+ tasklet_enable(&priv->rx_task);
+ tasklet_enable(&priv->qe_task);
+
+ /* Enable interrupts */
+ mwl_fwcmd_int_enable(hw);
+
+ rc = mwl_fwcmd_radio_enable(hw);
+ if (rc)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_set_rate_adapt_mode(hw, 0);
+ if (rc)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_set_wmm_mode(hw, true);
+ if (rc)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_ht_guard_interval(hw, GUARD_INTERVAL_AUTO);
+ if (rc)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_set_dwds_stamode(hw, true);
+ if (rc)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_set_fw_flush_timer(hw, SYSADPT_AMSDU_FLUSH_TIME);
+ if (rc)
+ goto fwcmd_fail;
+ rc = mwl_fwcmd_set_optimization_level(hw, 1);
+ if (rc)
+ goto fwcmd_fail;
+
+ ieee80211_wake_queues(hw);
+ return 0;
+
+fwcmd_fail:
+ mwl_fwcmd_int_disable(hw);
+ tasklet_disable(&priv->tx_task);
+ tasklet_disable(&priv->tx_done_task);
+ tasklet_disable(&priv->rx_task);
+ tasklet_disable(&priv->qe_task);
+
+ return rc;
+}
+
+static void mwl_mac80211_stop(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ mwl_fwcmd_radio_disable(hw);
+
+ ieee80211_stop_queues(hw);
+
+ /* Disable interrupts */
+ mwl_fwcmd_int_disable(hw);
+
+ /* Disable TX reclaim and RX tasklets. */
+ tasklet_disable(&priv->tx_task);
+ tasklet_disable(&priv->tx_done_task);
+ tasklet_disable(&priv->rx_task);
+ tasklet_disable(&priv->qe_task);
+
+ /* Return all skbs to mac80211 */
+ mwl_tx_done((unsigned long)hw);
+}
+
+static int mwl_mac80211_add_interface(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ u32 macids_supported;
+ int macid;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_AP:
+ macids_supported = priv->ap_macids_supported;
+ break;
+ case NL80211_IFTYPE_STATION:
+ macids_supported = priv->sta_macids_supported;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ macid = ffs(macids_supported & ~priv->macids_used);
+
+ if (!macid) {
+ wiphy_warn(hw->wiphy, "no macid can be allocated\n");
+ return -EBUSY;
+ }
+ macid--;
+
+ /* Setup driver private area. */
+ mwl_vif = mwl_dev_get_vif(vif);
+ memset(mwl_vif, 0, sizeof(*mwl_vif));
+ mwl_vif->macid = macid;
+ mwl_vif->seqno = 0;
+ mwl_vif->is_hw_crypto_enabled = false;
+ mwl_vif->beacon_info.valid = false;
+ mwl_vif->iv16 = 1;
+ mwl_vif->iv32 = 0;
+ mwl_vif->keyidx = 0;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_AP:
+ ether_addr_copy(mwl_vif->bssid, vif->addr);
+ mwl_fwcmd_set_new_stn_add_self(hw, vif);
+ break;
+ case NL80211_IFTYPE_STATION:
+ ether_addr_copy(mwl_vif->sta_mac, vif->addr);
+ mwl_fwcmd_bss_start(hw, vif, true);
+ mwl_fwcmd_set_infra_mode(hw, vif);
+ mwl_fwcmd_set_mac_addr_client(hw, vif, vif->addr);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ priv->macids_used |= 1 << mwl_vif->macid;
+ spin_lock_bh(&priv->vif_lock);
+ list_add_tail(&mwl_vif->list, &priv->vif_list);
+ spin_unlock_bh(&priv->vif_lock);
+
+ return 0;
+}
+
+static void mwl_mac80211_remove_vif(struct mwl_priv *priv,
+ struct ieee80211_vif *vif)
+{
+ struct mwl_vif *mwl_vif = mwl_dev_get_vif(vif);
+
+ if (!priv->macids_used)
+ return;
+
+ mwl_tx_del_pkts_via_vif(priv->hw, vif);
+
+ priv->macids_used &= ~(1 << mwl_vif->macid);
+ spin_lock_bh(&priv->vif_lock);
+ list_del(&mwl_vif->list);
+ spin_unlock_bh(&priv->vif_lock);
+}
+
+static void mwl_mac80211_remove_interface(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_AP:
+ mwl_fwcmd_set_new_stn_del(hw, vif, vif->addr);
+ break;
+ case NL80211_IFTYPE_STATION:
+ mwl_fwcmd_remove_mac_addr(hw, vif, vif->addr);
+ break;
+ default:
+ break;
+ }
+
+ mwl_mac80211_remove_vif(priv, vif);
+}
+
+static int mwl_mac80211_config(struct ieee80211_hw *hw,
+ u32 changed)
+{
+ struct ieee80211_conf *conf = &hw->conf;
+ int rc;
+
+ wiphy_debug(hw->wiphy, "change: 0x%x\n", changed);
+
+ if (conf->flags & IEEE80211_CONF_IDLE)
+ rc = mwl_fwcmd_radio_disable(hw);
+ else
+ rc = mwl_fwcmd_radio_enable(hw);
+
+ if (rc)
+ goto out;
+
+ if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
+ int rate = 0;
+
+ if (conf->chandef.chan->band == NL80211_BAND_2GHZ) {
+ mwl_fwcmd_set_apmode(hw, AP_MODE_2_4GHZ_11AC_MIXED);
+ mwl_fwcmd_set_linkadapt_cs_mode(hw,
+ LINK_CS_STATE_CONSERV);
+ rate = mwl_rates_24[0].hw_value;
+ } else if (conf->chandef.chan->band == NL80211_BAND_5GHZ) {
+ mwl_fwcmd_set_apmode(hw, AP_MODE_11AC);
+ mwl_fwcmd_set_linkadapt_cs_mode(hw,
+ LINK_CS_STATE_AUTO);
+ rate = mwl_rates_50[0].hw_value;
+
+ if (conf->radar_enabled)
+ mwl_fwcmd_set_radar_detect(hw, MONITOR_START);
+ else
+ mwl_fwcmd_set_radar_detect(hw,
+ STOP_DETECT_RADAR);
+ }
+
+ rc = mwl_fwcmd_set_rf_channel(hw, conf);
+ if (rc)
+ goto out;
+ rc = mwl_fwcmd_use_fixed_rate(hw, rate, rate);
+ if (rc)
+ goto out;
+ rc = mwl_fwcmd_max_tx_power(hw, conf, 0);
+ if (rc)
+ goto out;
+ rc = mwl_fwcmd_tx_power(hw, conf, 0);
+ if (rc)
+ goto out;
+ rc = mwl_fwcmd_set_cdd(hw);
+ }
+
+out:
+
+ return rc;
+}
+
+static void mwl_mac80211_bss_info_changed_sta(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ if (changed & BSS_CHANGED_ERP_PREAMBLE)
+ mwl_fwcmd_set_radio_preamble(hw,
+ vif->bss_conf.use_short_preamble);
+
+ if ((changed & BSS_CHANGED_ASSOC) && vif->bss_conf.assoc)
+ mwl_fwcmd_set_aid(hw, vif, (u8 *)vif->bss_conf.bssid,
+ vif->bss_conf.aid);
+}
+
+static void mwl_mac80211_bss_info_changed_ap(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ if (changed & BSS_CHANGED_ERP_PREAMBLE)
+ mwl_fwcmd_set_radio_preamble(hw,
+ vif->bss_conf.use_short_preamble);
+
+ if (changed & BSS_CHANGED_BASIC_RATES) {
+ int idx;
+ int rate;
+
+ /* Use lowest supported basic rate for multicasts
+ * and management frames (such as probe responses --
+ * beacons will always go out at 1 Mb/s).
+ */
+ idx = ffs(vif->bss_conf.basic_rates);
+ if (idx)
+ idx--;
+
+ if (hw->conf.chandef.chan->band == NL80211_BAND_2GHZ)
+ rate = mwl_rates_24[idx].hw_value;
+ else
+ rate = mwl_rates_50[idx].hw_value;
+
+ mwl_fwcmd_use_fixed_rate(hw, rate, rate);
+ }
+
+ if (changed & (BSS_CHANGED_BEACON_INT | BSS_CHANGED_BEACON)) {
+ struct sk_buff *skb;
+
+ if ((info->ssid[0] != '\0') &&
+ (info->ssid_len != 0) &&
+ (!info->hidden_ssid))
+ mwl_fwcmd_broadcast_ssid_enable(hw, vif, true);
+ else
+ mwl_fwcmd_broadcast_ssid_enable(hw, vif, false);
+
+ skb = ieee80211_beacon_get(hw, vif);
+
+ if (skb) {
+ mwl_fwcmd_set_beacon(hw, vif, skb->data, skb->len);
+ dev_kfree_skb_any(skb);
+ }
+ }
+
+ if (changed & BSS_CHANGED_BEACON_ENABLED)
+ mwl_fwcmd_bss_start(hw, vif, info->enable_beacon);
+}
+
+static void mwl_mac80211_bss_info_changed(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ switch (vif->type) {
+ case NL80211_IFTYPE_AP:
+ mwl_mac80211_bss_info_changed_ap(hw, vif, info, changed);
+ break;
+ case NL80211_IFTYPE_STATION:
+ mwl_mac80211_bss_info_changed_sta(hw, vif, info, changed);
+ break;
+ default:
+ break;
+ }
+}
+
+static void mwl_mac80211_configure_filter(struct ieee80211_hw *hw,
+ unsigned int changed_flags,
+ unsigned int *total_flags,
+ u64 multicast)
+{
+ /* AP firmware doesn't allow fine-grained control over
+ * the receive filter.
+ */
+ *total_flags &= FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC;
+}
+
+static int mwl_mac80211_set_key(struct ieee80211_hw *hw,
+ enum set_key_cmd cmd_param,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key)
+{
+ struct mwl_vif *mwl_vif;
+ int rc = 0;
+ u8 encr_type;
+ u8 *addr;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+
+ if (!sta) {
+ addr = vif->addr;
+ } else {
+ addr = sta->addr;
+ if (vif->type == NL80211_IFTYPE_STATION)
+ ether_addr_copy(mwl_vif->bssid, addr);
+ }
+
+ if (cmd_param == SET_KEY) {
+ rc = mwl_fwcmd_encryption_set_key(hw, vif, addr, key);
+
+ if (rc)
+ goto out;
+
+ if ((key->cipher == WLAN_CIPHER_SUITE_WEP40) ||
+ (key->cipher == WLAN_CIPHER_SUITE_WEP104)) {
+ encr_type = ENCR_TYPE_WEP;
+ } else if (key->cipher == WLAN_CIPHER_SUITE_CCMP) {
+ encr_type = ENCR_TYPE_AES;
+ if ((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) == 0) {
+ if (vif->type != NL80211_IFTYPE_STATION)
+ mwl_vif->keyidx = key->keyidx;
+ }
+ } else if (key->cipher == WLAN_CIPHER_SUITE_TKIP) {
+ encr_type = ENCR_TYPE_TKIP;
+ } else {
+ encr_type = ENCR_TYPE_DISABLE;
+ }
+
+ rc = mwl_fwcmd_update_encryption_enable(hw, vif, addr,
+ encr_type);
+ if (rc)
+ goto out;
+
+ mwl_vif->is_hw_crypto_enabled = true;
+ } else {
+ rc = mwl_fwcmd_encryption_remove_key(hw, vif, addr, key);
+ if (rc)
+ goto out;
+ }
+
+out:
+
+ return rc;
+}
+
+static int mwl_mac80211_set_rts_threshold(struct ieee80211_hw *hw,
+ u32 value)
+{
+ return mwl_fwcmd_set_rts_threshold(hw, value);
+}
+
+static int mwl_mac80211_sta_add(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_vif *mwl_vif;
+ struct mwl_sta *sta_info;
+ struct ieee80211_key_conf *key;
+ int rc;
+ int i;
+
+ mwl_vif = mwl_dev_get_vif(vif);
+ sta_info = mwl_dev_get_sta(sta);
+
+ memset(sta_info, 0, sizeof(*sta_info));
+
+ if (sta->ht_cap.ht_supported) {
+ sta_info->is_ampdu_allowed = true;
+ sta_info->is_amsdu_allowed = false;
+ if (sta->ht_cap.cap & IEEE80211_HT_CAP_MAX_AMSDU)
+ sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_8K;
+ else
+ sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_4K;
+ if ((sta->tdls) && (!sta->wme))
+ sta->wme = true;
+ }
+ sta_info->iv16 = 1;
+ sta_info->iv32 = 0;
+ spin_lock_init(&sta_info->amsdu_lock);
+ spin_lock_bh(&priv->sta_lock);
+ list_add_tail(&sta_info->list, &priv->sta_list);
+ spin_unlock_bh(&priv->sta_lock);
+
+ if (vif->type == NL80211_IFTYPE_STATION)
+ mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr);
+
+ rc = mwl_fwcmd_set_new_stn_add(hw, vif, sta);
+
+ for (i = 0; i < NUM_WEP_KEYS; i++) {
+ key = (struct ieee80211_key_conf *)mwl_vif->wep_key_conf[i].key;
+
+ if (mwl_vif->wep_key_conf[i].enabled)
+ mwl_mac80211_set_key(hw, SET_KEY, vif, sta, key);
+ }
+
+ return rc;
+}
+
+static int mwl_mac80211_sta_remove(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct mwl_priv *priv = hw->priv;
+ int rc;
+ struct mwl_sta *sta_info = mwl_dev_get_sta(sta);
+
+ mwl_tx_del_sta_amsdu_pkts(sta);
+
+ spin_lock_bh(&priv->stream_lock);
+ mwl_fwcmd_del_sta_streams(hw, sta);
+ spin_unlock_bh(&priv->stream_lock);
+
+ mwl_tx_del_pkts_via_sta(hw, sta);
+
+ rc = mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr);
+
+ spin_lock_bh(&priv->sta_lock);
+ list_del(&sta_info->list);
+ spin_unlock_bh(&priv->sta_lock);
+
+ return rc;
+}
+
+static int mwl_mac80211_conf_tx(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ u16 queue,
+ const struct ieee80211_tx_queue_params *params)
+{
+ struct mwl_priv *priv = hw->priv;
+ int rc = 0;
+
+ if (WARN_ON(queue > SYSADPT_TX_WMM_QUEUES - 1))
+ return -EINVAL;
+
+ memcpy(&priv->wmm_params[queue], params, sizeof(*params));
+
+ if (!priv->wmm_enabled) {
+ rc = mwl_fwcmd_set_wmm_mode(hw, true);
+ priv->wmm_enabled = true;
+ }
+
+ if (!rc) {
+ int q = SYSADPT_TX_WMM_QUEUES - 1 - queue;
+
+ rc = mwl_fwcmd_set_edca_params(hw, q,
+ params->cw_min, params->cw_max,
+ params->aifs, params->txop);
+ }
+
+ return rc;
+}
+
+static int mwl_mac80211_get_stats(struct ieee80211_hw *hw,
+ struct ieee80211_low_level_stats *stats)
+{
+ return mwl_fwcmd_get_stat(hw, stats);
+}
+
+static int mwl_mac80211_get_survey(struct ieee80211_hw *hw,
+ int idx,
+ struct survey_info *survey)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct ieee80211_conf *conf = &hw->conf;
+
+ if (idx != 0)
+ return -ENOENT;
+
+ survey->channel = conf->chandef.chan;
+ survey->filled = SURVEY_INFO_NOISE_DBM;
+ survey->noise = priv->noise;
+
+ return 0;
+}
+
+static int mwl_mac80211_ampdu_action(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_ampdu_params *params)
+{
+ int rc = 0;
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_ampdu_stream *stream;
+ enum ieee80211_ampdu_mlme_action action = params->action;
+ struct ieee80211_sta *sta = params->sta;
+ u16 tid = params->tid;
+ u8 buf_size = params->buf_size;
+ u8 *addr = sta->addr, idx;
+ struct mwl_sta *sta_info;
+
+ sta_info = mwl_dev_get_sta(sta);
+
+ spin_lock_bh(&priv->stream_lock);
+
+ stream = mwl_fwcmd_lookup_stream(hw, addr, tid);
+
+ switch (action) {
+ case IEEE80211_AMPDU_RX_START:
+ case IEEE80211_AMPDU_RX_STOP:
+ break;
+ case IEEE80211_AMPDU_TX_START:
+ if (!sta_info->is_ampdu_allowed) {
+ wiphy_warn(hw->wiphy, "ampdu not allowed\n");
+ rc = -EPERM;
+ break;
+ }
+
+ if (!stream) {
+ stream = mwl_fwcmd_add_stream(hw, sta, tid);
+ if (!stream) {
+ wiphy_warn(hw->wiphy, "no stream found\n");
+ rc = -EPERM;
+ break;
+ }
+ }
+
+ spin_unlock_bh(&priv->stream_lock);
+ rc = mwl_fwcmd_check_ba(hw, stream, vif);
+ spin_lock_bh(&priv->stream_lock);
+ if (rc) {
+ mwl_fwcmd_remove_stream(hw, stream);
+ sta_info->check_ba_failed[tid]++;
+ rc = -EPERM;
+ break;
+ }
+ stream->state = AMPDU_STREAM_IN_PROGRESS;
+ params->ssn = 0;
+ ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid);
+ break;
+ case IEEE80211_AMPDU_TX_STOP_CONT:
+ case IEEE80211_AMPDU_TX_STOP_FLUSH:
+ case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
+ if (stream) {
+ if (stream->state == AMPDU_STREAM_ACTIVE) {
+ mwl_tx_del_ampdu_pkts(hw, sta, tid);
+ idx = stream->idx;
+ spin_unlock_bh(&priv->stream_lock);
+ mwl_fwcmd_destroy_ba(hw, idx);
+ spin_lock_bh(&priv->stream_lock);
+ sta_info->is_amsdu_allowed = false;
+ }
+
+ mwl_fwcmd_remove_stream(hw, stream);
+ ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid);
+ } else {
+ rc = -EPERM;
+ }
+ break;
+ case IEEE80211_AMPDU_TX_OPERATIONAL:
+ if (stream) {
+ if (WARN_ON(stream->state !=
+ AMPDU_STREAM_IN_PROGRESS)) {
+ rc = -EPERM;
+ break;
+ }
+ spin_unlock_bh(&priv->stream_lock);
+ rc = mwl_fwcmd_create_ba(hw, stream, buf_size, vif);
+ spin_lock_bh(&priv->stream_lock);
+
+ if (!rc) {
+ stream->state = AMPDU_STREAM_ACTIVE;
+ sta_info->check_ba_failed[tid] = 0;
+ sta_info->is_amsdu_allowed = params->amsdu;
+ } else {
+ idx = stream->idx;
+ spin_unlock_bh(&priv->stream_lock);
+ mwl_fwcmd_destroy_ba(hw, idx);
+ spin_lock_bh(&priv->stream_lock);
+ mwl_fwcmd_remove_stream(hw, stream);
+ wiphy_err(hw->wiphy,
+ "ampdu operation error code: %d\n",
+ rc);
+ }
+ } else {
+ rc = -EPERM;
+ }
+ break;
+ default:
+ rc = -ENOTSUPP;
+ break;
+ }
+
+ spin_unlock_bh(&priv->stream_lock);
+
+ return rc;
+}
+
+static int mwl_mac80211_chnl_switch(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_channel_switch *ch_switch)
+{
+ struct mwl_priv *priv = hw->priv;
+ int rc = 0;
+
+ rc = mwl_fwcmd_set_switch_channel(priv, ch_switch);
+
+ return rc;
+}
+
+const struct ieee80211_ops mwl_mac80211_ops = {
+ .tx = mwl_mac80211_tx,
+ .start = mwl_mac80211_start,
+ .stop = mwl_mac80211_stop,
+ .add_interface = mwl_mac80211_add_interface,
+ .remove_interface = mwl_mac80211_remove_interface,
+ .config = mwl_mac80211_config,
+ .bss_info_changed = mwl_mac80211_bss_info_changed,
+ .configure_filter = mwl_mac80211_configure_filter,
+ .set_key = mwl_mac80211_set_key,
+ .set_rts_threshold = mwl_mac80211_set_rts_threshold,
+ .sta_add = mwl_mac80211_sta_add,
+ .sta_remove = mwl_mac80211_sta_remove,
+ .conf_tx = mwl_mac80211_conf_tx,
+ .get_stats = mwl_mac80211_get_stats,
+ .get_survey = mwl_mac80211_get_survey,
+ .ampdu_action = mwl_mac80211_ampdu_action,
+ .pre_channel_switch = mwl_mac80211_chnl_switch,
+};
diff --git a/drivers/net/wireless/marvell/mwlwifi/main.c b/drivers/net/wireless/marvell/mwlwifi/main.c
new file mode 100644
index 0000000..5be867d
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/main.c
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file implements main functions of this module. */
+
+#include <linux/module.h>
+#ifdef CONFIG_OF
+#include <linux/of.h>
+#endif
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "fwdl.h"
+#include "fwcmd.h"
+#include "tx.h"
+#include "rx.h"
+#include "isr.h"
+#include "thermal.h"
+#ifdef CONFIG_DEBUG_FS
+#include "debugfs.h"
+#endif
+
+#define MWL_DESC "Marvell 802.11ac Wireless Network Driver"
+#define MWL_DEV_NAME "Marvell 802.11ac Adapter"
+
+#define FILE_PATH_LEN 64
+#define CMD_BUF_SIZE 0x4000
+
+static struct pci_device_id mwl_pci_id_tbl[] = {
+ { PCI_VDEVICE(MARVELL, 0x2a55), .driver_data = MWL8864, },
+ { PCI_VDEVICE(MARVELL, 0x2b38), .driver_data = MWL8897, },
+ { },
+};
+
+static struct mwl_chip_info mwl_chip_tbl[] = {
+ [MWL8864] = {
+ .part_name = "88W8864",
+ .fw_image = "mwlwifi/88W8864.bin",
+ .antenna_tx = ANTENNA_TX_4_AUTO,
+ .antenna_rx = ANTENNA_RX_4_AUTO,
+ },
+ [MWL8897] = {
+ .part_name = "88W8897",
+ .fw_image = "mwlwifi/88W8897.bin",
+ .antenna_tx = ANTENNA_TX_2,
+ .antenna_rx = ANTENNA_RX_2,
+ },
+};
+
+static const struct ieee80211_channel mwl_channels_24[] = {
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2412, .hw_value = 1, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2417, .hw_value = 2, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2422, .hw_value = 3, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2427, .hw_value = 4, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2432, .hw_value = 5, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2437, .hw_value = 6, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2442, .hw_value = 7, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2447, .hw_value = 8, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2452, .hw_value = 9, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2457, .hw_value = 10, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2462, .hw_value = 11, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2467, .hw_value = 12, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2472, .hw_value = 13, },
+ { .band = NL80211_BAND_2GHZ, .center_freq = 2484, .hw_value = 14, },
+};
+
+static const struct ieee80211_rate mwl_rates_24[] = {
+ { .bitrate = 10, .hw_value = 2, },
+ { .bitrate = 20, .hw_value = 4, },
+ { .bitrate = 55, .hw_value = 11, },
+ { .bitrate = 110, .hw_value = 22, },
+ { .bitrate = 220, .hw_value = 44, },
+ { .bitrate = 60, .hw_value = 12, },
+ { .bitrate = 90, .hw_value = 18, },
+ { .bitrate = 120, .hw_value = 24, },
+ { .bitrate = 180, .hw_value = 36, },
+ { .bitrate = 240, .hw_value = 48, },
+ { .bitrate = 360, .hw_value = 72, },
+ { .bitrate = 480, .hw_value = 96, },
+ { .bitrate = 540, .hw_value = 108, },
+};
+
+static const struct ieee80211_channel mwl_channels_50[] = {
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5180, .hw_value = 36, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5200, .hw_value = 40, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5220, .hw_value = 44, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5240, .hw_value = 48, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5260, .hw_value = 52, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5280, .hw_value = 56, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5300, .hw_value = 60, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5320, .hw_value = 64, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5500, .hw_value = 100, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5520, .hw_value = 104, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5540, .hw_value = 108, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5560, .hw_value = 112, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5580, .hw_value = 116, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5600, .hw_value = 120, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5620, .hw_value = 124, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5640, .hw_value = 128, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5660, .hw_value = 132, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5680, .hw_value = 136, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5700, .hw_value = 140, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5720, .hw_value = 144, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5745, .hw_value = 149, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5765, .hw_value = 153, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5785, .hw_value = 157, },
+ { .band = NL80211_BAND_5GHZ, .center_freq = 5805, .hw_value = 161, },
+};
+
+static const struct ieee80211_rate mwl_rates_50[] = {
+ { .bitrate = 60, .hw_value = 12, },
+ { .bitrate = 90, .hw_value = 18, },
+ { .bitrate = 120, .hw_value = 24, },
+ { .bitrate = 180, .hw_value = 36, },
+ { .bitrate = 240, .hw_value = 48, },
+ { .bitrate = 360, .hw_value = 72, },
+ { .bitrate = 480, .hw_value = 96, },
+ { .bitrate = 540, .hw_value = 108, },
+};
+
+static const struct ieee80211_iface_limit ap_if_limits[] = {
+ { .max = SYSADPT_NUM_OF_AP, .types = BIT(NL80211_IFTYPE_AP) },
+ { .max = 1, .types = BIT(NL80211_IFTYPE_STATION) },
+};
+
+static const struct ieee80211_iface_combination ap_if_comb = {
+ .limits = ap_if_limits,
+ .n_limits = ARRAY_SIZE(ap_if_limits),
+ .max_interfaces = SYSADPT_NUM_OF_AP,
+ .num_different_channels = 1,
+ .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) |
+ BIT(NL80211_CHAN_WIDTH_20) |
+ BIT(NL80211_CHAN_WIDTH_40) |
+ BIT(NL80211_CHAN_WIDTH_80),
+};
+
+struct region_code_mapping {
+ const char *alpha2;
+ u32 region_code;
+};
+
+static const struct region_code_mapping regmap[] = {
+ {"US", 0x10}, /* US FCC */
+ {"CA", 0x20}, /* Canada */
+ {"EU", 0x30}, /* ETSI */
+ {"ES", 0x31}, /* Spain */
+ {"FR", 0x32}, /* France */
+ {"JP", 0x40}, /* Japan */
+ {"TW", 0x80}, /* Taiwan */
+ {"AU", 0x81}, /* Australia */
+ {"CN", 0x90}, /* China (Asia) */
+};
+
+static int mwl_alloc_pci_resource(struct mwl_priv *priv)
+{
+ struct pci_dev *pdev = priv->pdev;
+ void __iomem *addr;
+
+ priv->next_bar_num = 1; /* 32-bit */
+ if (pci_resource_flags(pdev, 0) & 0x04)
+ priv->next_bar_num = 2; /* 64-bit */
+
+ addr = devm_ioremap_resource(priv->dev, &pdev->resource[0]);
+ if (IS_ERR(addr)) {
+ wiphy_err(priv->hw->wiphy,
+ "%s: cannot reserve PCI memory region 0\n",
+ MWL_DRV_NAME);
+ goto err;
+ }
+ priv->iobase0 = addr;
+ wiphy_debug(priv->hw->wiphy, "priv->iobase0 = %p\n", priv->iobase0);
+
+ addr = devm_ioremap_resource(priv->dev,
+ &pdev->resource[priv->next_bar_num]);
+ if (IS_ERR(addr)) {
+ wiphy_err(priv->hw->wiphy,
+ "%s: cannot reserve PCI memory region 1\n",
+ MWL_DRV_NAME);
+ goto err;
+ }
+ priv->iobase1 = addr;
+ wiphy_debug(priv->hw->wiphy, "priv->iobase1 = %p\n", priv->iobase1);
+
+ priv->pcmd_buf =
+ (unsigned short *)dmam_alloc_coherent(priv->dev,
+ CMD_BUF_SIZE,
+ &priv->pphys_cmd_buf,
+ GFP_KERNEL);
+ if (!priv->pcmd_buf) {
+ wiphy_err(priv->hw->wiphy,
+ "%s: cannot alloc memory for command buffer\n",
+ MWL_DRV_NAME);
+ goto err;
+ }
+ wiphy_debug(priv->hw->wiphy,
+ "priv->pcmd_buf = %p priv->pphys_cmd_buf = %p\n",
+ priv->pcmd_buf,
+ (void *)priv->pphys_cmd_buf);
+ memset(priv->pcmd_buf, 0x00, CMD_BUF_SIZE);
+
+ return 0;
+
+err:
+ wiphy_err(priv->hw->wiphy, "pci alloc fail\n");
+
+ return -EIO;
+}
+
+static int mwl_init_firmware(struct mwl_priv *priv, const char *fw_name)
+{
+ int rc = 0;
+
+ rc = request_firmware((const struct firmware **)&priv->fw_ucode,
+ fw_name, priv->dev);
+
+ if (rc) {
+ wiphy_err(priv->hw->wiphy,
+ "%s: cannot find firmware image <%s>\n",
+ MWL_DRV_NAME, fw_name);
+ goto err_load_fw;
+ }
+
+ rc = mwl_fwdl_download_firmware(priv->hw);
+ if (rc) {
+ wiphy_err(priv->hw->wiphy,
+ "%s: cannot download firmware image <%s>\n",
+ MWL_DRV_NAME, fw_name);
+ goto err_download_fw;
+ }
+
+ return rc;
+
+err_download_fw:
+
+ release_firmware(priv->fw_ucode);
+
+err_load_fw:
+
+ wiphy_err(priv->hw->wiphy, "firmware init fail\n");
+
+ return rc;
+}
+
+static void mwl_reg_notifier(struct wiphy *wiphy,
+ struct regulatory_request *request)
+{
+ struct ieee80211_hw *hw;
+ struct mwl_priv *priv;
+
+ hw = (struct ieee80211_hw *)wiphy_priv(wiphy);
+ priv = hw->priv;
+
+ if (priv->forbidden_setting) {
+ if (!priv->regulatory_set) {
+ regulatory_hint(wiphy, priv->fw_alpha2);
+ priv->regulatory_set = true;
+ } else {
+ if (memcmp(priv->fw_alpha2, request->alpha2, 2))
+ regulatory_hint(wiphy, priv->fw_alpha2);
+ }
+ return;
+ }
+
+ priv->dfs_region = request->dfs_region;
+}
+
+static void mwl_process_of_dts(struct mwl_priv *priv)
+{
+#ifdef CONFIG_OF
+ struct property *prop;
+ u32 prop_value;
+
+ priv->dt_node =
+ of_find_node_by_name(pci_bus_to_OF_node(priv->pdev->bus),
+ "mwlwifi");
+ if (!priv->dt_node)
+ return;
+
+ /* look for all matching property names */
+ for_each_property_of_node(priv->dt_node, prop) {
+ if (strcmp(prop->name, "marvell,2ghz") == 0)
+ priv->disable_2g = true;
+ if (strcmp(prop->name, "marvell,5ghz") == 0)
+ priv->disable_5g = true;
+ if (strcmp(prop->name, "marvell,chainmask") == 0) {
+ prop_value = be32_to_cpu(*((__be32 *)prop->value));
+ if (prop_value == 2)
+ priv->antenna_tx = ANTENNA_TX_2;
+
+ prop_value = be32_to_cpu(*((__be32 *)
+ (prop->value + 4)));
+ if (prop_value == 2)
+ priv->antenna_rx = ANTENNA_RX_2;
+ }
+ }
+
+ priv->pwr_node = of_find_node_by_name(priv->dt_node,
+ "marvell,powertable");
+#endif
+}
+
+static void mwl_set_ht_caps(struct mwl_priv *priv,
+ struct ieee80211_supported_band *band)
+{
+ struct ieee80211_hw *hw;
+
+ hw = priv->hw;
+
+ band->ht_cap.ht_supported = 1;
+
+ band->ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING;
+ band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+ band->ht_cap.cap |= IEEE80211_HT_CAP_SM_PS;
+ band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20;
+ band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40;
+
+ ieee80211_hw_set(hw, AMPDU_AGGREGATION);
+ ieee80211_hw_set(hw, SUPPORTS_AMSDU_IN_AMPDU);
+ band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
+ band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_4;
+
+ band->ht_cap.mcs.rx_mask[0] = 0xff;
+ band->ht_cap.mcs.rx_mask[1] = 0xff;
+ if (priv->antenna_rx == ANTENNA_RX_4_AUTO)
+ band->ht_cap.mcs.rx_mask[2] = 0xff;
+ band->ht_cap.mcs.rx_mask[4] = 0x01;
+
+ band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
+}
+
+static void mwl_set_vht_caps(struct mwl_priv *priv,
+ struct ieee80211_supported_band *band)
+{
+ band->vht_cap.vht_supported = 1;
+
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895;
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC;
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80;
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1;
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE;
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK;
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN;
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN;
+
+ if (priv->antenna_rx == ANTENNA_RX_2)
+ band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xfffa);
+ else
+ band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xffea);
+
+ if (priv->antenna_tx == ANTENNA_TX_2)
+ band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xfffa);
+ else
+ band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xffea);
+}
+
+static void mwl_set_caps(struct mwl_priv *priv)
+{
+ struct ieee80211_hw *hw;
+
+ hw = priv->hw;
+
+ /* set up band information for 2.4G */
+ if (!priv->disable_2g) {
+ BUILD_BUG_ON(sizeof(priv->channels_24) !=
+ sizeof(mwl_channels_24));
+ memcpy(priv->channels_24, mwl_channels_24,
+ sizeof(mwl_channels_24));
+
+ BUILD_BUG_ON(sizeof(priv->rates_24) != sizeof(mwl_rates_24));
+ memcpy(priv->rates_24, mwl_rates_24, sizeof(mwl_rates_24));
+
+ priv->band_24.band = NL80211_BAND_2GHZ;
+ priv->band_24.channels = priv->channels_24;
+ priv->band_24.n_channels = ARRAY_SIZE(mwl_channels_24);
+ priv->band_24.bitrates = priv->rates_24;
+ priv->band_24.n_bitrates = ARRAY_SIZE(mwl_rates_24);
+
+ mwl_set_ht_caps(priv, &priv->band_24);
+ mwl_set_vht_caps(priv, &priv->band_24);
+
+ hw->wiphy->bands[NL80211_BAND_2GHZ] = &priv->band_24;
+ }
+
+ /* set up band information for 5G */
+ if (!priv->disable_5g) {
+ BUILD_BUG_ON(sizeof(priv->channels_50) !=
+ sizeof(mwl_channels_50));
+ memcpy(priv->channels_50, mwl_channels_50,
+ sizeof(mwl_channels_50));
+
+ BUILD_BUG_ON(sizeof(priv->rates_50) != sizeof(mwl_rates_50));
+ memcpy(priv->rates_50, mwl_rates_50, sizeof(mwl_rates_50));
+
+ priv->band_50.band = NL80211_BAND_5GHZ;
+ priv->band_50.channels = priv->channels_50;
+ priv->band_50.n_channels = ARRAY_SIZE(mwl_channels_50);
+ priv->band_50.bitrates = priv->rates_50;
+ priv->band_50.n_bitrates = ARRAY_SIZE(mwl_rates_50);
+
+ mwl_set_ht_caps(priv, &priv->band_50);
+ mwl_set_vht_caps(priv, &priv->band_50);
+
+ hw->wiphy->bands[NL80211_BAND_5GHZ] = &priv->band_50;
+ }
+}
+
+static void mwl_regd_init(struct mwl_priv *priv)
+{
+ u8 region_code;
+ int i;
+
+ /* hook regulatory domain change notification */
+ priv->hw->wiphy->reg_notifier = mwl_reg_notifier;
+
+ if (mwl_fwcmd_get_device_pwr_tbl(priv->hw,
+ &priv->device_pwr_tbl[0],
+ ®ion_code,
+ &priv->number_of_channels,
+ 0))
+ return;
+
+ priv->forbidden_setting = true;
+
+ for (i = 0; i < priv->number_of_channels; i++)
+ mwl_fwcmd_get_device_pwr_tbl(priv->hw,
+ &priv->device_pwr_tbl[i],
+ ®ion_code,
+ &priv->number_of_channels,
+ i);
+
+ for (i = 0; i < ARRAY_SIZE(regmap); i++)
+ if (regmap[i].region_code == priv->fw_region_code) {
+ memcpy(priv->fw_alpha2, regmap[i].alpha2, 2);
+ break;
+ }
+}
+
+static void timer_routine(unsigned long data)
+{
+ struct mwl_priv *priv = (struct mwl_priv *)data;
+ struct mwl_ampdu_stream *stream;
+ struct mwl_sta *sta_info;
+ struct mwl_tx_info *tx_stats;
+ int i;
+
+ spin_lock_bh(&priv->stream_lock);
+ for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) {
+ stream = &priv->ampdu[i];
+
+ if (stream->state == AMPDU_STREAM_ACTIVE) {
+ sta_info = mwl_dev_get_sta(stream->sta);
+ tx_stats = &sta_info->tx_stats[stream->tid];
+
+ if ((jiffies - tx_stats->start_time > HZ) &&
+ (tx_stats->pkts < SYSADPT_AMPDU_PACKET_THRESHOLD)) {
+ ieee80211_stop_tx_ba_session(stream->sta,
+ stream->tid);
+ }
+
+ if (jiffies - tx_stats->start_time > HZ) {
+ tx_stats->pkts = 0;
+ tx_stats->start_time = jiffies;
+ }
+ }
+ }
+ spin_unlock_bh(&priv->stream_lock);
+
+ mod_timer(&priv->period_timer, jiffies +
+ msecs_to_jiffies(SYSADPT_TIMER_WAKEUP_TIME));
+}
+
+static int mwl_wl_init(struct mwl_priv *priv)
+{
+ struct ieee80211_hw *hw;
+ int rc;
+ int i;
+
+ hw = priv->hw;
+
+ hw->extra_tx_headroom = SYSADPT_MIN_BYTES_HEADROOM;
+ hw->queues = SYSADPT_TX_WMM_QUEUES;
+
+ /* Set rssi values to dBm */
+ ieee80211_hw_set(hw, SIGNAL_DBM);
+ ieee80211_hw_set(hw, HAS_RATE_CONTROL);
+
+ /* Ask mac80211 not to trigger PS mode
+ * based on PM bit of incoming frames.
+ */
+ ieee80211_hw_set(hw, AP_LINK_PS);
+
+ ieee80211_hw_set(hw, SUPPORTS_PER_STA_GTK);
+ ieee80211_hw_set(hw, MFP_CAPABLE);
+
+ hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN;
+ hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH;
+
+ hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
+
+ hw->vif_data_size = sizeof(struct mwl_vif);
+ hw->sta_data_size = sizeof(struct mwl_sta);
+
+ priv->ap_macids_supported = 0x0000ffff;
+ priv->sta_macids_supported = 0x00010000;
+ priv->macids_used = 0;
+ INIT_LIST_HEAD(&priv->vif_list);
+ INIT_LIST_HEAD(&priv->sta_list);
+
+ /* Set default radio state, preamble and wmm */
+ priv->radio_on = false;
+ priv->radio_short_preamble = false;
+ priv->wmm_enabled = false;
+
+ priv->powinited = 0;
+
+ priv->csa_active = false;
+ priv->dfs_chirp_count_min = 5;
+ priv->dfs_chirp_time_interval = 1000;
+ priv->dfs_pw_filter = 0;
+ priv->dfs_min_num_radar = 5;
+ priv->dfs_min_pri_count = 4;
+
+ /* Handle watchdog ba events */
+ INIT_WORK(&priv->watchdog_ba_handle, mwl_watchdog_ba_events);
+ INIT_WORK(&priv->chnl_switch_handle, mwl_chnl_switch_event);
+
+ tasklet_init(&priv->tx_task, (void *)mwl_tx_skbs, (unsigned long)hw);
+ tasklet_disable(&priv->tx_task);
+ tasklet_init(&priv->tx_done_task,
+ (void *)mwl_tx_done, (unsigned long)hw);
+ tasklet_disable(&priv->tx_done_task);
+ tasklet_init(&priv->rx_task, (void *)mwl_rx_recv, (unsigned long)hw);
+ tasklet_disable(&priv->rx_task);
+ tasklet_init(&priv->qe_task,
+ (void *)mwl_tx_flush_amsdu, (unsigned long)hw);
+ tasklet_disable(&priv->qe_task);
+ priv->txq_limit = SYSADPT_TX_QUEUE_LIMIT;
+ priv->is_tx_done_schedule = false;
+ priv->recv_limit = SYSADPT_RECEIVE_LIMIT;
+ priv->is_rx_schedule = false;
+ priv->is_qe_schedule = false;
+ priv->qe_trigger_num = 0;
+ priv->qe_trigger_time = jiffies;
+
+ mutex_init(&priv->fwcmd_mutex);
+ spin_lock_init(&priv->tx_desc_lock);
+ spin_lock_init(&priv->vif_lock);
+ spin_lock_init(&priv->sta_lock);
+ spin_lock_init(&priv->stream_lock);
+
+ rc = mwl_thermal_register(priv);
+ if (rc) {
+ wiphy_err(hw->wiphy, "%s: fail to register thermal framework\n",
+ MWL_DRV_NAME);
+ goto err_thermal_register;
+ }
+
+ rc = mwl_tx_init(hw);
+ if (rc) {
+ wiphy_err(hw->wiphy, "%s: fail to initialize TX\n",
+ MWL_DRV_NAME);
+ goto err_mwl_tx_init;
+ }
+
+ rc = mwl_rx_init(hw);
+ if (rc) {
+ wiphy_err(hw->wiphy, "%s: fail to initialize RX\n",
+ MWL_DRV_NAME);
+ goto err_mwl_rx_init;
+ }
+
+ rc = mwl_fwcmd_get_hw_specs(hw);
+ if (rc) {
+ wiphy_err(hw->wiphy, "%s: fail to get HW specifications\n",
+ MWL_DRV_NAME);
+ goto err_get_hw_specs;
+ }
+
+ SET_IEEE80211_PERM_ADDR(hw, priv->hw_data.mac_addr);
+
+ writel(priv->desc_data[0].pphys_tx_ring,
+ priv->iobase0 + priv->desc_data[0].wcb_base);
+ for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++)
+ writel(priv->desc_data[i].pphys_tx_ring,
+ priv->iobase0 + priv->desc_data[i].wcb_base);
+ writel(priv->desc_data[0].pphys_rx_ring,
+ priv->iobase0 + priv->desc_data[0].rx_desc_read);
+ writel(priv->desc_data[0].pphys_rx_ring,
+ priv->iobase0 + priv->desc_data[0].rx_desc_write);
+
+ rc = mwl_fwcmd_set_hw_specs(hw);
+ if (rc) {
+ wiphy_err(hw->wiphy, "%s: fail to set HW specifications\n",
+ MWL_DRV_NAME);
+ goto err_set_hw_specs;
+ }
+
+ wiphy_info(hw->wiphy,
+ "firmware version: 0x%x\n", priv->hw_data.fw_release_num);
+
+ if (!mwl_fwcmd_get_fw_region_code(hw, &priv->fw_region_code)) {
+ priv->fw_device_pwrtbl = true;
+ mwl_regd_init(priv);
+ wiphy_info(hw->wiphy,
+ "firmware region code: %x\n", priv->fw_region_code);
+ }
+
+ mwl_fwcmd_radio_disable(hw);
+
+ mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_TX, priv->antenna_tx);
+
+ mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_RX, priv->antenna_rx);
+
+ hw->wiphy->interface_modes = 0;
+ hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP);
+ hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_STATION);
+ hw->wiphy->iface_combinations = &ap_if_comb;
+ hw->wiphy->n_iface_combinations = 1;
+
+ mwl_set_caps(priv);
+
+ rc = ieee80211_register_hw(hw);
+ if (rc) {
+ wiphy_err(hw->wiphy, "%s: fail to register device\n",
+ MWL_DRV_NAME);
+ goto err_register_hw;
+ }
+
+ rc = request_irq(priv->pdev->irq, mwl_isr,
+ IRQF_SHARED, MWL_DRV_NAME, hw);
+ if (rc) {
+ priv->irq = -1;
+ wiphy_err(hw->wiphy, "fail to register IRQ handler\n");
+ goto err_register_irq;
+ }
+ priv->irq = priv->pdev->irq;
+
+ setup_timer(&priv->period_timer, timer_routine, (unsigned long)priv);
+ mod_timer(&priv->period_timer, jiffies +
+ msecs_to_jiffies(SYSADPT_TIMER_WAKEUP_TIME));
+
+ return rc;
+
+err_register_irq:
+err_register_hw:
+err_set_hw_specs:
+err_get_hw_specs:
+
+ mwl_rx_deinit(hw);
+
+err_mwl_rx_init:
+
+ mwl_tx_deinit(hw);
+
+err_mwl_tx_init:
+err_thermal_register:
+
+ wiphy_err(hw->wiphy, "init fail\n");
+
+ return rc;
+}
+
+static void mwl_wl_deinit(struct mwl_priv *priv)
+{
+ struct ieee80211_hw *hw = priv->hw;
+
+ del_timer_sync(&priv->period_timer);
+
+ if (priv->irq != -1) {
+ free_irq(priv->pdev->irq, hw);
+ priv->irq = -1;
+ }
+
+ ieee80211_unregister_hw(hw);
+ mwl_thermal_unregister(priv);
+ mwl_rx_deinit(hw);
+ mwl_tx_deinit(hw);
+ tasklet_kill(&priv->qe_task);
+ tasklet_kill(&priv->rx_task);
+ tasklet_kill(&priv->tx_done_task);
+ tasklet_kill(&priv->tx_task);
+ cancel_work_sync(&priv->watchdog_ba_handle);
+ mwl_fwcmd_reset(hw);
+}
+
+static int mwl_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct ieee80211_hw *hw;
+ struct mwl_priv *priv;
+ const char *fw_name;
+ int rc = 0;
+
+ if (id->driver_data >= MWLUNKNOWN)
+ return -ENODEV;
+
+ rc = pci_enable_device(pdev);
+ if (rc) {
+ pr_err("%s: cannot enable new PCI device",
+ MWL_DRV_NAME);
+ return rc;
+ }
+
+ rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
+ if (rc) {
+ pr_err("%s: 32-bit PCI DMA not supported",
+ MWL_DRV_NAME);
+ goto err_pci_disable_device;
+ }
+
+ pci_set_master(pdev);
+
+ hw = ieee80211_alloc_hw(sizeof(*priv), &mwl_mac80211_ops);
+ if (!hw) {
+ pr_err("%s: ieee80211 alloc failed",
+ MWL_DRV_NAME);
+ rc = -ENOMEM;
+ goto err_pci_disable_device;
+ }
+
+ pci_set_drvdata(pdev, hw);
+
+ priv = hw->priv;
+ priv->hw = hw;
+ priv->pdev = pdev;
+ priv->dev = &pdev->dev;
+ priv->chip_type = id->driver_data;
+ priv->fw_device_pwrtbl = false;
+ priv->forbidden_setting = false;
+ priv->regulatory_set = false;
+ priv->disable_2g = false;
+ priv->disable_5g = false;
+ priv->antenna_tx = mwl_chip_tbl[priv->chip_type].antenna_tx;
+ priv->antenna_rx = mwl_chip_tbl[priv->chip_type].antenna_rx;
+
+ SET_IEEE80211_DEV(hw, priv->dev);
+
+ rc = mwl_alloc_pci_resource(priv);
+ if (rc)
+ goto err_alloc_pci_resource;
+
+ fw_name = mwl_chip_tbl[priv->chip_type].fw_image;
+
+ rc = mwl_init_firmware(priv, fw_name);
+
+ if (rc) {
+ wiphy_err(hw->wiphy, "%s: fail to initialize firmware\n",
+ MWL_DRV_NAME);
+ goto err_init_firmware;
+ }
+
+ /* firmware is loaded to H/W, it can be released now */
+ release_firmware(priv->fw_ucode);
+
+ mwl_process_of_dts(priv);
+
+ rc = mwl_wl_init(priv);
+ if (rc) {
+ wiphy_err(hw->wiphy, "%s: fail to initialize wireless lan\n",
+ MWL_DRV_NAME);
+ goto err_wl_init;
+ }
+
+ wiphy_info(priv->hw->wiphy, "2G %s, 5G %s\n",
+ priv->disable_2g ? "disabled" : "enabled",
+ priv->disable_5g ? "disabled" : "enabled");
+
+ wiphy_info(priv->hw->wiphy, "%s TX antennas, %s RX antennas\n",
+ (priv->antenna_tx == ANTENNA_TX_4_AUTO) ? "4" : "2",
+ (priv->antenna_rx == ANTENNA_RX_4_AUTO) ? "4" : "2");
+
+#ifdef CONFIG_DEBUG_FS
+ mwl_debugfs_init(hw);
+#endif
+
+ return rc;
+
+err_wl_init:
+err_init_firmware:
+
+ mwl_fwcmd_reset(hw);
+
+err_alloc_pci_resource:
+
+ pci_set_drvdata(pdev, NULL);
+ ieee80211_free_hw(hw);
+
+err_pci_disable_device:
+
+ pci_disable_device(pdev);
+
+ return rc;
+}
+
+static void mwl_remove(struct pci_dev *pdev)
+{
+ struct ieee80211_hw *hw = pci_get_drvdata(pdev);
+ struct mwl_priv *priv;
+
+ if (!hw)
+ return;
+
+ priv = hw->priv;
+
+ mwl_wl_deinit(priv);
+ pci_set_drvdata(pdev, NULL);
+ ieee80211_free_hw(hw);
+ pci_disable_device(pdev);
+
+#ifdef CONFIG_DEBUG_FS
+ mwl_debugfs_remove(hw);
+#endif
+}
+
+static struct pci_driver mwl_pci_driver = {
+ .name = MWL_DRV_NAME,
+ .id_table = mwl_pci_id_tbl,
+ .probe = mwl_probe,
+ .remove = mwl_remove
+};
+
+module_pci_driver(mwl_pci_driver);
+
+MODULE_DESCRIPTION(MWL_DESC);
+MODULE_AUTHOR("Marvell Semiconductor, Inc.");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE(MWL_DEV_NAME);
+MODULE_DEVICE_TABLE(pci, mwl_pci_id_tbl);
diff --git a/drivers/net/wireless/marvell/mwlwifi/rx.c b/drivers/net/wireless/marvell/mwlwifi/rx.c
new file mode 100644
index 0000000..1b8b385
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/rx.c
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file implements receive related functions. */
+
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "rx.h"
+
+#define MAX_NUM_RX_RING_BYTES (SYSADPT_MAX_NUM_RX_DESC * \
+ sizeof(struct mwl_rx_desc))
+
+#define MAX_NUM_RX_HNDL_BYTES (SYSADPT_MAX_NUM_RX_DESC * \
+ sizeof(struct mwl_rx_hndl))
+
+#define DECRYPT_ERR_MASK 0x80
+#define GENERAL_DECRYPT_ERR 0xFF
+#define TKIP_DECRYPT_MIC_ERR 0x02
+#define WEP_DECRYPT_ICV_ERR 0x04
+#define TKIP_DECRYPT_ICV_ERR 0x08
+
+#define W836X_RSSI_OFFSET 8
+
+/* Receive rate information constants */
+#define RX_RATE_INFO_FORMAT_11A 0
+#define RX_RATE_INFO_FORMAT_11B 1
+#define RX_RATE_INFO_FORMAT_11N 2
+#define RX_RATE_INFO_FORMAT_11AC 4
+
+#define RX_RATE_INFO_HT20 0
+#define RX_RATE_INFO_HT40 1
+#define RX_RATE_INFO_HT80 2
+
+#define RX_RATE_INFO_LONG_INTERVAL 0
+#define RX_RATE_INFO_SHORT_INTERVAL 1
+
+static int mwl_rx_ring_alloc(struct mwl_priv *priv)
+{
+ struct mwl_desc_data *desc;
+
+ desc = &priv->desc_data[0];
+
+ desc->prx_ring = (struct mwl_rx_desc *)
+ dma_alloc_coherent(priv->dev,
+ MAX_NUM_RX_RING_BYTES,
+ &desc->pphys_rx_ring,
+ GFP_KERNEL);
+
+ if (!desc->prx_ring) {
+ wiphy_err(priv->hw->wiphy, "cannot alloc mem\n");
+ return -ENOMEM;
+ }
+
+ memset(desc->prx_ring, 0x00, MAX_NUM_RX_RING_BYTES);
+
+ desc->rx_hndl = kmalloc(MAX_NUM_RX_HNDL_BYTES, GFP_KERNEL);
+
+ if (!desc->rx_hndl) {
+ dma_free_coherent(priv->dev,
+ MAX_NUM_RX_RING_BYTES,
+ desc->prx_ring,
+ desc->pphys_rx_ring);
+ return -ENOMEM;
+ }
+
+ memset(desc->rx_hndl, 0x00, MAX_NUM_RX_HNDL_BYTES);
+
+ return 0;
+}
+
+static int mwl_rx_ring_init(struct mwl_priv *priv)
+{
+ struct mwl_desc_data *desc;
+ int i;
+ struct mwl_rx_hndl *rx_hndl;
+ dma_addr_t dma;
+ u32 val;
+
+ desc = &priv->desc_data[0];
+
+ if (desc->prx_ring) {
+ desc->rx_buf_size = SYSADPT_MAX_AGGR_SIZE;
+
+ for (i = 0; i < SYSADPT_MAX_NUM_RX_DESC; i++) {
+ rx_hndl = &desc->rx_hndl[i];
+ rx_hndl->psk_buff =
+ dev_alloc_skb(desc->rx_buf_size);
+
+ if (!rx_hndl->psk_buff) {
+ wiphy_err(priv->hw->wiphy,
+ "rxdesc %i: no skbuff available\n",
+ i);
+ return -ENOMEM;
+ }
+
+ skb_reserve(rx_hndl->psk_buff,
+ SYSADPT_MIN_BYTES_HEADROOM);
+ desc->prx_ring[i].rx_control =
+ EAGLE_RXD_CTRL_DRIVER_OWN;
+ desc->prx_ring[i].status = EAGLE_RXD_STATUS_OK;
+ desc->prx_ring[i].qos_ctrl = 0x0000;
+ desc->prx_ring[i].channel = 0x00;
+ desc->prx_ring[i].rssi = 0x00;
+ desc->prx_ring[i].pkt_len =
+ cpu_to_le16(SYSADPT_MAX_AGGR_SIZE);
+ dma = pci_map_single(priv->pdev,
+ rx_hndl->psk_buff->data,
+ desc->rx_buf_size,
+ PCI_DMA_FROMDEVICE);
+ if (pci_dma_mapping_error(priv->pdev, dma)) {
+ wiphy_err(priv->hw->wiphy,
+ "failed to map pci memory!\n");
+ return -ENOMEM;
+ }
+ desc->prx_ring[i].pphys_buff_data = cpu_to_le32(dma);
+ val = (u32)desc->pphys_rx_ring +
+ ((i + 1) * sizeof(struct mwl_rx_desc));
+ desc->prx_ring[i].pphys_next = cpu_to_le32(val);
+ rx_hndl->pdesc = &desc->prx_ring[i];
+ if (i < (SYSADPT_MAX_NUM_RX_DESC - 1))
+ rx_hndl->pnext = &desc->rx_hndl[i + 1];
+ }
+ desc->prx_ring[SYSADPT_MAX_NUM_RX_DESC - 1].pphys_next =
+ cpu_to_le32((u32)desc->pphys_rx_ring);
+ desc->rx_hndl[SYSADPT_MAX_NUM_RX_DESC - 1].pnext =
+ &desc->rx_hndl[0];
+ desc->pnext_rx_hndl = &desc->rx_hndl[0];
+
+ return 0;
+ }
+
+ wiphy_err(priv->hw->wiphy, "no valid RX mem\n");
+
+ return -ENOMEM;
+}
+
+static void mwl_rx_ring_cleanup(struct mwl_priv *priv)
+{
+ struct mwl_desc_data *desc;
+ int i;
+ struct mwl_rx_hndl *rx_hndl;
+
+ desc = &priv->desc_data[0];
+
+ if (desc->prx_ring) {
+ for (i = 0; i < SYSADPT_MAX_NUM_RX_DESC; i++) {
+ rx_hndl = &desc->rx_hndl[i];
+ if (!rx_hndl->psk_buff)
+ continue;
+
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu
+ (rx_hndl->pdesc->pphys_buff_data),
+ desc->rx_buf_size,
+ PCI_DMA_FROMDEVICE);
+
+ dev_kfree_skb_any(rx_hndl->psk_buff);
+
+ wiphy_info(priv->hw->wiphy,
+ "unmapped+free'd %i 0x%p 0x%x %i\n",
+ i, rx_hndl->psk_buff->data,
+ le32_to_cpu(rx_hndl->pdesc->pphys_buff_data),
+ desc->rx_buf_size);
+
+ rx_hndl->psk_buff = NULL;
+ }
+ }
+}
+
+static void mwl_rx_ring_free(struct mwl_priv *priv)
+{
+ struct mwl_desc_data *desc;
+
+ desc = &priv->desc_data[0];
+
+ if (desc->prx_ring) {
+ mwl_rx_ring_cleanup(priv);
+
+ dma_free_coherent(priv->dev,
+ MAX_NUM_RX_RING_BYTES,
+ desc->prx_ring,
+ desc->pphys_rx_ring);
+
+ desc->prx_ring = NULL;
+ }
+
+ kfree(desc->rx_hndl);
+
+ desc->pnext_rx_hndl = NULL;
+}
+
+static inline void mwl_rx_prepare_status(struct mwl_rx_desc *pdesc,
+ struct ieee80211_rx_status *status)
+{
+ u16 rate, format, nss, bw, gi, rt;
+
+ memset(status, 0, sizeof(*status));
+
+ status->signal = -(pdesc->rssi + W836X_RSSI_OFFSET);
+
+ rate = le16_to_cpu(pdesc->rate);
+ format = rate & MWL_RX_RATE_FORMAT_MASK;
+ nss = (rate & MWL_RX_RATE_NSS_MASK) >> MWL_RX_RATE_NSS_SHIFT;
+ bw = (rate & MWL_RX_RATE_BW_MASK) >> MWL_RX_RATE_BW_SHIFT;
+ gi = (rate & MWL_RX_RATE_GI_MASK) >> MWL_RX_RATE_GI_SHIFT;
+ rt = (rate & MWL_RX_RATE_RT_MASK) >> MWL_RX_RATE_RT_SHIFT;
+
+ switch (format) {
+ case RX_RATE_INFO_FORMAT_11N:
+ status->flag |= RX_FLAG_HT;
+ if (bw == RX_RATE_INFO_HT40)
+ status->flag |= RX_FLAG_40MHZ;
+ if (gi == RX_RATE_INFO_SHORT_INTERVAL)
+ status->flag |= RX_FLAG_SHORT_GI;
+ break;
+ case RX_RATE_INFO_FORMAT_11AC:
+ status->flag |= RX_FLAG_VHT;
+ if (bw == RX_RATE_INFO_HT40)
+ status->flag |= RX_FLAG_40MHZ;
+ if (bw == RX_RATE_INFO_HT80)
+ status->vht_flag |= RX_VHT_FLAG_80MHZ;
+ if (gi == RX_RATE_INFO_SHORT_INTERVAL)
+ status->flag |= RX_FLAG_SHORT_GI;
+ status->vht_nss = (nss + 1);
+ break;
+ }
+
+ status->rate_idx = rt;
+
+ if (pdesc->channel > BAND_24_CHANNEL_NUM) {
+ status->band = NL80211_BAND_5GHZ;
+ if ((!(status->flag & RX_FLAG_HT)) &&
+ (!(status->flag & RX_FLAG_VHT))) {
+ status->rate_idx -= 5;
+ if (status->rate_idx >= BAND_50_RATE_NUM)
+ status->rate_idx = BAND_50_RATE_NUM - 1;
+ }
+ } else {
+ status->band = NL80211_BAND_2GHZ;
+ if ((!(status->flag & RX_FLAG_HT)) &&
+ (!(status->flag & RX_FLAG_VHT))) {
+ if (status->rate_idx >= BAND_24_RATE_NUM)
+ status->rate_idx = BAND_24_RATE_NUM - 1;
+ }
+ }
+
+ status->freq = ieee80211_channel_to_frequency(pdesc->channel,
+ status->band);
+
+ /* check if status has a specific error bit (bit 7) set or indicates
+ * a general decrypt error
+ */
+ if ((pdesc->status == GENERAL_DECRYPT_ERR) ||
+ (pdesc->status & DECRYPT_ERR_MASK)) {
+ /* check if status is not equal to 0xFF
+ * the 0xFF check is for backward compatibility
+ */
+ if (pdesc->status != GENERAL_DECRYPT_ERR) {
+ if (((pdesc->status & (~DECRYPT_ERR_MASK)) &
+ TKIP_DECRYPT_MIC_ERR) && !((pdesc->status &
+ (WEP_DECRYPT_ICV_ERR | TKIP_DECRYPT_ICV_ERR)))) {
+ status->flag |= RX_FLAG_MMIC_ERROR;
+ }
+ }
+ }
+}
+
+static inline struct mwl_vif *mwl_rx_find_vif_bss(struct mwl_priv *priv,
+ u8 *bssid)
+{
+ struct mwl_vif *mwl_vif;
+
+ spin_lock_bh(&priv->vif_lock);
+ list_for_each_entry(mwl_vif, &priv->vif_list, list) {
+ if (ether_addr_equal(bssid, mwl_vif->bssid)) {
+ spin_unlock_bh(&priv->vif_lock);
+ return mwl_vif;
+ }
+ }
+ spin_unlock_bh(&priv->vif_lock);
+
+ return NULL;
+}
+
+static inline void mwl_rx_remove_dma_header(struct sk_buff *skb, __le16 qos)
+{
+ struct mwl_dma_data *tr;
+ int hdrlen;
+
+ tr = (struct mwl_dma_data *)skb->data;
+ hdrlen = ieee80211_hdrlen(tr->wh.frame_control);
+
+ if (hdrlen != sizeof(tr->wh)) {
+ if (ieee80211_is_data_qos(tr->wh.frame_control)) {
+ memmove(tr->data - hdrlen, &tr->wh, hdrlen - 2);
+ *((__le16 *)(tr->data - 2)) = qos;
+ } else {
+ memmove(tr->data - hdrlen, &tr->wh, hdrlen);
+ }
+ }
+
+ if (hdrlen != sizeof(*tr))
+ skb_pull(skb, sizeof(*tr) - hdrlen);
+}
+
+static int mwl_rx_refill(struct mwl_priv *priv, struct mwl_rx_hndl *rx_hndl)
+{
+ struct mwl_desc_data *desc;
+ dma_addr_t dma;
+
+ desc = &priv->desc_data[0];
+
+ rx_hndl->psk_buff = dev_alloc_skb(desc->rx_buf_size);
+
+ if (!rx_hndl->psk_buff)
+ return -ENOMEM;
+
+ skb_reserve(rx_hndl->psk_buff, SYSADPT_MIN_BYTES_HEADROOM);
+
+ rx_hndl->pdesc->status = EAGLE_RXD_STATUS_OK;
+ rx_hndl->pdesc->qos_ctrl = 0x0000;
+ rx_hndl->pdesc->channel = 0x00;
+ rx_hndl->pdesc->rssi = 0x00;
+ rx_hndl->pdesc->pkt_len = cpu_to_le16(desc->rx_buf_size);
+
+ dma = pci_map_single(priv->pdev,
+ rx_hndl->psk_buff->data,
+ desc->rx_buf_size,
+ PCI_DMA_FROMDEVICE);
+ if (pci_dma_mapping_error(priv->pdev, dma)) {
+ dev_kfree_skb_any(rx_hndl->psk_buff);
+ wiphy_err(priv->hw->wiphy,
+ "failed to map pci memory!\n");
+ return -ENOMEM;
+ }
+
+ rx_hndl->pdesc->pphys_buff_data = cpu_to_le32(dma);
+
+ return 0;
+}
+
+int mwl_rx_init(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+ int rc;
+
+ rc = mwl_rx_ring_alloc(priv);
+ if (rc) {
+ wiphy_err(hw->wiphy, "allocating RX ring failed\n");
+ return rc;
+ }
+
+ rc = mwl_rx_ring_init(priv);
+ if (rc) {
+ mwl_rx_ring_free(priv);
+ wiphy_err(hw->wiphy,
+ "initializing RX ring failed\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+void mwl_rx_deinit(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ mwl_rx_ring_cleanup(priv);
+ mwl_rx_ring_free(priv);
+}
+
+void mwl_rx_recv(unsigned long data)
+{
+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data;
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_desc_data *desc;
+ struct mwl_rx_hndl *curr_hndl;
+ int work_done = 0;
+ struct sk_buff *prx_skb = NULL;
+ int pkt_len;
+ struct ieee80211_rx_status status;
+ struct mwl_vif *mwl_vif = NULL;
+ struct ieee80211_hdr *wh;
+ u32 status_mask;
+
+ desc = &priv->desc_data[0];
+ curr_hndl = desc->pnext_rx_hndl;
+
+ if (!curr_hndl) {
+ status_mask = readl(priv->iobase1 +
+ MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+ writel(status_mask | MACREG_A2HRIC_BIT_RX_RDY,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+
+ priv->is_rx_schedule = false;
+ wiphy_warn(hw->wiphy, "busy or no receiving packets\n");
+ return;
+ }
+
+ while ((curr_hndl->pdesc->rx_control == EAGLE_RXD_CTRL_DMA_OWN) &&
+ (work_done < priv->recv_limit)) {
+ prx_skb = curr_hndl->psk_buff;
+ if (!prx_skb)
+ goto out;
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu(curr_hndl->pdesc->pphys_buff_data),
+ desc->rx_buf_size,
+ PCI_DMA_FROMDEVICE);
+ pkt_len = le16_to_cpu(curr_hndl->pdesc->pkt_len);
+
+ if (skb_tailroom(prx_skb) < pkt_len) {
+ dev_kfree_skb_any(prx_skb);
+ goto out;
+ }
+
+ if (curr_hndl->pdesc->channel !=
+ hw->conf.chandef.chan->hw_value) {
+ dev_kfree_skb_any(prx_skb);
+ goto out;
+ }
+
+ mwl_rx_prepare_status(curr_hndl->pdesc, &status);
+
+ priv->noise = -curr_hndl->pdesc->noise_floor;
+
+ wh = &((struct mwl_dma_data *)prx_skb->data)->wh;
+
+ if (ieee80211_has_protected(wh->frame_control)) {
+ /* Check if hw crypto has been enabled for
+ * this bss. If yes, set the status flags
+ * accordingly
+ */
+ if (ieee80211_has_tods(wh->frame_control)) {
+ mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr1);
+ if (!mwl_vif &&
+ ieee80211_has_a4(wh->frame_control))
+ mwl_vif =
+ mwl_rx_find_vif_bss(priv,
+ wh->addr2);
+ } else {
+ mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr2);
+ }
+
+ if ((mwl_vif && mwl_vif->is_hw_crypto_enabled) ||
+ is_multicast_ether_addr(wh->addr1) ||
+ (ieee80211_is_mgmt(wh->frame_control) &&
+ ieee80211_has_protected(wh->frame_control) &&
+ !is_multicast_ether_addr(wh->addr1))) {
+ /* When MMIC ERROR is encountered
+ * by the firmware, payload is
+ * dropped and only 32 bytes of
+ * mwlwifi Firmware header is sent
+ * to the host.
+ *
+ * We need to add four bytes of
+ * key information. In it
+ * MAC80211 expects keyidx set to
+ * 0 for triggering Counter
+ * Measure of MMIC failure.
+ */
+ if (status.flag & RX_FLAG_MMIC_ERROR) {
+ struct mwl_dma_data *tr;
+
+ tr = (struct mwl_dma_data *)
+ prx_skb->data;
+ memset((void *)&tr->data, 0, 4);
+ pkt_len += 4;
+ }
+
+ if (!ieee80211_is_auth(wh->frame_control))
+ status.flag |= RX_FLAG_IV_STRIPPED |
+ RX_FLAG_DECRYPTED |
+ RX_FLAG_MMIC_STRIPPED;
+ }
+ }
+
+ skb_put(prx_skb, pkt_len);
+ mwl_rx_remove_dma_header(prx_skb, curr_hndl->pdesc->qos_ctrl);
+
+ memcpy(IEEE80211_SKB_RXCB(prx_skb), &status, sizeof(status));
+ ieee80211_rx(hw, prx_skb);
+out:
+ mwl_rx_refill(priv, curr_hndl);
+ curr_hndl->pdesc->rx_control = EAGLE_RXD_CTRL_DRIVER_OWN;
+ curr_hndl->pdesc->qos_ctrl = 0;
+ curr_hndl = curr_hndl->pnext;
+ work_done++;
+ }
+
+ desc->pnext_rx_hndl = curr_hndl;
+
+ status_mask = readl(priv->iobase1 +
+ MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+ writel(status_mask | MACREG_A2HRIC_BIT_RX_RDY,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+
+ priv->is_rx_schedule = false;
+}
diff --git a/drivers/net/wireless/marvell/mwlwifi/rx.h b/drivers/net/wireless/marvell/mwlwifi/rx.h
new file mode 100644
index 0000000..f19e692
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/rx.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines receive related functions. */
+
+#ifndef _RX_H_
+#define _RX_H_
+
+int mwl_rx_init(struct ieee80211_hw *hw);
+void mwl_rx_deinit(struct ieee80211_hw *hw);
+void mwl_rx_recv(unsigned long data);
+
+#endif /* _RX_H_ */
diff --git a/drivers/net/wireless/marvell/mwlwifi/sysadpt.h b/drivers/net/wireless/marvell/mwlwifi/sysadpt.h
new file mode 100644
index 0000000..8535a43
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/sysadpt.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines system adaptation related information. */
+
+#ifndef _SYSADPT_H_
+#define _SYSADPT_H_
+
+#define SYSADPT_MAX_NUM_CHANNELS 64
+
+#define SYSADPT_MAX_DATA_RATES_G 14
+
+#define SYSADPT_TX_POWER_LEVEL_TOTAL 16
+
+#define SYSADPT_TX_WMM_QUEUES 4
+
+#define SYSADPT_TX_AMPDU_QUEUES 4
+
+#define SYSADPT_NUM_OF_AP 16
+
+#define SYSADPT_TOTAL_TX_QUEUES (SYSADPT_TX_WMM_QUEUES + \
+ SYSADPT_NUM_OF_AP)
+
+#define SYSADPT_TOTAL_HW_QUEUES (SYSADPT_TX_WMM_QUEUES + \
+ SYSADPT_TX_AMPDU_QUEUES)
+
+#define SYSADPT_NUM_OF_DESC_DATA (4 + SYSADPT_NUM_OF_AP)
+
+#define SYSADPT_MAX_NUM_TX_DESC 256
+
+#define SYSADPT_TX_QUEUE_LIMIT (3 * SYSADPT_MAX_NUM_TX_DESC)
+
+#define SYSADPT_TX_WAKE_Q_THRESHOLD (2 * SYSADPT_MAX_NUM_TX_DESC)
+
+#define SYSADPT_DELAY_FREE_Q_LIMIT SYSADPT_MAX_NUM_TX_DESC
+
+#define SYSADPT_MAX_NUM_RX_DESC 256
+
+#define SYSADPT_RECEIVE_LIMIT 64
+
+#define SYSADPT_MAX_AGGR_SIZE 8192
+
+#define SYSADPT_MIN_BYTES_HEADROOM 64
+
+#define SYSADPT_AMPDU_PACKET_THRESHOLD 64
+
+#define SYSADPT_AMSDU_FW_MAX_SIZE 3300
+
+#define SYSADPT_AMSDU_4K_MAX_SIZE SYSADPT_AMSDU_FW_MAX_SIZE
+
+#define SYSADPT_AMSDU_8K_MAX_SIZE SYSADPT_AMSDU_FW_MAX_SIZE
+
+#define SYSADPT_AMSDU_ALLOW_SIZE 1600
+
+#define SYSADPT_AMSDU_FLUSH_TIME 500
+
+#define SYSADPT_AMSDU_PACKET_THRESHOLD 10
+
+#define SYSADPT_MAX_TID 8
+
+#define SYSADPT_QUIET_PERIOD_DEFAULT 100
+
+#define SYSADPT_QUIET_PERIOD_MIN 25
+
+#define SYSADPT_QUIET_START_OFFSET 10
+
+#define SYSADPT_THERMAL_THROTTLE_MAX 100
+
+#define SYSADPT_TIMER_WAKEUP_TIME 300 /* ms */
+
+#endif /* _SYSADPT_H_ */
diff --git a/drivers/net/wireless/marvell/mwlwifi/thermal.c b/drivers/net/wireless/marvell/mwlwifi/thermal.c
new file mode 100644
index 0000000..ada9056
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/thermal.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file implements thermal framework related functions. */
+
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/thermal.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "fwcmd.h"
+#include "thermal.h"
+
+static int
+mwl_thermal_get_max_throttle_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ *state = SYSADPT_THERMAL_THROTTLE_MAX;
+
+ return 0;
+}
+
+static int
+mwl_thermal_get_cur_throttle_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct mwl_priv *priv = cdev->devdata;
+
+ *state = priv->throttle_state;
+
+ return 0;
+}
+
+static int
+mwl_thermal_set_cur_throttle_state(struct thermal_cooling_device *cdev,
+ unsigned long throttle_state)
+{
+ struct mwl_priv *priv = cdev->devdata;
+
+ if (throttle_state > SYSADPT_THERMAL_THROTTLE_MAX) {
+ wiphy_warn(priv->hw->wiphy,
+ "throttle state %ld is exceeding the limit %d\n",
+ throttle_state, SYSADPT_THERMAL_THROTTLE_MAX);
+ return -EINVAL;
+ }
+ priv->throttle_state = throttle_state;
+ mwl_thermal_set_throttling(priv);
+
+ return 0;
+}
+
+static struct thermal_cooling_device_ops mwl_thermal_ops = {
+ .get_max_state = mwl_thermal_get_max_throttle_state,
+ .get_cur_state = mwl_thermal_get_cur_throttle_state,
+ .set_cur_state = mwl_thermal_set_cur_throttle_state,
+};
+
+static ssize_t mwl_thermal_show_temp(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct mwl_priv *priv = dev_get_drvdata(dev);
+ int ret, temperature;
+
+ ret = mwl_fwcmd_get_temp(priv->hw, &priv->temperature);
+ if (ret) {
+ wiphy_warn(priv->hw->wiphy, "failed: can't get temperature\n");
+ goto out;
+ }
+
+ temperature = priv->temperature;
+
+ /* display in millidegree celcius */
+ ret = snprintf(buf, PAGE_SIZE, "%d\n", temperature * 1000);
+out:
+ return ret;
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, 0444, mwl_thermal_show_temp,
+ NULL, 0);
+
+static struct attribute *mwl_hwmon_attrs[] = {
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(mwl_hwmon);
+
+void mwl_thermal_set_throttling(struct mwl_priv *priv)
+{
+ u32 period, duration, enabled;
+ int ret;
+
+ period = priv->quiet_period;
+ duration = (period * priv->throttle_state) / 100;
+ enabled = duration ? 1 : 0;
+
+ ret = mwl_fwcmd_quiet_mode(priv->hw, enabled, period,
+ duration, SYSADPT_QUIET_START_OFFSET);
+ if (ret) {
+ wiphy_warn(priv->hw->wiphy,
+ "failed: period %u duarion %u enabled %u ret %d\n",
+ period, duration, enabled, ret);
+ }
+}
+
+int mwl_thermal_register(struct mwl_priv *priv)
+{
+ struct thermal_cooling_device *cdev;
+ struct device *hwmon_dev;
+ int ret;
+
+ if (priv->chip_type != MWL8897)
+ return 0;
+
+ cdev = thermal_cooling_device_register("mwlwifi_thermal", priv,
+ &mwl_thermal_ops);
+ if (IS_ERR(cdev)) {
+ wiphy_err(priv->hw->wiphy,
+ "failed to setup thermal device result: %ld\n",
+ PTR_ERR(cdev));
+ return -EINVAL;
+ }
+
+ ret = sysfs_create_link(&priv->dev->kobj, &cdev->device.kobj,
+ "cooling_device");
+ if (ret) {
+ wiphy_err(priv->hw->wiphy,
+ "failed to create cooling device symlink\n");
+ goto err_cooling_destroy;
+ }
+
+ priv->cdev = cdev;
+ priv->quiet_period = SYSADPT_QUIET_PERIOD_DEFAULT;
+
+ if (!IS_ENABLED(CONFIG_HWMON))
+ return 0;
+
+ hwmon_dev =
+ devm_hwmon_device_register_with_groups(priv->dev,
+ "mwlwifi_hwmon", priv,
+ mwl_hwmon_groups);
+ if (IS_ERR(hwmon_dev)) {
+ wiphy_err(priv->hw->wiphy,
+ "failed to register hwmon device: %ld\n",
+ PTR_ERR(hwmon_dev));
+ ret = -EINVAL;
+ goto err_remove_link;
+ }
+
+ return 0;
+
+err_remove_link:
+ sysfs_remove_link(&priv->dev->kobj, "cooling_device");
+err_cooling_destroy:
+ thermal_cooling_device_unregister(cdev);
+
+ return ret;
+}
+
+void mwl_thermal_unregister(struct mwl_priv *priv)
+{
+ if (priv->chip_type != MWL8897)
+ return;
+
+ sysfs_remove_link(&priv->dev->kobj, "cooling_device");
+ thermal_cooling_device_unregister(priv->cdev);
+}
diff --git a/drivers/net/wireless/marvell/mwlwifi/thermal.h b/drivers/net/wireless/marvell/mwlwifi/thermal.h
new file mode 100644
index 0000000..67d601c
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/thermal.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines Linux thermal framework related functions. */
+
+#ifndef _THERMAL_H_
+#define _THERMAL_H_
+
+#include <linux/kconfig.h>
+
+#if IS_ENABLED(CONFIG_THERMAL)
+int mwl_thermal_register(struct mwl_priv *priv);
+void mwl_thermal_unregister(struct mwl_priv *priv);
+void mwl_thermal_set_throttling(struct mwl_priv *priv);
+#else
+static inline int mwl_thermal_register(struct mwl_priv *priv)
+{
+ return 0;
+}
+
+static inline void mwl_thermal_unregister(struct mwl_priv *priv)
+{
+}
+
+static inline void mwl_thermal_set_throttling(struct mwl_priv *priv)
+{
+}
+#endif
+
+#endif /* _THERMAL_H_ */
diff --git a/drivers/net/wireless/marvell/mwlwifi/tx.c b/drivers/net/wireless/marvell/mwlwifi/tx.c
new file mode 100644
index 0000000..ee3d58a
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/tx.c
@@ -0,0 +1,1250 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file implements transmit related functions. */
+
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+
+#include "sysadpt.h"
+#include "dev.h"
+#include "fwcmd.h"
+#include "tx.h"
+
+#define MAX_NUM_TX_RING_BYTES (SYSADPT_MAX_NUM_TX_DESC * \
+ sizeof(struct mwl_tx_desc))
+
+#define MAX_NUM_TX_HNDL_BYTES (SYSADPT_MAX_NUM_TX_DESC * \
+ sizeof(struct mwl_tx_hndl))
+
+#define EAGLE_TXD_XMITCTRL_USE_MC_RATE 0x8 /* Use multicast data rate */
+
+#define MWL_QOS_ACK_POLICY_MASK 0x0060
+#define MWL_QOS_ACK_POLICY_NORMAL 0x0000
+#define MWL_QOS_ACK_POLICY_BLOCKACK 0x0060
+
+#define EXT_IV 0x20
+#define INCREASE_IV(iv16, iv32) \
+{ \
+ (iv16)++; \
+ if ((iv16) == 0) \
+ (iv32)++; \
+}
+
+/* Transmit rate information constants */
+#define TX_RATE_FORMAT_LEGACY 0
+#define TX_RATE_FORMAT_11N 1
+#define TX_RATE_FORMAT_11AC 2
+
+#define TX_RATE_BANDWIDTH_20 0
+#define TX_RATE_BANDWIDTH_40 1
+#define TX_RATE_BANDWIDTH_80 2
+
+#define TX_RATE_INFO_STD_GI 0
+#define TX_RATE_INFO_SHORT_GI 1
+
+enum {
+ IEEE_TYPE_MANAGEMENT = 0,
+ IEEE_TYPE_CONTROL,
+ IEEE_TYPE_DATA
+};
+
+struct ccmp_hdr {
+ __le16 iv16;
+ u8 rsvd;
+ u8 key_id;
+ __le32 iv32;
+} __packed;
+
+static int mwl_tx_ring_alloc(struct mwl_priv *priv)
+{
+ struct mwl_desc_data *desc;
+ int num;
+ u8 *mem;
+
+ desc = &priv->desc_data[0];
+
+ mem = dma_alloc_coherent(priv->dev,
+ MAX_NUM_TX_RING_BYTES *
+ SYSADPT_NUM_OF_DESC_DATA,
+ &desc->pphys_tx_ring,
+ GFP_KERNEL);
+
+ if (!mem) {
+ wiphy_err(priv->hw->wiphy, "cannot alloc mem\n");
+ return -ENOMEM;
+ }
+
+ for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) {
+ desc = &priv->desc_data[num];
+
+ desc->ptx_ring = (struct mwl_tx_desc *)
+ (mem + num * MAX_NUM_TX_RING_BYTES);
+
+ desc->pphys_tx_ring = (dma_addr_t)
+ ((u32)priv->desc_data[0].pphys_tx_ring +
+ num * MAX_NUM_TX_RING_BYTES);
+
+ memset(desc->ptx_ring, 0x00,
+ MAX_NUM_TX_RING_BYTES);
+ }
+
+ mem = kmalloc(MAX_NUM_TX_HNDL_BYTES * SYSADPT_NUM_OF_DESC_DATA,
+ GFP_KERNEL);
+
+ if (!mem) {
+ wiphy_err(priv->hw->wiphy, "cannot alloc mem\n");
+ dma_free_coherent(priv->dev,
+ MAX_NUM_TX_RING_BYTES *
+ SYSADPT_NUM_OF_DESC_DATA,
+ priv->desc_data[0].ptx_ring,
+ priv->desc_data[0].pphys_tx_ring);
+ return -ENOMEM;
+ }
+
+ for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) {
+ desc = &priv->desc_data[num];
+
+ desc->tx_hndl = (struct mwl_tx_hndl *)
+ (mem + num * MAX_NUM_TX_HNDL_BYTES);
+
+ memset(desc->tx_hndl, 0x00,
+ MAX_NUM_TX_HNDL_BYTES);
+ }
+
+ return 0;
+}
+
+static int mwl_tx_ring_init(struct mwl_priv *priv)
+{
+ int num, i;
+ struct mwl_desc_data *desc;
+
+ for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) {
+ skb_queue_head_init(&priv->txq[num]);
+ priv->fw_desc_cnt[num] = 0;
+
+ desc = &priv->desc_data[num];
+
+ if (desc->ptx_ring) {
+ for (i = 0; i < SYSADPT_MAX_NUM_TX_DESC; i++) {
+ desc->ptx_ring[i].status =
+ cpu_to_le32(EAGLE_TXD_STATUS_IDLE);
+ desc->ptx_ring[i].pphys_next =
+ cpu_to_le32((u32)desc->pphys_tx_ring +
+ ((i + 1) * sizeof(struct mwl_tx_desc)));
+ desc->tx_hndl[i].pdesc =
+ &desc->ptx_ring[i];
+ if (i < SYSADPT_MAX_NUM_TX_DESC - 1)
+ desc->tx_hndl[i].pnext =
+ &desc->tx_hndl[i + 1];
+ }
+ desc->ptx_ring[SYSADPT_MAX_NUM_TX_DESC - 1].pphys_next =
+ cpu_to_le32((u32)desc->pphys_tx_ring);
+ desc->tx_hndl[SYSADPT_MAX_NUM_TX_DESC - 1].pnext =
+ &desc->tx_hndl[0];
+
+ desc->pstale_tx_hndl = &desc->tx_hndl[0];
+ desc->pnext_tx_hndl = &desc->tx_hndl[0];
+ } else {
+ wiphy_err(priv->hw->wiphy, "no valid TX mem\n");
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static void mwl_tx_ring_cleanup(struct mwl_priv *priv)
+{
+ int cleaned_tx_desc = 0;
+ int num, i;
+ struct mwl_desc_data *desc;
+
+ for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) {
+ skb_queue_purge(&priv->txq[num]);
+ priv->fw_desc_cnt[num] = 0;
+
+ desc = &priv->desc_data[num];
+
+ if (desc->ptx_ring) {
+ for (i = 0; i < SYSADPT_MAX_NUM_TX_DESC; i++) {
+ if (!desc->tx_hndl[i].psk_buff)
+ continue;
+
+ wiphy_info(priv->hw->wiphy,
+ "unmapped and free'd %i 0x%p 0x%x\n",
+ i,
+ desc->tx_hndl[i].psk_buff->data,
+ le32_to_cpu(
+ desc->ptx_ring[i].pkt_ptr));
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu(
+ desc->ptx_ring[i].pkt_ptr),
+ desc->tx_hndl[i].psk_buff->len,
+ PCI_DMA_TODEVICE);
+ dev_kfree_skb_any(desc->tx_hndl[i].psk_buff);
+ desc->ptx_ring[i].status =
+ cpu_to_le32(EAGLE_TXD_STATUS_IDLE);
+ desc->ptx_ring[i].pkt_ptr = 0;
+ desc->ptx_ring[i].pkt_len = 0;
+ desc->tx_hndl[i].psk_buff = NULL;
+ cleaned_tx_desc++;
+ }
+ }
+ }
+
+ wiphy_info(priv->hw->wiphy, "cleaned %i TX descr\n", cleaned_tx_desc);
+}
+
+static void mwl_tx_ring_free(struct mwl_priv *priv)
+{
+ int num;
+
+ if (priv->desc_data[0].ptx_ring) {
+ dma_free_coherent(priv->dev,
+ MAX_NUM_TX_RING_BYTES *
+ SYSADPT_NUM_OF_DESC_DATA,
+ priv->desc_data[0].ptx_ring,
+ priv->desc_data[0].pphys_tx_ring);
+ }
+
+ for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) {
+ if (priv->desc_data[num].ptx_ring)
+ priv->desc_data[num].ptx_ring = NULL;
+ priv->desc_data[num].pstale_tx_hndl = NULL;
+ priv->desc_data[num].pnext_tx_hndl = NULL;
+ }
+
+ kfree(priv->desc_data[0].tx_hndl);
+}
+
+static inline void mwl_tx_add_dma_header(struct mwl_priv *priv,
+ struct sk_buff *skb,
+ int head_pad,
+ int tail_pad)
+{
+ struct ieee80211_hdr *wh;
+ int hdrlen;
+ int reqd_hdrlen;
+ struct mwl_dma_data *tr;
+
+ /* Add a firmware DMA header; the firmware requires that we
+ * present a 2-byte payload length followed by a 4-address
+ * header (without QoS field), followed (optionally) by any
+ * WEP/ExtIV header (but only filled in for CCMP).
+ */
+ wh = (struct ieee80211_hdr *)skb->data;
+
+ hdrlen = ieee80211_hdrlen(wh->frame_control);
+
+ reqd_hdrlen = sizeof(*tr) + head_pad;
+
+ if (hdrlen != reqd_hdrlen)
+ skb_push(skb, reqd_hdrlen - hdrlen);
+
+ if (ieee80211_is_data_qos(wh->frame_control))
+ hdrlen -= IEEE80211_QOS_CTL_LEN;
+
+ tr = (struct mwl_dma_data *)skb->data;
+
+ if (wh != &tr->wh)
+ memmove(&tr->wh, wh, hdrlen);
+
+ if (hdrlen != sizeof(tr->wh))
+ memset(((void *)&tr->wh) + hdrlen, 0, sizeof(tr->wh) - hdrlen);
+
+ /* Firmware length is the length of the fully formed "802.11
+ * payload". That is, everything except for the 802.11 header.
+ * This includes all crypto material including the MIC.
+ */
+ tr->fwlen = cpu_to_le16(skb->len - sizeof(*tr) + tail_pad);
+}
+
+static inline void mwl_tx_encapsulate_frame(struct mwl_priv *priv,
+ struct sk_buff *skb,
+ struct ieee80211_key_conf *k_conf,
+ bool *ccmp)
+{
+ int head_pad = 0;
+ int data_pad = 0;
+
+ /* Make sure the packet header is in the DMA header format (4-address
+ * without QoS), and add head & tail padding when HW crypto is enabled.
+ *
+ * We have the following trailer padding requirements:
+ * - WEP: 4 trailer bytes (ICV)
+ * - TKIP: 12 trailer bytes (8 MIC + 4 ICV)
+ * - CCMP: 8 trailer bytes (MIC)
+ */
+
+ if (k_conf) {
+ head_pad = k_conf->iv_len;
+
+ switch (k_conf->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ data_pad = 4;
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ data_pad = 12;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ data_pad = 8;
+ *ccmp = true;
+ break;
+ }
+ }
+
+ mwl_tx_add_dma_header(priv, skb, head_pad, data_pad);
+}
+
+static inline void mwl_tx_insert_ccmp_hdr(u8 *pccmp_hdr,
+ u8 key_id, u16 iv16, u32 iv32)
+{
+ struct ccmp_hdr *ccmp_h = (struct ccmp_hdr *)pccmp_hdr;
+
+ ccmp_h->iv16 = cpu_to_le16(iv16);
+ ccmp_h->rsvd = 0;
+ ccmp_h->key_id = EXT_IV | (key_id << 6);
+ ccmp_h->iv32 = cpu_to_le32(iv32);
+}
+
+static inline int mwl_tx_tid_queue_mapping(u8 tid)
+{
+ switch (tid) {
+ case 0:
+ case 3:
+ return IEEE80211_AC_BE;
+ case 1:
+ case 2:
+ return IEEE80211_AC_BK;
+ case 4:
+ case 5:
+ return IEEE80211_AC_VI;
+ case 6:
+ case 7:
+ return IEEE80211_AC_VO;
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+static inline void mwl_tx_count_packet(struct ieee80211_sta *sta, u8 tid)
+{
+ struct mwl_sta *sta_info;
+ struct mwl_tx_info *tx_stats;
+
+ if (WARN_ON(tid >= SYSADPT_MAX_TID))
+ return;
+
+ sta_info = mwl_dev_get_sta(sta);
+
+ tx_stats = &sta_info->tx_stats[tid];
+
+ if (tx_stats->start_time == 0)
+ tx_stats->start_time = jiffies;
+
+ /* reset the packet count after each second elapses. If the number of
+ * packets ever exceeds the ampdu_min_traffic threshold, we will allow
+ * an ampdu stream to be started.
+ */
+ if (jiffies - tx_stats->start_time > HZ) {
+ tx_stats->pkts = 0;
+ tx_stats->start_time = jiffies;
+ } else {
+ tx_stats->pkts++;
+ }
+}
+
+static inline bool mwl_tx_available(struct mwl_priv *priv, int desc_num)
+{
+ struct mwl_tx_hndl *tx_hndl;
+
+ tx_hndl = priv->desc_data[desc_num].pnext_tx_hndl;
+
+ if (!tx_hndl->pdesc)
+ return false;
+
+ if (tx_hndl->pdesc->status != EAGLE_TXD_STATUS_IDLE) {
+ /* Interrupt F/W anyway */
+ if (tx_hndl->pdesc->status &
+ cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED))
+ writel(MACREG_H2ARIC_BIT_PPA_READY,
+ priv->iobase1 +
+ MACREG_REG_H2A_INTERRUPT_EVENTS);
+ return false;
+ }
+
+ return true;
+}
+
+static inline void mwl_tx_skb(struct mwl_priv *priv, int desc_num,
+ struct sk_buff *tx_skb)
+{
+ struct ieee80211_tx_info *tx_info;
+ struct mwl_tx_ctrl *tx_ctrl;
+ struct mwl_tx_hndl *tx_hndl;
+ struct mwl_tx_desc *tx_desc;
+ struct ieee80211_sta *sta;
+ struct ieee80211_vif *vif;
+ struct mwl_vif *mwl_vif;
+ struct ieee80211_key_conf *k_conf;
+ bool ccmp = false;
+ struct mwl_dma_data *dma_data;
+ struct ieee80211_hdr *wh;
+ dma_addr_t dma;
+
+ if (WARN_ON(!tx_skb))
+ return;
+
+ tx_info = IEEE80211_SKB_CB(tx_skb);
+ tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+ sta = (struct ieee80211_sta *)tx_ctrl->sta;
+ vif = (struct ieee80211_vif *)tx_ctrl->vif;
+ mwl_vif = mwl_dev_get_vif(vif);
+ k_conf = (struct ieee80211_key_conf *)tx_ctrl->k_conf;
+
+ mwl_tx_encapsulate_frame(priv, tx_skb, k_conf, &ccmp);
+
+ dma_data = (struct mwl_dma_data *)tx_skb->data;
+ wh = &dma_data->wh;
+
+ if (ieee80211_is_data(wh->frame_control) ||
+ (ieee80211_is_mgmt(wh->frame_control) &&
+ ieee80211_has_protected(wh->frame_control) &&
+ !is_multicast_ether_addr(wh->addr1))) {
+ if (is_multicast_ether_addr(wh->addr1)) {
+ if (ccmp) {
+ mwl_tx_insert_ccmp_hdr(dma_data->data,
+ mwl_vif->keyidx,
+ mwl_vif->iv16,
+ mwl_vif->iv32);
+ INCREASE_IV(mwl_vif->iv16, mwl_vif->iv32);
+ }
+ } else {
+ if (ccmp) {
+ if (vif->type == NL80211_IFTYPE_STATION) {
+ mwl_tx_insert_ccmp_hdr(dma_data->data,
+ mwl_vif->keyidx,
+ mwl_vif->iv16,
+ mwl_vif->iv32);
+ INCREASE_IV(mwl_vif->iv16,
+ mwl_vif->iv32);
+ } else {
+ struct mwl_sta *sta_info;
+
+ sta_info = mwl_dev_get_sta(sta);
+
+ mwl_tx_insert_ccmp_hdr(dma_data->data,
+ 0,
+ sta_info->iv16,
+ sta_info->iv32);
+ INCREASE_IV(sta_info->iv16,
+ sta_info->iv32);
+ }
+ }
+ }
+ }
+
+ tx_hndl = priv->desc_data[desc_num].pnext_tx_hndl;
+ tx_hndl->psk_buff = tx_skb;
+ tx_desc = tx_hndl->pdesc;
+ tx_desc->tx_priority = tx_ctrl->tx_priority;
+ tx_desc->qos_ctrl = cpu_to_le16(tx_ctrl->qos_ctrl);
+ tx_desc->pkt_len = cpu_to_le16(tx_skb->len);
+ tx_desc->packet_info = 0;
+ tx_desc->data_rate = 0;
+ tx_desc->type = tx_ctrl->type;
+ tx_desc->xmit_control = tx_ctrl->xmit_control;
+ tx_desc->sap_pkt_info = 0;
+ dma = pci_map_single(priv->pdev, tx_skb->data,
+ tx_skb->len, PCI_DMA_TODEVICE);
+ if (pci_dma_mapping_error(priv->pdev, dma)) {
+ dev_kfree_skb_any(tx_skb);
+ wiphy_err(priv->hw->wiphy,
+ "failed to map pci memory!\n");
+ return;
+ }
+ tx_desc->pkt_ptr = cpu_to_le32(dma);
+ tx_desc->status = cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED);
+ /* make sure all the memory transactions done by cpu were completed */
+ wmb(); /*Data Memory Barrier*/
+ writel(MACREG_H2ARIC_BIT_PPA_READY,
+ priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS);
+ priv->desc_data[desc_num].pnext_tx_hndl = tx_hndl->pnext;
+ priv->fw_desc_cnt[desc_num]++;
+}
+
+static inline struct sk_buff *mwl_tx_do_amsdu(struct mwl_priv *priv,
+ int desc_num,
+ struct sk_buff *tx_skb,
+ struct ieee80211_tx_info *tx_info)
+{
+ struct ieee80211_sta *sta;
+ struct mwl_sta *sta_info;
+ struct mwl_tx_ctrl *tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+ struct ieee80211_tx_info *amsdu_info;
+ struct sk_buff_head *amsdu_pkts;
+ struct mwl_amsdu_frag *amsdu;
+ int amsdu_allow_size;
+ struct ieee80211_hdr *wh;
+ int wh_len;
+ u16 len;
+ u8 *data;
+
+ sta = (struct ieee80211_sta *)tx_ctrl->sta;
+ sta_info = mwl_dev_get_sta(sta);
+
+ if (!sta_info->is_amsdu_allowed)
+ return tx_skb;
+
+ wh = (struct ieee80211_hdr *)tx_skb->data;
+ if (sta_info->is_mesh_node && is_multicast_ether_addr(wh->addr3))
+ return tx_skb;
+
+ if (sta_info->amsdu_ctrl.cap == MWL_AMSDU_SIZE_4K)
+ amsdu_allow_size = SYSADPT_AMSDU_4K_MAX_SIZE;
+ else if (sta_info->amsdu_ctrl.cap == MWL_AMSDU_SIZE_8K)
+ amsdu_allow_size = SYSADPT_AMSDU_8K_MAX_SIZE;
+ else
+ return tx_skb;
+
+ spin_lock_bh(&sta_info->amsdu_lock);
+ amsdu = &sta_info->amsdu_ctrl.frag[desc_num];
+
+ if (tx_skb->len > SYSADPT_AMSDU_ALLOW_SIZE) {
+ if (amsdu->num) {
+ mwl_tx_skb(priv, desc_num, amsdu->skb);
+ amsdu->num = 0;
+ amsdu->cur_pos = NULL;
+ }
+ spin_unlock_bh(&sta_info->amsdu_lock);
+ return tx_skb;
+ }
+
+ /* potential amsdu size, should add amsdu header 14 bytes +
+ * maximum padding 3.
+ */
+ wh_len = ieee80211_hdrlen(wh->frame_control);
+ len = tx_skb->len - wh_len + 17;
+
+ if (amsdu->num) {
+ if ((amsdu->skb->len + len) > amsdu_allow_size) {
+ mwl_tx_skb(priv, desc_num, amsdu->skb);
+ amsdu->num = 0;
+ amsdu->cur_pos = NULL;
+ }
+ }
+
+ amsdu->jiffies = jiffies;
+ len = tx_skb->len - wh_len;
+
+ if (amsdu->num == 0) {
+ struct sk_buff *newskb;
+
+ amsdu_pkts = (struct sk_buff_head *)
+ kmalloc(sizeof(*amsdu_pkts), GFP_ATOMIC);
+ if (!amsdu_pkts) {
+ spin_unlock_bh(&sta_info->amsdu_lock);
+ return tx_skb;
+ }
+ newskb = dev_alloc_skb(amsdu_allow_size +
+ SYSADPT_MIN_BYTES_HEADROOM);
+ if (!newskb) {
+ spin_unlock_bh(&sta_info->amsdu_lock);
+ kfree(amsdu_pkts);
+ return tx_skb;
+ }
+
+ data = newskb->data;
+ memcpy(data, tx_skb->data, wh_len);
+ if (sta_info->is_mesh_node) {
+ ether_addr_copy(data + wh_len, wh->addr3);
+ ether_addr_copy(data + wh_len + ETH_ALEN, wh->addr4);
+ } else {
+ ether_addr_copy(data + wh_len,
+ ieee80211_get_DA(wh));
+ ether_addr_copy(data + wh_len + ETH_ALEN,
+ ieee80211_get_SA(wh));
+ }
+ *(u8 *)(data + wh_len + ETH_HLEN - 1) = len & 0xff;
+ *(u8 *)(data + wh_len + ETH_HLEN - 2) = (len >> 8) & 0xff;
+ memcpy(data + wh_len + ETH_HLEN, tx_skb->data + wh_len, len);
+
+ skb_put(newskb, tx_skb->len + ETH_HLEN);
+ tx_ctrl->qos_ctrl |= IEEE80211_QOS_CTL_A_MSDU_PRESENT;
+ amsdu_info = IEEE80211_SKB_CB(newskb);
+ memcpy(amsdu_info, tx_info, sizeof(*tx_info));
+ skb_queue_head_init(amsdu_pkts);
+ ((struct mwl_tx_ctrl *)&amsdu_info->status)->amsdu_pkts =
+ (void *)amsdu_pkts;
+ amsdu->skb = newskb;
+ } else {
+ amsdu->cur_pos += amsdu->pad;
+ data = amsdu->cur_pos;
+
+ if (sta_info->is_mesh_node) {
+ ether_addr_copy(data, wh->addr3);
+ ether_addr_copy(data + ETH_ALEN, wh->addr4);
+ } else {
+ ether_addr_copy(data, ieee80211_get_DA(wh));
+ ether_addr_copy(data + ETH_ALEN, ieee80211_get_SA(wh));
+ }
+ *(u8 *)(data + ETH_HLEN - 1) = len & 0xff;
+ *(u8 *)(data + ETH_HLEN - 2) = (len >> 8) & 0xff;
+ memcpy(data + ETH_HLEN, tx_skb->data + wh_len, len);
+
+ skb_put(amsdu->skb, len + ETH_HLEN + amsdu->pad);
+ amsdu_info = IEEE80211_SKB_CB(amsdu->skb);
+ amsdu_pkts = (struct sk_buff_head *)
+ ((struct mwl_tx_ctrl *)&amsdu_info->status)->amsdu_pkts;
+ }
+
+ amsdu->num++;
+ amsdu->pad = ((len + ETH_HLEN) % 4) ? (4 - (len + ETH_HLEN) % 4) : 0;
+ amsdu->cur_pos = amsdu->skb->data + amsdu->skb->len;
+ skb_queue_tail(amsdu_pkts, tx_skb);
+
+ if (amsdu->num > SYSADPT_AMSDU_PACKET_THRESHOLD) {
+ amsdu->num = 0;
+ amsdu->cur_pos = NULL;
+ spin_unlock_bh(&sta_info->amsdu_lock);
+ return amsdu->skb;
+ }
+
+ spin_unlock_bh(&sta_info->amsdu_lock);
+ return NULL;
+}
+
+static inline void mwl_tx_prepare_info(struct ieee80211_hw *hw, u32 rate,
+ struct ieee80211_tx_info *info)
+{
+ u32 format, bandwidth, short_gi, rate_id;
+
+ ieee80211_tx_info_clear_status(info);
+
+ info->status.rates[0].idx = -1;
+ info->status.rates[0].count = 0;
+ info->status.rates[0].flags = 0;
+
+ if (rate) {
+ /* Prepare rate information */
+ format = rate & MWL_TX_RATE_FORMAT_MASK;
+ bandwidth =
+ (rate & MWL_TX_RATE_BANDWIDTH_MASK) >>
+ MWL_TX_RATE_BANDWIDTH_SHIFT;
+ short_gi = (rate & MWL_TX_RATE_SHORTGI_MASK) >>
+ MWL_TX_RATE_SHORTGI_SHIFT;
+ rate_id = (rate & MWL_TX_RATE_RATEIDMCS_MASK) >>
+ MWL_TX_RATE_RATEIDMCS_SHIFT;
+
+ info->status.rates[0].idx = rate_id;
+ if (format == TX_RATE_FORMAT_LEGACY) {
+ if (hw->conf.chandef.chan->hw_value >
+ BAND_24_CHANNEL_NUM) {
+ info->status.rates[0].idx -= 5;
+ }
+ }
+ if (format == TX_RATE_FORMAT_11N)
+ info->status.rates[0].flags |=
+ IEEE80211_TX_RC_MCS;
+ if (format == TX_RATE_FORMAT_11AC)
+ info->status.rates[0].flags |=
+ IEEE80211_TX_RC_VHT_MCS;
+ if (bandwidth == TX_RATE_BANDWIDTH_40)
+ info->status.rates[0].flags |=
+ IEEE80211_TX_RC_40_MHZ_WIDTH;
+ if (bandwidth == TX_RATE_BANDWIDTH_80)
+ info->status.rates[0].flags |=
+ IEEE80211_TX_RC_80_MHZ_WIDTH;
+ if (short_gi == TX_RATE_INFO_SHORT_GI)
+ info->status.rates[0].flags |=
+ IEEE80211_TX_RC_SHORT_GI;
+ info->status.rates[0].count = 1;
+ info->status.rates[1].idx = -1;
+ }
+}
+
+static inline void mwl_tx_ack_amsdu_pkts(struct ieee80211_hw *hw, u32 rate,
+ struct sk_buff_head *amsdu_pkts)
+{
+ struct sk_buff *amsdu_pkt;
+ struct ieee80211_tx_info *info;
+
+ while (skb_queue_len(amsdu_pkts) > 0) {
+ amsdu_pkt = skb_dequeue(amsdu_pkts);
+ info = IEEE80211_SKB_CB(amsdu_pkt);
+ mwl_tx_prepare_info(hw, rate, info);
+ info->flags &= ~IEEE80211_TX_CTL_AMPDU;
+ info->flags |= IEEE80211_TX_STAT_ACK;
+ ieee80211_tx_status(hw, amsdu_pkt);
+ }
+
+ kfree(amsdu_pkts);
+}
+
+int mwl_tx_init(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+ int rc;
+
+ skb_queue_head_init(&priv->delay_q);
+
+ rc = mwl_tx_ring_alloc(priv);
+ if (rc) {
+ wiphy_err(hw->wiphy, "allocating TX ring failed\n");
+ return rc;
+ }
+
+ rc = mwl_tx_ring_init(priv);
+ if (rc) {
+ mwl_tx_ring_free(priv);
+ wiphy_err(hw->wiphy, "initializing TX ring failed\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+void mwl_tx_deinit(struct ieee80211_hw *hw)
+{
+ struct mwl_priv *priv = hw->priv;
+
+ skb_queue_purge(&priv->delay_q);
+
+ mwl_tx_ring_cleanup(priv);
+ mwl_tx_ring_free(priv);
+}
+
+void mwl_tx_xmit(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
+{
+ struct mwl_priv *priv = hw->priv;
+ int index;
+ struct ieee80211_sta *sta;
+ struct ieee80211_tx_info *tx_info;
+ struct mwl_vif *mwl_vif;
+ struct ieee80211_hdr *wh;
+ u8 xmitcontrol;
+ u16 qos;
+ int txpriority;
+ u8 tid = 0;
+ struct mwl_ampdu_stream *stream = NULL;
+ bool start_ba_session = false;
+ bool mgmtframe = false;
+ struct ieee80211_mgmt *mgmt;
+ bool eapol_frame = false;
+ struct mwl_tx_ctrl *tx_ctrl;
+ struct ieee80211_key_conf *k_conf = NULL;
+
+ index = skb_get_queue_mapping(skb);
+ sta = control->sta;
+
+ wh = (struct ieee80211_hdr *)skb->data;
+
+ if (ieee80211_is_data_qos(wh->frame_control))
+ qos = *((u16 *)ieee80211_get_qos_ctl(wh));
+ else
+ qos = 0;
+
+ if (skb->protocol == cpu_to_be16(ETH_P_PAE)) {
+ index = IEEE80211_AC_VO;
+ eapol_frame = true;
+ }
+
+ if (ieee80211_is_mgmt(wh->frame_control)) {
+ mgmtframe = true;
+ mgmt = (struct ieee80211_mgmt *)skb->data;
+ }
+
+ tx_info = IEEE80211_SKB_CB(skb);
+ mwl_vif = mwl_dev_get_vif(tx_info->control.vif);
+
+ if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) {
+ wh->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG);
+ wh->seq_ctrl |= cpu_to_le16(mwl_vif->seqno);
+ mwl_vif->seqno += 0x10;
+ }
+
+ /* Setup firmware control bit fields for each frame type. */
+ xmitcontrol = 0;
+
+ if (mgmtframe || ieee80211_is_ctl(wh->frame_control)) {
+ qos = 0;
+ } else if (ieee80211_is_data(wh->frame_control)) {
+ qos &= ~MWL_QOS_ACK_POLICY_MASK;
+
+ if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) {
+ xmitcontrol &= 0xfb;
+ qos |= MWL_QOS_ACK_POLICY_BLOCKACK;
+ } else {
+ xmitcontrol |= 0x4;
+ qos |= MWL_QOS_ACK_POLICY_NORMAL;
+ }
+
+ if (is_multicast_ether_addr(wh->addr1))
+ xmitcontrol |= EAGLE_TXD_XMITCTRL_USE_MC_RATE;
+ }
+
+ k_conf = tx_info->control.hw_key;
+
+ /* Queue ADDBA request in the respective data queue. While setting up
+ * the ampdu stream, mac80211 queues further packets for that
+ * particular ra/tid pair. However, packets piled up in the hardware
+ * for that ra/tid pair will still go out. ADDBA request and the
+ * related data packets going out from different queues asynchronously
+ * will cause a shift in the receiver window which might result in
+ * ampdu packets getting dropped at the receiver after the stream has
+ * been setup.
+ */
+ if (mgmtframe) {
+ u16 capab;
+
+ if (unlikely(ieee80211_is_action(wh->frame_control) &&
+ mgmt->u.action.category == WLAN_CATEGORY_BACK &&
+ mgmt->u.action.u.addba_req.action_code ==
+ WLAN_ACTION_ADDBA_REQ)) {
+ capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab);
+ tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2;
+ index = mwl_tx_tid_queue_mapping(tid);
+ }
+ }
+
+ index = SYSADPT_TX_WMM_QUEUES - index - 1;
+ txpriority = index;
+
+ if (sta && sta->ht_cap.ht_supported && !eapol_frame &&
+ ieee80211_is_data_qos(wh->frame_control)) {
+ tid = qos & 0xf;
+ mwl_tx_count_packet(sta, tid);
+
+ spin_lock_bh(&priv->stream_lock);
+ stream = mwl_fwcmd_lookup_stream(hw, sta->addr, tid);
+
+ if (stream) {
+ if (stream->state == AMPDU_STREAM_ACTIVE) {
+ if (WARN_ON(!(qos &
+ MWL_QOS_ACK_POLICY_BLOCKACK))) {
+ spin_unlock_bh(&priv->stream_lock);
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ txpriority =
+ (SYSADPT_TX_WMM_QUEUES + stream->idx) %
+ SYSADPT_TOTAL_HW_QUEUES;
+ } else if (stream->state == AMPDU_STREAM_NEW) {
+ /* We get here if the driver sends us packets
+ * after we've initiated a stream, but before
+ * our ampdu_action routine has been called
+ * with IEEE80211_AMPDU_TX_START to get the SSN
+ * for the ADDBA request. So this packet can
+ * go out with no risk of sequence number
+ * mismatch. No special handling is required.
+ */
+ } else {
+ /* Drop packets that would go out after the
+ * ADDBA request was sent but before the ADDBA
+ * response is received. If we don't do this,
+ * the recipient would probably receive it
+ * after the ADDBA request with SSN 0. This
+ * will cause the recipient's BA receive window
+ * to shift, which would cause the subsequent
+ * packets in the BA stream to be discarded.
+ * mac80211 queues our packets for us in this
+ * case, so this is really just a safety check.
+ */
+ wiphy_warn(hw->wiphy,
+ "can't send packet during ADDBA\n");
+ spin_unlock_bh(&priv->stream_lock);
+ dev_kfree_skb_any(skb);
+ return;
+ }
+ } else {
+ if (mwl_fwcmd_ampdu_allowed(sta, tid)) {
+ stream = mwl_fwcmd_add_stream(hw, sta, tid);
+
+ if (stream)
+ start_ba_session = true;
+ }
+ }
+
+ spin_unlock_bh(&priv->stream_lock);
+ } else {
+ qos &= ~MWL_QOS_ACK_POLICY_MASK;
+ qos |= MWL_QOS_ACK_POLICY_NORMAL;
+ }
+
+ tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+ tx_ctrl->vif = (void *)tx_info->control.vif;
+ tx_ctrl->sta = (void *)sta;
+ tx_ctrl->k_conf = (void *)k_conf;
+ tx_ctrl->amsdu_pkts = NULL;
+ tx_ctrl->tx_priority = txpriority;
+ tx_ctrl->type = (mgmtframe ? IEEE_TYPE_MANAGEMENT : IEEE_TYPE_DATA);
+ tx_ctrl->qos_ctrl = qos;
+ tx_ctrl->xmit_control = xmitcontrol;
+
+ if (skb_queue_len(&priv->txq[index]) > priv->txq_limit)
+ ieee80211_stop_queue(hw, SYSADPT_TX_WMM_QUEUES - index - 1);
+
+ skb_queue_tail(&priv->txq[index], skb);
+
+ tasklet_schedule(&priv->tx_task);
+
+ /* Initiate the ampdu session here */
+ if (start_ba_session) {
+ spin_lock_bh(&priv->stream_lock);
+ if (mwl_fwcmd_start_stream(hw, stream))
+ mwl_fwcmd_remove_stream(hw, stream);
+ spin_unlock_bh(&priv->stream_lock);
+ }
+}
+
+void mwl_tx_del_pkts_via_vif(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct mwl_priv *priv = hw->priv;
+ int num;
+ struct sk_buff *skb, *tmp;
+ struct ieee80211_tx_info *tx_info;
+ struct mwl_tx_ctrl *tx_ctrl;
+ struct sk_buff_head *amsdu_pkts;
+
+ for (num = 1; num < SYSADPT_NUM_OF_DESC_DATA; num++) {
+ spin_lock_bh(&priv->txq[num].lock);
+ skb_queue_walk_safe(&priv->txq[num], skb, tmp) {
+ tx_info = IEEE80211_SKB_CB(skb);
+ tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+ if (tx_ctrl->vif == vif) {
+ amsdu_pkts = (struct sk_buff_head *)
+ tx_ctrl->amsdu_pkts;
+ if (amsdu_pkts) {
+ skb_queue_purge(amsdu_pkts);
+ kfree(amsdu_pkts);
+ }
+ __skb_unlink(skb, &priv->txq[num]);
+ dev_kfree_skb_any(skb);
+ }
+ }
+ spin_unlock_bh(&priv->txq[num].lock);
+ }
+}
+
+void mwl_tx_del_pkts_via_sta(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta)
+{
+ struct mwl_priv *priv = hw->priv;
+ int num;
+ struct sk_buff *skb, *tmp;
+ struct ieee80211_tx_info *tx_info;
+ struct mwl_tx_ctrl *tx_ctrl;
+ struct sk_buff_head *amsdu_pkts;
+
+ for (num = 1; num < SYSADPT_NUM_OF_DESC_DATA; num++) {
+ spin_lock_bh(&priv->txq[num].lock);
+ skb_queue_walk_safe(&priv->txq[num], skb, tmp) {
+ tx_info = IEEE80211_SKB_CB(skb);
+ tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+ if (tx_ctrl->sta == sta) {
+ amsdu_pkts = (struct sk_buff_head *)
+ tx_ctrl->amsdu_pkts;
+ if (amsdu_pkts) {
+ skb_queue_purge(amsdu_pkts);
+ kfree(amsdu_pkts);
+ }
+ __skb_unlink(skb, &priv->txq[num]);
+ dev_kfree_skb_any(skb);
+ }
+ }
+ spin_unlock_bh(&priv->txq[num].lock);
+ }
+}
+
+void mwl_tx_del_ampdu_pkts(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta, u8 tid)
+{
+ struct mwl_priv *priv = hw->priv;
+ struct mwl_sta *sta_info = mwl_dev_get_sta(sta);
+ int ac, desc_num;
+ struct mwl_amsdu_frag *amsdu_frag;
+ struct sk_buff *skb, *tmp;
+ struct ieee80211_tx_info *tx_info;
+ struct mwl_tx_ctrl *tx_ctrl;
+ struct sk_buff_head *amsdu_pkts;
+
+ ac = mwl_tx_tid_queue_mapping(tid);
+ desc_num = SYSADPT_TX_WMM_QUEUES - ac - 1;
+ spin_lock_bh(&priv->txq[desc_num].lock);
+ skb_queue_walk_safe(&priv->txq[desc_num], skb, tmp) {
+ tx_info = IEEE80211_SKB_CB(skb);
+ tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+ if (tx_ctrl->sta == sta) {
+ amsdu_pkts = (struct sk_buff_head *)
+ tx_ctrl->amsdu_pkts;
+ if (amsdu_pkts) {
+ skb_queue_purge(amsdu_pkts);
+ kfree(amsdu_pkts);
+ }
+ __skb_unlink(skb, &priv->txq[desc_num]);
+ dev_kfree_skb_any(skb);
+ }
+ }
+ spin_unlock_bh(&priv->txq[desc_num].lock);
+
+ spin_lock_bh(&sta_info->amsdu_lock);
+ amsdu_frag = &sta_info->amsdu_ctrl.frag[desc_num];
+ if (amsdu_frag->num) {
+ amsdu_frag->num = 0;
+ amsdu_frag->cur_pos = NULL;
+ if (amsdu_frag->skb) {
+ tx_info = IEEE80211_SKB_CB(amsdu_frag->skb);
+ tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+ amsdu_pkts = (struct sk_buff_head *)
+ tx_ctrl->amsdu_pkts;
+ if (amsdu_pkts) {
+ skb_queue_purge(amsdu_pkts);
+ kfree(amsdu_pkts);
+ }
+ dev_kfree_skb_any(amsdu_frag->skb);
+ }
+ }
+ spin_unlock_bh(&sta_info->amsdu_lock);
+}
+
+void mwl_tx_skbs(unsigned long data)
+{
+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data;
+ struct mwl_priv *priv = hw->priv;
+ int num = SYSADPT_TX_WMM_QUEUES;
+ struct sk_buff *tx_skb;
+
+ spin_lock_bh(&priv->tx_desc_lock);
+ while (num--) {
+ while (skb_queue_len(&priv->txq[num]) > 0) {
+ struct ieee80211_tx_info *tx_info;
+ struct mwl_tx_ctrl *tx_ctrl;
+
+ if (!mwl_tx_available(priv, num))
+ break;
+
+ tx_skb = skb_dequeue(&priv->txq[num]);
+ tx_info = IEEE80211_SKB_CB(tx_skb);
+ tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status;
+
+ if ((tx_skb->protocol != cpu_to_be16(ETH_P_PAE)) &&
+ (tx_ctrl->tx_priority >= SYSADPT_TX_WMM_QUEUES)) {
+ tx_skb = mwl_tx_do_amsdu(priv, num,
+ tx_skb, tx_info);
+ }
+
+ if (tx_skb) {
+ if (mwl_tx_available(priv, num))
+ mwl_tx_skb(priv, num, tx_skb);
+ else
+ skb_queue_head(&priv->txq[num], tx_skb);
+ }
+ }
+
+ if (skb_queue_len(&priv->txq[num]) <
+ SYSADPT_TX_WAKE_Q_THRESHOLD) {
+ int queue;
+
+ queue = SYSADPT_TX_WMM_QUEUES - num - 1;
+ if (ieee80211_queue_stopped(hw, queue))
+ ieee80211_wake_queue(hw, queue);
+ }
+ }
+ spin_unlock_bh(&priv->tx_desc_lock);
+}
+
+void mwl_tx_done(unsigned long data)
+{
+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data;
+ struct mwl_priv *priv = hw->priv;
+ int num;
+ struct mwl_desc_data *desc;
+ struct mwl_tx_hndl *tx_hndl;
+ struct mwl_tx_desc *tx_desc;
+ struct sk_buff *done_skb;
+ u32 rate;
+ struct mwl_dma_data *tr;
+ struct ieee80211_tx_info *info;
+ struct mwl_tx_ctrl *tx_ctrl;
+ struct sk_buff_head *amsdu_pkts;
+ int hdrlen;
+
+ spin_lock_bh(&priv->tx_desc_lock);
+ for (num = 0; num < SYSADPT_TX_WMM_QUEUES; num++) {
+ desc = &priv->desc_data[num];
+ tx_hndl = desc->pstale_tx_hndl;
+ tx_desc = tx_hndl->pdesc;
+
+ if ((tx_desc->status &
+ cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)) &&
+ (tx_hndl->pnext->pdesc->status &
+ cpu_to_le32(EAGLE_TXD_STATUS_OK)))
+ tx_desc->status = cpu_to_le32(EAGLE_TXD_STATUS_OK);
+
+ while (tx_hndl &&
+ (tx_desc->status & cpu_to_le32(EAGLE_TXD_STATUS_OK)) &&
+ (!(tx_desc->status &
+ cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)))) {
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu(tx_desc->pkt_ptr),
+ le16_to_cpu(tx_desc->pkt_len),
+ PCI_DMA_TODEVICE);
+ done_skb = tx_hndl->psk_buff;
+ rate = le32_to_cpu(tx_desc->rate_info);
+ tx_desc->pkt_ptr = 0;
+ tx_desc->pkt_len = 0;
+ tx_desc->status =
+ cpu_to_le32(EAGLE_TXD_STATUS_IDLE);
+ tx_hndl->psk_buff = NULL;
+ wmb(); /* memory barrier */
+
+ skb_get(done_skb);
+ skb_queue_tail(&priv->delay_q, done_skb);
+ if (skb_queue_len(&priv->delay_q) >
+ SYSADPT_DELAY_FREE_Q_LIMIT)
+ dev_kfree_skb_any(skb_dequeue(&priv->delay_q));
+
+ tr = (struct mwl_dma_data *)done_skb->data;
+ info = IEEE80211_SKB_CB(done_skb);
+
+ if (ieee80211_is_data(tr->wh.frame_control) ||
+ ieee80211_is_data_qos(tr->wh.frame_control)) {
+ tx_ctrl = (struct mwl_tx_ctrl *)&info->status;
+ amsdu_pkts = (struct sk_buff_head *)
+ tx_ctrl->amsdu_pkts;
+ if (amsdu_pkts) {
+ mwl_tx_ack_amsdu_pkts(hw, rate,
+ amsdu_pkts);
+ dev_kfree_skb_any(done_skb);
+ done_skb = NULL;
+ } else {
+ mwl_tx_prepare_info(hw, rate, info);
+ }
+ } else {
+ mwl_tx_prepare_info(hw, 0, info);
+ }
+
+ if (done_skb) {
+ /* Remove H/W dma header */
+ hdrlen = ieee80211_hdrlen(tr->wh.frame_control);
+ memmove(tr->data - hdrlen, &tr->wh, hdrlen);
+ skb_pull(done_skb, sizeof(*tr) - hdrlen);
+ info->flags &= ~IEEE80211_TX_CTL_AMPDU;
+ info->flags |= IEEE80211_TX_STAT_ACK;
+ ieee80211_tx_status(hw, done_skb);
+ }
+
+ tx_hndl = tx_hndl->pnext;
+ tx_desc = tx_hndl->pdesc;
+ priv->fw_desc_cnt[num]--;
+ }
+
+ desc->pstale_tx_hndl = tx_hndl;
+ }
+ spin_unlock_bh(&priv->tx_desc_lock);
+
+ if (priv->is_tx_done_schedule) {
+ u32 status_mask;
+
+ status_mask = readl(priv->iobase1 +
+ MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+ writel(status_mask | MACREG_A2HRIC_BIT_TX_DONE,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+
+ tasklet_schedule(&priv->tx_task);
+ priv->is_tx_done_schedule = false;
+ }
+}
+
+void mwl_tx_flush_amsdu(unsigned long data)
+{
+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data;
+ struct mwl_priv *priv = hw->priv;
+ u32 status_mask;
+ struct mwl_sta *sta_info;
+ int i;
+ struct mwl_amsdu_frag *amsdu_frag;
+
+ spin_lock(&priv->sta_lock);
+ list_for_each_entry(sta_info, &priv->sta_list, list) {
+ spin_lock(&priv->tx_desc_lock);
+ spin_lock(&sta_info->amsdu_lock);
+ for (i = 0; i < SYSADPT_TX_WMM_QUEUES; i++) {
+ amsdu_frag = &sta_info->amsdu_ctrl.frag[i];
+ if (amsdu_frag->num) {
+ if (time_after(jiffies,
+ (amsdu_frag->jiffies + 1))) {
+ if (mwl_tx_available(priv, i)) {
+ mwl_tx_skb(priv, i,
+ amsdu_frag->skb);
+ amsdu_frag->num = 0;
+ amsdu_frag->cur_pos = NULL;
+ }
+ }
+ }
+ }
+ spin_unlock(&sta_info->amsdu_lock);
+ spin_unlock(&priv->tx_desc_lock);
+ }
+ spin_unlock(&priv->sta_lock);
+
+ status_mask = readl(priv->iobase1 +
+ MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+ writel(status_mask | MACREG_A2HRIC_BIT_QUE_EMPTY,
+ priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK);
+
+ priv->is_qe_schedule = false;
+}
+
+void mwl_tx_del_sta_amsdu_pkts(struct ieee80211_sta *sta)
+{
+ struct mwl_sta *sta_info = mwl_dev_get_sta(sta);
+ int num;
+ struct mwl_amsdu_frag *amsdu_frag;
+ struct ieee80211_tx_info *tx_info;
+ struct mwl_tx_ctrl *tx_ctrl;
+ struct sk_buff_head *amsdu_pkts;
+
+ spin_lock_bh(&sta_info->amsdu_lock);
+ for (num = 0; num < SYSADPT_TX_WMM_QUEUES; num++) {
+ amsdu_frag = &sta_info->amsdu_ctrl.frag[num];
+ if (amsdu_frag->num) {
+ amsdu_frag->num = 0;
+ amsdu_frag->cur_pos = NULL;
+ if (amsdu_frag->skb) {
+ tx_info = IEEE80211_SKB_CB(amsdu_frag->skb);
+ tx_ctrl = (struct mwl_tx_ctrl *)
+ &tx_info->status;
+ amsdu_pkts = (struct sk_buff_head *)
+ tx_ctrl->amsdu_pkts;
+ if (amsdu_pkts) {
+ skb_queue_purge(amsdu_pkts);
+ kfree(amsdu_pkts);
+ }
+ dev_kfree_skb_any(amsdu_frag->skb);
+ }
+ }
+ }
+ spin_unlock_bh(&sta_info->amsdu_lock);
+}
diff --git a/drivers/net/wireless/marvell/mwlwifi/tx.h b/drivers/net/wireless/marvell/mwlwifi/tx.h
new file mode 100644
index 0000000..eaacc3b
--- /dev/null
+++ b/drivers/net/wireless/marvell/mwlwifi/tx.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2006-2016, Marvell International Ltd.
+ *
+ * This software file (the "File") is distributed by Marvell International
+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991
+ * (the "License"). You may use, redistribute and/or modify this File in
+ * accordance with the terms and conditions of the License, a copy of which
+ * is available by writing to the Free Software Foundation, Inc.
+ *
+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about
+ * this warranty disclaimer.
+ */
+
+/* Description: This file defines transmit related functions. */
+
+#ifndef _TX_H_
+#define _TX_H_
+
+int mwl_tx_init(struct ieee80211_hw *hw);
+void mwl_tx_deinit(struct ieee80211_hw *hw);
+void mwl_tx_xmit(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb);
+void mwl_tx_del_pkts_via_vif(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif);
+void mwl_tx_del_pkts_via_sta(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta);
+void mwl_tx_del_ampdu_pkts(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta, u8 tid);
+void mwl_tx_skbs(unsigned long data);
+void mwl_tx_done(unsigned long data);
+void mwl_tx_flush_amsdu(unsigned long data);
+void mwl_tx_del_sta_amsdu_pkts(struct ieee80211_sta *sta);
+
+#endif /* _TX_H_ */
--
1.9.3
^ permalink raw reply related
* [PATCH v9] Add new mac80211 driver mwlwifi.
From: David Lin @ 2016-12-21 4:11 UTC (permalink / raw)
To: Kalle Valo
Cc: linux-wireless@vger.kernel.org, Johannes Berg, Chor Teck Law,
James Lin, Pete Hsieh
PATCH v8 changes since PATCH v7:
- Used scnprintf() to replace sprintf() for debugfs output messages to avoid
overwriting buffer boundary.
- Used mutex to replace spinlock for the protection of firmware command.
- Used NL80211_BAND_XXXX instead of IEEE80211_BAND_XXXX (in order to work with
updated mac80211).
- Used usleep_range() instead of mdelay().
- Modified the code to work with new mac80211 API ampdu_action() and get peer
AMSDU information from parameters of this function instead of peeking ADDBA
related packets.
- Removed BA stream if traffic is not heavy.
- Removed version information.
- Added DFS, WPS, WDS and thermal function.
- Changed length of mac vht_mpdu from 7991 to 3895.
PATCH v9 changes since PATCH v8:
- Added code to support Marvell WiFi chip with device power table.
- Used IS_ENABLED macro to test for Kconfig symbols.
David Lin (1):
Add new mac80211 driver mwlwifi.
MAINTAINERS | 6 +
drivers/net/wireless/marvell/Kconfig | 1 +
drivers/net/wireless/marvell/Makefile | 1 +
drivers/net/wireless/marvell/mwlwifi/Kconfig | 23 +
drivers/net/wireless/marvell/mwlwifi/Makefile | 13 +
drivers/net/wireless/marvell/mwlwifi/debugfs.c | 830 +++++++
drivers/net/wireless/marvell/mwlwifi/debugfs.h | 24 +
drivers/net/wireless/marvell/mwlwifi/dev.h | 516 +++++
drivers/net/wireless/marvell/mwlwifi/fwcmd.c | 2837 +++++++++++++++++++++++
drivers/net/wireless/marvell/mwlwifi/fwcmd.h | 223 ++
drivers/net/wireless/marvell/mwlwifi/fwdl.c | 186 ++
drivers/net/wireless/marvell/mwlwifi/fwdl.h | 25 +
drivers/net/wireless/marvell/mwlwifi/hostcmd.h | 913 ++++++++
drivers/net/wireless/marvell/mwlwifi/isr.c | 172 ++
drivers/net/wireless/marvell/mwlwifi/isr.h | 27 +
drivers/net/wireless/marvell/mwlwifi/mac80211.c | 719 ++++++
drivers/net/wireless/marvell/mwlwifi/main.c | 840 +++++++
drivers/net/wireless/marvell/mwlwifi/rx.c | 513 ++++
drivers/net/wireless/marvell/mwlwifi/rx.h | 25 +
drivers/net/wireless/marvell/mwlwifi/sysadpt.h | 83 +
drivers/net/wireless/marvell/mwlwifi/thermal.c | 182 ++
drivers/net/wireless/marvell/mwlwifi/thermal.h | 42 +
drivers/net/wireless/marvell/mwlwifi/tx.c | 1250 ++++++++++
drivers/net/wireless/marvell/mwlwifi/tx.h | 37 +
24 files changed, 9488 insertions(+)
create mode 100644 drivers/net/wireless/marvell/mwlwifi/Kconfig
create mode 100644 drivers/net/wireless/marvell/mwlwifi/Makefile
create mode 100644 drivers/net/wireless/marvell/mwlwifi/debugfs.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/debugfs.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/dev.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/fwcmd.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/fwcmd.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/fwdl.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/fwdl.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/hostcmd.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/isr.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/isr.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/mac80211.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/main.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/rx.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/rx.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/sysadpt.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/thermal.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/thermal.h
create mode 100644 drivers/net/wireless/marvell/mwlwifi/tx.c
create mode 100644 drivers/net/wireless/marvell/mwlwifi/tx.h
--
1.9.3
^ permalink raw reply
* Re: [PATCH 2/3] NFC: trf7970a: Add device tree option of 1.8 Volt IO voltage
From: Mark Greer @ 2016-12-21 2:23 UTC (permalink / raw)
To: Geoff Lansberry
Cc: linux-wireless, lauro.venancio, aloisio.almeida, sameo, robh+dt,
mark.rutland, netdev, devicetree, linux-kernel, justin
In-Reply-To: <1482250592-4268-2-git-send-email-glansberry@gmail.com>
On Tue, Dec 20, 2016 at 11:16:31AM -0500, Geoff Lansberry wrote:
> From: Geoff Lansberry <geoff@kuvee.com>
>
> The TRF7970A has configuration options for supporting hardware designs
> with 1.8 Volt or 3.3 Volt IO. This commit adds a device tree option,
> using a fixed regulator binding, for setting the io voltage to match
> the hardware configuration. If no option is supplied it defaults to
> 3.3 volt configuration.
Sign-off ?? Same comment for you other patches.
<time passes>
Okay I see you have it at the end of the patch. It should be here.
'git commit -s' is your friend.
> ---
> .../devicetree/bindings/net/nfc/trf7970a.txt | 4 ++--
> drivers/nfc/trf7970a.c | 28 +++++++++++++++++++++-
> 2 files changed, 29 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/net/nfc/trf7970a.txt b/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
> index e262ac1..b5777d8 100644
> --- a/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
> +++ b/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
> @@ -21,9 +21,9 @@ Optional SoC Specific Properties:
> - t5t-rmb-extra-byte-quirk: Specify that the trf7970a has the erratum
> where an extra byte is returned by Read Multiple Block commands issued
> to Type 5 tags.
> +- vdd-io-supply: Regulator specifying voltage for vdd-io
> - clock-frequency: Set to specify that the input frequency to the trf7970a is 13560000Hz or 27120000Hz
>
> -
> Example (for ARM-based BeagleBone with TRF7970A on SPI1):
>
> &spi1 {
> @@ -41,11 +41,11 @@ Example (for ARM-based BeagleBone with TRF7970A on SPI1):
> <&gpio2 5 GPIO_ACTIVE_LOW>;
> vin-supply = <&ldo3_reg>;
> vin-voltage-override = <5000000>;
> + vdd-io-supply = <&ldo2_reg>;
> autosuspend-delay = <30000>;
> irq-status-read-quirk;
> en2-rf-quirk;
> t5t-rmb-extra-byte-quirk;
> - vdd_io_1v8;
It was already mentioned but this shouldn't have been added in the
previous patch so it shouldn't be here now.
> clock-frequency = <27120000>;
> status = "okay";
> };
> diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c
> index 4e051e9..8a88195 100644
> --- a/drivers/nfc/trf7970a.c
> +++ b/drivers/nfc/trf7970a.c
> @@ -2062,6 +2068,7 @@ static int trf7970a_probe(struct spi_device *spi)
> return ret;
> }
>
> +
Please don't add an extra blank line.
> of_property_read_u32(np, "clock-frequency", &clk_freq);
> if ((clk_freq != TRF7970A_27MHZ_CLOCK_FREQUENCY) ||
> (clk_freq != TRF7970A_27MHZ_CLOCK_FREQUENCY)) {
> @@ -2105,6 +2112,25 @@ static int trf7970a_probe(struct spi_device *spi)
> if (uvolts > 4000000)
> trf->chip_status_ctrl = TRF7970A_CHIP_STATUS_VRS5_3;
>
> + trf->regulator = devm_regulator_get(&spi->dev, "vdd-io");
> + if (IS_ERR(trf->regulator)) {
> + ret = PTR_ERR(trf->regulator);
> + dev_err(trf->dev, "Can't get VDD_IO regulator: %d\n", ret);
> + goto err_destroy_lock;
> + }
> +
> + ret = regulator_enable(trf->regulator);
> + if (ret) {
> + dev_err(trf->dev, "Can't enable VDD_IO: %d\n", ret);
> + goto err_destroy_lock;
> + }
> +
> +
Please don't add an extra blank line.
> + if (regulator_get_voltage(trf->regulator) == 1800000) {
> + trf->io_ctrl = TRF7970A_REG_IO_CTRL_IO_LOW;
> + dev_dbg(trf->dev, "trf7970a config vdd_io to 1.8V\n");
> + }
> +
> trf->ddev = nfc_digital_allocate_device(&trf7970a_nfc_ops,
> TRF7970A_SUPPORTED_PROTOCOLS,
> NFC_DIGITAL_DRV_CAPS_IN_CRC |
> --
> Signed-off-by: Geoff Lansberry <geoff@kuvee.com>
Your 'Signed-off-by:' goes at the end of the commit description not here.
Overall, I think you did the right thing (unless someone disagrees).
Just some minor issues.
Mark
--
^ permalink raw reply
* Re: [PATCH 2/3] NFC: trf7970a: Add device tree option of 1.8 Volt IO voltage
From: Mark Greer @ 2016-12-21 2:07 UTC (permalink / raw)
To: Geoff Lansberry
Cc: Rob Herring, linux-wireless, Lauro Ramos Venancio,
Aloisio Almeida Jr, Samuel Ortiz, mark.rutland, netdev,
devicetree, linux-kernel, Justin Bronder
In-Reply-To: <CAO7Z3WLC7J+JzmtArOc-ZUNoLGeMX6s=1XQbzwB1zx1U3yTX2Q@mail.gmail.com>
On Tue, Dec 20, 2016 at 11:13:23AM -0500, Geoff Lansberry wrote:
> On Mon, Dec 19, 2016 at 5:35 PM, Rob Herring <robh@kernel.org> wrote:
> > On Thu, Dec 15, 2016 at 05:30:43PM -0500, Geoff Lansberry wrote:
> >> From: Geoff Lansberry <geoff@kuvee.com>
> >>
> >> ---
> >> Documentation/devicetree/bindings/net/nfc/trf7970a.txt | 2 ++
> >> drivers/nfc/trf7970a.c | 13 ++++++++++++-
> >> 2 files changed, 14 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/Documentation/devicetree/bindings/net/nfc/trf7970a.txt b/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
> >> index 9dda879..208f045 100644
> >> --- a/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
> >> +++ b/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
> >> @@ -21,6 +21,7 @@ Optional SoC Specific Properties:
> >> - t5t-rmb-extra-byte-quirk: Specify that the trf7970a has the erratum
> >> where an extra byte is returned by Read Multiple Block commands issued
> >> to Type 5 tags.
> >> +- vdd_io_1v8: Set to specify that the trf7970a io voltage should be set to 1.8V
> >
> > Use the regulator binding and provide a fixed 1.8V supply.
> >
> >> - crystal_27mhz: Set to specify that the input frequency to the trf7970a is 27.12MHz
> >>
> >>
> >> @@ -45,6 +46,7 @@ Example (for ARM-based BeagleBone with TRF7970A on SPI1):
> >> irq-status-read-quirk;
> >> en2-rf-quirk;
> >> t5t-rmb-extra-byte-quirk;
> >> + vdd_io_1v8;
> >> crystal_27mhz;
> >> status = "okay";
> >> };
>
> Rob - using the regulator binding is new to me, but I've given it a
> shot and just sent you another set of patches for your inspection.
> Please let me know if this is what you had in mind.
This is my bad. Geoff followed my example and did something similar to
'vin-voltage-override' which shouldn't have been there in the first place.
I have this fixed (I think) locally and will submit once it I'm back from
my holiday travels.
Mark
--
^ permalink raw reply
* [PATCH] nfc: fdp: fix NULL pointer dereference
From: Sudip Mukherjee @ 2016-12-20 21:09 UTC (permalink / raw)
To: Lauro Ramos Venancio, Aloisio Almeida Jr, Samuel Ortiz
Cc: linux-kernel, linux-wireless, Sudip Mukherjee
We are checking phy after dereferencing it. We can print the debug
information after checking it. If phy is NULL then we will get a good
stack trace to tell us that we are in this irq handler.
Signed-off-by: Sudip Mukherjee <sudip.mukherjee@codethink.co.uk>
---
drivers/nfc/fdp/i2c.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/nfc/fdp/i2c.c b/drivers/nfc/fdp/i2c.c
index 5e797d5..712936f 100644
--- a/drivers/nfc/fdp/i2c.c
+++ b/drivers/nfc/fdp/i2c.c
@@ -210,14 +210,14 @@ static irqreturn_t fdp_nci_i2c_irq_thread_fn(int irq, void *phy_id)
struct sk_buff *skb;
int r;
- client = phy->i2c_dev;
- dev_dbg(&client->dev, "%s\n", __func__);
-
if (!phy || irq != phy->i2c_dev->irq) {
WARN_ON_ONCE(1);
return IRQ_NONE;
}
+ client = phy->i2c_dev;
+ dev_dbg(&client->dev, "%s\n", __func__);
+
r = fdp_nci_i2c_read(phy, &skb);
if (r == -EREMOTEIO)
--
1.9.1
^ permalink raw reply related
* Re: [PATCH v2 2/2] cfg80211: Add support to sched scan to report better BSSs
From: Malinen, Jouni @ 2016-12-20 20:52 UTC (permalink / raw)
To: Johannes Berg
Cc: Arend Van Spriel, Vamsi, Krishna, linux-wireless@vger.kernel.org
In-Reply-To: <1481882211.27953.18.camel@sipsolutions.net>
On Fri, Dec 16, 2016 at 10:56:51AM +0100, Johannes Berg wrote:
> On Thu, 2016-12-15 at 11:06 +0000, Malinen, Jouni wrote:
> > Maybe the nl80211.h description was not clear enough, but the
> > comments in cfg80211.h should be quite clear on how this was designed
> > to work at the implementation level:
> >=20
> > "If the current connected BSS is in the 2.4 GHz band, other BSSs in
> > the 2.4 GHz band to be reported should have better RSSI by
> > @relative_rssi and other BSSs in the 5 GHz band to be reported should
> > have better RSSI by (@relative_rssi - @relative_rssi_5g_pref).
> > If the current connected BSS is in the 5 GHz band, other BSSs in the
> > 2.4 GHz band to be reported should have better RSSI by
> > (@relative_rssi + @relative_rssi_5g_pref) and other BSSs in the 5 GHz
> > band to be reported should have better RSSI by by @relative_rssi."
>=20
> Oh, right. Should probably be in nl80211.h too, to set expectations
> from userspace.
Sure, we can update that in the next revision.
> > At minimum, we would need to clearly document struct
> > nl80211_bss_select_rssi_adjust, but even if we do, I'm not sure it
> > really is ideal mechanism to move to now that I realized it is not an
> > array, but a single band,delta pair.
>=20
> We can move to an array easily in the future by extending the attribute
> length and advertising the number of array entries that are supported,
> if that's your biggest concern? I don't see it as being very useful
> right now since I don't think we'll see offloaded roaming between 2.4/5
> and 60 GHz anytime soon. This may change when we add more bands later,
> I suppose.
Hmm.. So do you want us to move to using this packed struct in the new
attribute instead of using a signed 8-bit integer as the variable value?
> > If we are talking only about roaming within an ESS (a single SSID),
> > that would sound clear, but which relative RSSI rules would apply if
> > there are match sets for both the currently connected SSID and
> > another SSID that the candidate BSS is for?
>=20
> Right, I see how this might become a problem. I generally see no issue
> with supporting multiple matchsets with different SSIDs but all having
> the "relative to connected BSS RSSI filter" (which would disregard the
> SSID specified in the matchset), but this then would become a problem
> when multiple matchsets are specified with *different* such RSSI
> filters, e.g. one matchset would specify that you want a 5G preference
> of 10dB, but the other would specify a preference of only 5dB.
>=20
> Conceptually in this approach, that would be supported, but firmware
> likely would not be able to express this, I suppose?
That's certainly not at the level we were planning on implementing.. :)
> > I think I'm missing something here.. Where would the threshold value
> > (how much better new BSS needs to be) be stored? And do we really
> > want something like the combination of
> > NL80211_BSS_SELECT_ATTR_BAND_PREF and
> > NL80211_BSS_SELECT_ATTR_RSSI_ADJUST which seems to be two different
> > ways of doing band preference (the former without specifying delta
> > and the latter with specific delta)? Or am I still not understanding
> > how NL80211_BSS_SELECT_ATTR_* really works?
>=20
> No, you're right, I missed the "better by" threshold.
>=20
> I think between that (unless we add that, we could technically extend
> flag attributes to allow them being an int as well, or add a new one)
> and the fact that the device may not support different relative RSSI
> matches in different matchsets, I'm almost convinced that adding new
> attributes is better.
I'm not completely sure how to interpret all this and also the last
email from Arend in this thread. Could either (or both :) of you provide
more detailed suggestion on what exactly you would like us to change, if
anything, in the attribute design now so that we can try to close on
this?
--=20
Jouni Malinen PGP id EFC895FA=
^ permalink raw reply
* [PATCH] cfg80211: Random local address for Public Action frame exchange
From: Jouni Malinen @ 2016-12-20 20:39 UTC (permalink / raw)
To: Johannes Berg; +Cc: linux-wireless, vamsi krishna, Jouni Malinen
From: vamsi krishna <vamsin@qti.qualcomm.com>
Add support to use random local address (Address 2 = SA in transmit and
the same address in receive functionality) for Public Action frames in
order to improve privacy of WLAN clients. Applications fill the random
source address in the frame buffer along with setting the
NL80211_ATTR_RANDOM_SA flag when using the NL80211_CMD_FRAME command.
This can be used only with the drivers that indicate support for random
local address by setting the new NL80211_EXT_FEATURE_MGMT_TX_RANDOM_SA
and/or NL80211_EXT_FEATURE_MGMT_TX_RANDOM_SA_CONNECTED in ext_features.
The driver needs to configure receive behavior to accept frames to the
specified random address during the time the frame exchange is pending
and such frames need to be acknowledged similarly to frames sent to the
local permanent address when this random address functionality is not
used.
Signed-off-by: vamsi krishna <vamsin@qti.qualcomm.com>
Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
---
include/net/cfg80211.h | 6 ++++++
include/uapi/linux/nl80211.h | 16 ++++++++++++++++
net/wireless/mlme.c | 5 ++++-
net/wireless/nl80211.c | 14 ++++++++++++++
4 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index ca2ac1c..c442e4c 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -2303,6 +2303,11 @@ struct cfg80211_update_ft_ies_params {
* @dont_wait_for_ack: tells the low level not to wait for an ack
* @n_csa_offsets: length of csa_offsets array
* @csa_offsets: array of all the csa offsets in the frame
+ * @random_sa: indicates whether the source address is randomized. When this is
+ * true, the driver needs to transmit the management frame using the
+ * address specified in the SA field (Address 2) in the buffer and the
+ * driver needs to receive and acknowledge the response frame to this
+ * address instead of its permanent MAC address.
*/
struct cfg80211_mgmt_tx_params {
struct ieee80211_channel *chan;
@@ -2314,6 +2319,7 @@ struct cfg80211_mgmt_tx_params {
bool dont_wait_for_ack;
int n_csa_offsets;
const u16 *csa_offsets;
+ bool random_sa;
};
/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index d74e10b..cffc117 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -567,6 +567,9 @@
* %NL80211_ATTR_CSA_C_OFFSETS_TX is an array of offsets to CSA
* counters which will be updated to the current value. This attribute
* is used during CSA period.
+ * When sending a Public Action frame, %NL80211_ATTR_MGMT_TX_RANDOM_SA can
+ * be used to indicate that random local address (SA) is used for the
+ * exchange.
* @NL80211_CMD_FRAME_WAIT_CANCEL: When an off-channel TX was requested, this
* command may be used with the corresponding cookie to cancel the wait
* time if it is known that it is no longer necessary.
@@ -1915,6 +1918,11 @@ enum nl80211_commands {
* %NL80211_ATTR_IFTYPE, %NL80211_ATTR_EXT_CAPA,
* %NL80211_ATTR_EXT_CAPA_MASK, to specify the extended capabilities per
* interface type.
+ * @NL80211_ATTR_MGMT_TX_RANDOM_SA: A flag attribute indicating whether the
+ * source address is randomized in frames sent using %NL80211_CMD_FRAME.
+ * If this flag is not set, the source address field is verified to match
+ * local MAC address. Random SA can be used only with Public Action frames
+ * (e.g., GAS/ANQP).
*
* @NL80211_ATTR_MU_MIMO_GROUP_DATA: array of 24 bytes that defines a MU-MIMO
* groupID for monitor mode.
@@ -2386,6 +2394,8 @@ enum nl80211_attrs {
NL80211_ATTR_BSSID,
+ NL80211_ATTR_MGMT_TX_RANDOM_SA,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
@@ -4697,6 +4707,10 @@ enum nl80211_feature_flags {
* configuration (AP/mesh) with VHT rates.
* @NL80211_EXT_FEATURE_FILS_STA: This driver supports Fast Initial Link Setup
* with user space SME (NL80211_CMD_AUTHENTICATE) in station mode.
+ * @NL80211_EXT_FEATURE_MGMT_TX_RANDOM_SA: This driver supports randomized SA
+ * in @NL80211_CMD_FRAME while not associated.
+ * @NL80211_EXT_FEATURE_MGMT_TX_RANDOM_SA_CONNECTED: This driver supports
+ * randomized SA in @NL80211_CMD_FRAME while associated.
*
* @NUM_NL80211_EXT_FEATURES: number of extended features.
* @MAX_NL80211_EXT_FEATURES: highest extended feature index.
@@ -4712,6 +4726,8 @@ enum nl80211_ext_feature_index {
NL80211_EXT_FEATURE_BEACON_RATE_HT,
NL80211_EXT_FEATURE_BEACON_RATE_VHT,
NL80211_EXT_FEATURE_FILS_STA,
+ NL80211_EXT_FEATURE_MGMT_TX_RANDOM_SA,
+ NL80211_EXT_FEATURE_MGMT_TX_RANDOM_SA_CONNECTED,
/* add new features before the definition below */
NUM_NL80211_EXT_FEATURES,
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index 4646cf5..9568a72 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -657,7 +657,10 @@ int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev,
return err;
}
- if (!ether_addr_equal(mgmt->sa, wdev_address(wdev)))
+ if (!(params->random_sa &&
+ (ieee80211_is_action(mgmt->frame_control) &&
+ mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)) &&
+ !ether_addr_equal(mgmt->sa, wdev_address(wdev)))
return -EINVAL;
/* Transmit the Action frame as requested by user space */
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 2369265..efced76 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -405,6 +405,7 @@ enum nl80211_multicast_groups {
[NL80211_ATTR_FILS_NONCES] = { .len = 2 * FILS_NONCE_LEN },
[NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED] = { .type = NLA_FLAG, },
[NL80211_ATTR_BSSID] = { .len = ETH_ALEN },
+ [NL80211_ATTR_MGMT_TX_RANDOM_SA] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -9209,6 +9210,19 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
}
}
+ params.random_sa = nla_get_flag(
+ info->attrs[NL80211_ATTR_MGMT_TX_RANDOM_SA]);
+ if (params.random_sa &&
+ ((!wdev->current_bss &&
+ !wiphy_ext_feature_isset(
+ &rdev->wiphy,
+ NL80211_EXT_FEATURE_MGMT_TX_RANDOM_SA)) ||
+ (wdev->current_bss &&
+ !wiphy_ext_feature_isset(
+ &rdev->wiphy,
+ NL80211_EXT_FEATURE_MGMT_TX_RANDOM_SA_CONNECTED))))
+ return -EINVAL;
+
if (!params.dont_wait_for_ack) {
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
--
1.9.1
^ permalink raw reply related
* Re: nfc: trf7970a: Prevent repeated polling from crashing the kernel
From: Mark Greer @ 2016-12-20 19:56 UTC (permalink / raw)
To: Justin Bronder
Cc: Geoff Lansberry, linux-wireless, lauro.venancio, aloisio.almeida,
sameo, robh+dt, mark.rutland, netdev, devicetree, linux-kernel,
Jaret Cantu
In-Reply-To: <20161220191352.GB23496@lasswell.members.linode.com>
On Tue, Dec 20, 2016 at 02:13:52PM -0500, Justin Bronder wrote:
> On 20/12/16 11:59 -0700, Mark Greer wrote:
> > On Tue, Dec 20, 2016 at 11:16:32AM -0500, Geoff Lansberry wrote:
> > > From: Jaret Cantu <jaret.cantu@timesys.com>
> > >
> > > Repeated polling attempts cause a NULL dereference error to occur.
> > > This is because the state of the trf7970a is currently reading but
> > > another request has been made to send a command before it has finished.
> >
> > How is this happening? Was trf7970a_abort_cmd() called and it didn't
> > work right? Was it not called at all and there is a bug in the digital
> > layer? More details please.
> >
> > > The solution is to properly kill the waiting reading (workqueue)
> > > before failing on the send.
> >
> > If the bug is in the calling code, then that is what should get fixed.
> > This seems to be a hack to work-around a digital layer bug.
>
> One of our uses of NFC is to begin polling to read a tag and then stop polling
> (in order to save power) until we know via user interaction that we need to poll
> again. This is typically many minutes later so the power saving is pretty
> significant. However, it's possible that a user will remove the tag before
> reading has completed. We also detect this case and stop polling. I can go
> more into this if necessary but that is what exposed a panic.
>
> You can reproduce using neard and python, in our testing it was very likely to
> occur in 10-100 iterations of the following.:
>
> #!/usr/bin/python
> import time
>
> import dbus
>
> bus = dbus.SystemBus()
> nfc0 = bus.get_object('org.neard', '/org/neard/nfc0')
> props = dbus.Interface(nfc0, 'org.freedesktop.DBus.Properties')
>
> try:
> props.Set('org.neard.Adapter', 'Powered', dbus.Boolean(1))
> except:
> pass
>
> adapter = dbus.Interface(nfc0, 'org.neard.Adapter')
>
> for i in range(1000):
> adapter.StartPollLoop('Initiator')
> time.sleep(0.1)
> adapter.StopPollLoop()
> print(i)
>
> I believe the last time we tested this was around the 4.1 release.
Thanks for the info, Justin, but I was also seeking more information
at the kernel NFC subsystem and trf7970a driver level. This patch
adds code inside an 'if' in the driver whose condition should never
be evaluate to true but apparently it did. How?
Thanks,
Mark
--
^ permalink raw reply
* Re: nfc: trf7970a: Prevent repeated polling from crashing the kernel
From: Justin Bronder @ 2016-12-20 19:13 UTC (permalink / raw)
To: Mark Greer
Cc: Geoff Lansberry, linux-wireless, lauro.venancio, aloisio.almeida,
sameo, robh+dt, mark.rutland, netdev, devicetree, linux-kernel,
Jaret Cantu
In-Reply-To: <20161220185905.GA5867@animalcreek.com>
On 20/12/16 11:59 -0700, Mark Greer wrote:
> On Tue, Dec 20, 2016 at 11:16:32AM -0500, Geoff Lansberry wrote:
> > From: Jaret Cantu <jaret.cantu@timesys.com>
> >
> > Repeated polling attempts cause a NULL dereference error to occur.
> > This is because the state of the trf7970a is currently reading but
> > another request has been made to send a command before it has finished.
>
> How is this happening? Was trf7970a_abort_cmd() called and it didn't
> work right? Was it not called at all and there is a bug in the digital
> layer? More details please.
>
> > The solution is to properly kill the waiting reading (workqueue)
> > before failing on the send.
>
> If the bug is in the calling code, then that is what should get fixed.
> This seems to be a hack to work-around a digital layer bug.
One of our uses of NFC is to begin polling to read a tag and then stop polling
(in order to save power) until we know via user interaction that we need to poll
again. This is typically many minutes later so the power saving is pretty
significant. However, it's possible that a user will remove the tag before
reading has completed. We also detect this case and stop polling. I can go
more into this if necessary but that is what exposed a panic.
You can reproduce using neard and python, in our testing it was very likely to
occur in 10-100 iterations of the following.:
#!/usr/bin/python
import time
import dbus
bus = dbus.SystemBus()
nfc0 = bus.get_object('org.neard', '/org/neard/nfc0')
props = dbus.Interface(nfc0, 'org.freedesktop.DBus.Properties')
try:
props.Set('org.neard.Adapter', 'Powered', dbus.Boolean(1))
except:
pass
adapter = dbus.Interface(nfc0, 'org.neard.Adapter')
for i in range(1000):
adapter.StartPollLoop('Initiator')
time.sleep(0.1)
adapter.StopPollLoop()
print(i)
I believe the last time we tested this was around the 4.1 release.
--
Justin Bronder
^ permalink raw reply
* Re: [PATCH 3/3] nfc: trf7970a: Prevent repeated polling from crashing the kernel
From: Mark Greer @ 2016-12-20 18:59 UTC (permalink / raw)
To: Geoff Lansberry
Cc: linux-wireless, lauro.venancio, aloisio.almeida, sameo, robh+dt,
mark.rutland, netdev, devicetree, linux-kernel, justin,
Jaret Cantu
In-Reply-To: <1482250592-4268-3-git-send-email-glansberry@gmail.com>
On Tue, Dec 20, 2016 at 11:16:32AM -0500, Geoff Lansberry wrote:
> From: Jaret Cantu <jaret.cantu@timesys.com>
>
> Repeated polling attempts cause a NULL dereference error to occur.
> This is because the state of the trf7970a is currently reading but
> another request has been made to send a command before it has finished.
How is this happening? Was trf7970a_abort_cmd() called and it didn't
work right? Was it not called at all and there is a bug in the digital
layer? More details please.
> The solution is to properly kill the waiting reading (workqueue)
> before failing on the send.
If the bug is in the calling code, then that is what should get fixed.
This seems to be a hack to work-around a digital layer bug.
Mark
--
^ permalink raw reply
* Re: [PATCH 1/3] NFC: trf7970a: add device tree option for 27MHz clock
From: Geoff Lansberry @ 2016-12-20 18:29 UTC (permalink / raw)
To: Mark Greer
Cc: linux-wireless, Lauro Ramos Venancio, Aloisio Almeida Jr,
Samuel Ortiz, robh+dt, mark.rutland, netdev, devicetree,
linux-kernel, Justin Bronder
In-Reply-To: <20161220181157.GB30273@animalcreek.com>
On Tue, Dec 20, 2016 at 1:11 PM, Mark Greer <mgreer@animalcreek.com> wrote:
> Hi Geoff.
>
> Please put the version in your subjects when submitting anything but the
> initial version of a patch (e.g., [PATCH v2 1/3]).
>
> Which series do you want reviewed?
>
> Mark
> --
Sorry about the double posting, I had forgotten to erase the patches I
generated while rebasing and checking, and I'll have to figure out how
to add that v2 line to the automatically generated subject line if I
end up submitting another round.
Please review the three most recent patches, which have the send time
of 17:16.
Best Regards,
Geoff
^ permalink raw reply
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