* AR6003 SDIO can't received data if you didn't send any date for a while
From: Randy Li @ 2013-05-15 17:21 UTC (permalink / raw)
To: linux-wireless; +Cc: ath6kl-devel
I use this WNIC in a s3c6410 devboard called OK6410 made by
forlinx(http://www.forlinx.net/Other/6.htm)
The kernel is 3.0.1, the driver in the kernel source can't work. So I
tried compat-wireless-3.3-2-n and
compat-wireless-3.5.4-1. 3.3-2 is eaier to get -101 error when I load
the modules, in this time I have to replug WNIC card.
When networking is connected, I can't ping ar6003 in the other place,
if ar6003 didn't send any data
recently.Once ar6003 stoped sending, I can't send to ar6003 after a
very little while.
But if I send large data like video both of them still stop working
after a while, 3.3-2(about 5 minutes) seems to work more longer than
3.5.4-1(about 1 mintue). But both ip link and iw wlan0 link show my
link is up. I have to ifdown and ifup to use wireless again.
kvalo said it cause by irq problem.But when I compiled compat-wireless
with debug, I only have files in /sys/kernel/debug/ieee80211/
phy0/ , but there is ath6kl directory there. I have enabled all
debug_mask, but I don't get any irq infomation frome dmesg.
Could you tell me the correct way enable debug in compiling and load module?
P.S how to install compat-wireless? I can't use INSTALL_MOD_PATH, I
have to copy these directory and use find tool to remove .c .h .o
files.
Here is output of /proc/interrupts in ok6410
CPU0
16: 3 s3c-uart s3c6400-uart
18: 240 s3c-uart s3c6400-uart
20: 2 s3c-uart
22: 48 s3c-uart
35: 0 VIC s3c-fimc0
36: 0 VIC s3c-fimc1
40: 0 VIC s3c-g3d
41: 0 VIC s3c-pp
42: 0 VIC s3c-rotator
43: 0 VIC s3c-g2d
44: 0 VIC TV_ENCODER
45: 0 VIC TV_SCALER
47: 0 VIC s3c-jpeg
48: 4735 VIC s3c-mfc
54: 0 VIC s3c-keypad
58: 0 VIC s3c2410-wdt
62: 460074 VIC s3c-lcd
68: 26 VIC AC97
73: 0 VIC DMA
74: 0 VIC DMA
79: 177847 VIC ohci_hcd:usb1
82: 123 VIC s3c2440-i2c.0
88: 46540 VIC mmc0
94: 0 VIC s3c_updown
95: 0 VIC adc, s3c_action
100: 727214 s3c-timer S3C2410 Timer Tick
101: 0 s3c-eint BUTTON1
102: 0 s3c-eint BUTTON2
103: 0 s3c-eint BUTTON3
104: 0 s3c-eint BUTTON4
105: 0 s3c-eint BUTTON5
106: 0 s3c-eint BUTTON6
108: 59168 s3c-eint eth0
Err: 0
-----
ayaka
^ permalink raw reply
* Re: [ath9k-devel] ath9k_htc: Target is unresponsive
From: Ignacy Gawedzki @ 2013-05-15 16:34 UTC (permalink / raw)
To: Eugene Krasnikov; +Cc: Kalle Valo, ath9k-devel, linux-wireless
In-Reply-To: <CAFSJ42bDB+c0=eRmBYZBaQnZ4-h2Jm_MWZbfSEhUELb9y0Qv_Q@mail.gmail.com>
On Wed, May 15, 2013 at 06:11:49PM +0200, thus spake Eugene Krasnikov:
> there is a function in driver "ath9k_htc_reset"
> http://lxr.free-electrons.com/source/drivers/net/wireless/ath/ath9k/htc_drv_main.c#L189
> but not sure that is what you are looking for.
>From the issue report about watchdog problems, I gather that the device is
supposed to be prepared for reception of the firmware code from the driver.
This normally happens when the device boots up and passes the first stage of
the bootloader. I suppose (I'm really totally guessing) that the driver
somehow doesn't do something to prepare the device for firmware upload if the
device is already past stage 2.
I would love to have the UART pins connected to some terminal, but I don't
have the soldering tools for such small-scale work...
--
A person is shit's way of making more shit.
-- S. Barnett, anthropologist.
^ permalink raw reply
* Re: [PATCH 00/29] ath10k: mac80211 driver for Qualcomm Atheros qca988x devices
From: Kalle Valo @ 2013-05-15 16:22 UTC (permalink / raw)
To: Sujith Manoharan; +Cc: Joe Perches, linux-wireless, ath10k-devel
In-Reply-To: <87wqr0ns47.fsf@kamboji.qca.qualcomm.com>
Kalle Valo <kvalo@qca.qualcomm.com> writes:
> Sujith Manoharan <sujith@msujith.org> writes:
>
>> Joe Perches wrote:
>>> Perhaps it would be reasonable to create and use standardized
>>> logging ath_<level> functions for all the device ath<x>k_<level>s.
>>
>> Indeed.
>>
>> You already did that in "ath: Add and use ath_printk and ath_<level>" :)
>
> IIRC Sujith actually suggested this to me already earlier, but I didn't
> buy the idea even then. ath9k and ath10k are very different, so we
> cannot share all the debug levels. I just feel that it restricts and
> confuses the driver debugging too much, and all just to save something
> like 100 lines of code. I'm having hard time to see why bother.
I also noticed that ath.h doesn't support tracing. I would hate to lose
that.
--
Kalle Valo
^ permalink raw reply
* Re: [ath9k-devel] ath9k_htc: Target is unresponsive
From: Eugene Krasnikov @ 2013-05-15 16:11 UTC (permalink / raw)
To: Kalle Valo; +Cc: Ignacy Gawedzki, ath9k-devel, linux-wireless
In-Reply-To: <87a9nwpaaf.fsf@kamboji.qca.qualcomm.com>
there is a function in driver "ath9k_htc_reset"
http://lxr.free-electrons.com/source/drivers/net/wireless/ath/ath9k/htc_drv_main.c#L189
but not sure that is what you are looking for.
2013/5/15 Kalle Valo <kvalo@qca.qualcomm.com>:
> Ignacy Gawedzki <i@lri.fr> writes:
>
>> Hi everyone,
>>
>> This is an old issue, about which it seems the first posts date back to 2009.
>> With the latest possible version of the firmware for AR9271 and the latest
>> possible wireless drivers, the issue is still there. Whether this is still
>> the exact same problem every time remains to be checked, but it is annoying
>> enough to deserve some consideration.
>>
>> The problem is that the driver can't talk to the device if the following
>> conditions are met:
>>
>> - The system cold boots with the USB dongle inserted or the USB dongle is
>> inserted on a running system.
>>
>> - The interface is *not* brought up.
>>
>> - The system (warm) reboots.
>>
>> Then the driver complains about the target being unresponsive after uploading
>> the firmware. Apparently the only way to make it work again is to unplug and
>> then plug the USB dongle back, physically. It seems that if the USB port is
>> not powered down during reboot (which happens with at least two different
>> setups that I've tested it with), the device is left in a broken state.
>>
>> I would gladly help in tracing down this bug if only I knew where to start.
>
> With ath6kl a cold reset helped in a similar situation. Try to find if
> ath9k_htc has a cold/warm reset and see if that helps.
>
> --
> Kalle Valo
> _______________________________________________
> ath9k-devel mailing list
> ath9k-devel@lists.ath9k.org
> https://lists.ath9k.org/mailman/listinfo/ath9k-devel
--
Best regards,
Eugene
^ permalink raw reply
* Re: [PATCH 00/29] ath10k: mac80211 driver for Qualcomm Atheros qca988x devices
From: Kalle Valo @ 2013-05-15 16:08 UTC (permalink / raw)
To: Sujith Manoharan; +Cc: Joe Perches, linux-wireless, ath10k-devel
In-Reply-To: <20883.44965.174742.469466@gargle.gargle.HOWL>
Sujith Manoharan <sujith@msujith.org> writes:
> Joe Perches wrote:
>> Perhaps it would be reasonable to create and use standardized
>> logging ath_<level> functions for all the device ath<x>k_<level>s.
>
> Indeed.
>
> You already did that in "ath: Add and use ath_printk and ath_<level>" :)
IIRC Sujith actually suggested this to me already earlier, but I didn't
buy the idea even then. ath9k and ath10k are very different, so we
cannot share all the debug levels. I just feel that it restricts and
confuses the driver debugging too much, and all just to save something
like 100 lines of code. I'm having hard time to see why bother.
--
Kalle Valo
^ permalink raw reply
* Re: [PATCH 00/29] ath10k: mac80211 driver for Qualcomm Atheros qca988x devices
From: Sujith Manoharan @ 2013-05-15 15:54 UTC (permalink / raw)
To: Joe Perches; +Cc: Kalle Valo, linux-wireless, ath10k-devel
In-Reply-To: <1368633142.2194.6.camel@joe-AO722>
Joe Perches wrote:
> Perhaps it would be reasonable to create and use standardized
> logging ath_<level> functions for all the device ath<x>k_<level>s.
Indeed.
You already did that in "ath: Add and use ath_printk and ath_<level>" :)
Sujith
^ permalink raw reply
* Re: [PATCH 00/29] ath10k: mac80211 driver for Qualcomm Atheros qca988x devices
From: Joe Perches @ 2013-05-15 15:52 UTC (permalink / raw)
To: Kalle Valo; +Cc: linux-wireless, ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
On Wed, 2013-05-15 at 17:42 +0300, Kalle Valo wrote:
> Here's a new mac80211 driver for Qualcomm Atheros qca988x devices.
> A major difference from ath9k is that there's now a firmware and
> that's why we had to implement a new driver.
[]
> ath10k: add debug.c
> ath10k: add debug.h
Perhaps it would be reasonable to create and use standardized
logging ath_<level> functions for all the device ath<x>k_<level>s.
^ permalink raw reply
* Re: [PATCH 02/29] ath10k: add bmi.h
From: Kalle Valo @ 2013-05-15 15:17 UTC (permalink / raw)
To: Johannes Berg; +Cc: linux-wireless, ath10k-devel
In-Reply-To: <1368630093.20897.8.camel@jlt4.sipsolutions.net>
Johannes Berg <johannes@sipsolutions.net> writes:
> On Wed, 2013-05-15 at 17:42 +0300, Kalle Valo wrote:
>
>> +/*
>> + * Bootloader Messaging Interface (BMI)
>
> This looks exactly like ath6kl, but is it? Seems it might have potential
> for sharing the code.
Somewhere deep down ath10k and ath6kl have common history, but the
firmware engineers have made so many changes everywhere that IMHO trying
to share code is not really worth it.
--
Kalle Valo
^ permalink raw reply
* [PATCH V3] rtlwifi: rtl8192cu: Add new USB ID
From: Larry Finger @ 2013-05-15 15:03 UTC (permalink / raw)
To: linville; +Cc: linux-wireless, Albert Pool, netdev, Larry Finger
From: Albert Pool <albertpool@solcon.nl>
This adds the USB ID of the On Networks N300MA, clone of Netgear WNA3100M.
Signed-off-by: Albert Pool <albertpool@solcon.nl>
Signed-off-by: Larry Finger <Larry.Finger@lwfinger.net>
Reported-by: Ana Rey <Anazul77@hotmail.com>
---
John,
Please use this version. V2 still had some whitespace errors that I fixed.
Albert,
Please use scripts/checkpatch.pl to check the patch before you send it.
Thanks,
Larry
---
drivers/net/wireless/rtlwifi/rtl8192cu/sw.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/sw.c b/drivers/net/wireless/rtlwifi/rtl8192cu/sw.c
index 23d640a..18da6de 100644
--- a/drivers/net/wireless/rtlwifi/rtl8192cu/sw.c
+++ b/drivers/net/wireless/rtlwifi/rtl8192cu/sw.c
@@ -349,6 +349,7 @@ static struct usb_device_id rtl8192c_usb_ids[] = {
{RTL_USB_DEVICE(0x07aa, 0x0056, rtl92cu_hal_cfg)}, /*ATKK-Gemtek*/
{RTL_USB_DEVICE(0x07b8, 0x8178, rtl92cu_hal_cfg)}, /*Funai -Abocom*/
{RTL_USB_DEVICE(0x0846, 0x9021, rtl92cu_hal_cfg)}, /*Netgear-Sercomm*/
+ {RTL_USB_DEVICE(0x0846, 0xf001, rtl92cu_hal_cfg)}, /*On Netwrks N300MA*/
{RTL_USB_DEVICE(0x0b05, 0x17ab, rtl92cu_hal_cfg)}, /*ASUS-Edimax*/
{RTL_USB_DEVICE(0x0bda, 0x8186, rtl92cu_hal_cfg)}, /*Realtek 92CE-VAU*/
{RTL_USB_DEVICE(0x0df6, 0x0061, rtl92cu_hal_cfg)}, /*Sitecom-Edimax*/
--
1.8.1.4
^ permalink raw reply related
* Re: [PATCH 02/29] ath10k: add bmi.h
From: Johannes Berg @ 2013-05-15 15:01 UTC (permalink / raw)
To: Kalle Valo; +Cc: linux-wireless, ath10k-devel
In-Reply-To: <20130515144254.4492.14914.stgit@localhost6.localdomain6>
On Wed, 2013-05-15 at 17:42 +0300, Kalle Valo wrote:
> +/*
> + * Bootloader Messaging Interface (BMI)
This looks exactly like ath6kl, but is it? Seems it might have potential
for sharing the code.
johannes
^ permalink raw reply
* [PATCH 29/29] ath10k: add Kconfig and Makefile
From: Kalle Valo @ 2013-05-15 14:46 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/Kconfig | 1 +
drivers/net/wireless/ath/Makefile | 1 +
drivers/net/wireless/ath/ath10k/Kconfig | 39 ++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath10k/Makefile | 20 +++++++++++++++
4 files changed, 61 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/Kconfig
create mode 100644 drivers/net/wireless/ath/ath10k/Makefile
diff --git a/drivers/net/wireless/ath/Kconfig b/drivers/net/wireless/ath/Kconfig
index 2c02b4e..7f2a160 100644
--- a/drivers/net/wireless/ath/Kconfig
+++ b/drivers/net/wireless/ath/Kconfig
@@ -27,6 +27,7 @@ config ATH_DEBUG
source "drivers/net/wireless/ath/ath5k/Kconfig"
source "drivers/net/wireless/ath/ath9k/Kconfig"
+source "drivers/net/wireless/ath/ath10k/Kconfig"
source "drivers/net/wireless/ath/carl9170/Kconfig"
source "drivers/net/wireless/ath/ath6kl/Kconfig"
source "drivers/net/wireless/ath/ar5523/Kconfig"
diff --git a/drivers/net/wireless/ath/Makefile b/drivers/net/wireless/ath/Makefile
index 97b964d..fb05cfd 100644
--- a/drivers/net/wireless/ath/Makefile
+++ b/drivers/net/wireless/ath/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_CARL9170) += carl9170/
obj-$(CONFIG_ATH6KL) += ath6kl/
obj-$(CONFIG_AR5523) += ar5523/
obj-$(CONFIG_WIL6210) += wil6210/
+obj-$(CONFIG_ATH10K) += ath10k/
obj-$(CONFIG_ATH_COMMON) += ath.o
diff --git a/drivers/net/wireless/ath/ath10k/Kconfig b/drivers/net/wireless/ath/ath10k/Kconfig
new file mode 100644
index 0000000..cde58fe
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/Kconfig
@@ -0,0 +1,39 @@
+config ATH10K
+ tristate "Atheros 802.11ac wireless cards support"
+ depends on MAC80211
+ select ATH_COMMON
+ ---help---
+ This module adds support for wireless adapters based on
+ Atheros IEEE 802.11ac family of chipsets.
+
+ If you choose to build a module, it'll be called ath10k.
+
+config ATH10K_PCI
+ tristate "Atheros ath10k PCI support"
+ depends on ATH10K && PCI
+ ---help---
+ This module adds support for PCIE bus
+
+config ATH10K_DEBUG
+ bool "Atheros ath10k debugging"
+ depends on ATH10K
+ ---help---
+ Enables debug support
+
+ If unsure, say Y to make it easier to debug problems.
+
+config ATH10K_DEBUGFS
+ bool "Atheros ath10k debugfs support"
+ depends on ATH10K
+ ---help---
+ Enabled debugfs support
+
+ If unsure, say Y to make it easier to debug problems.
+
+config ATH10K_TRACING
+ bool "Atheros ath10k tracing support"
+ depends on ATH10K
+ depends on EVENT_TRACING
+ ---help---
+ Select this to ath10k use tracing infrastructure.
+
diff --git a/drivers/net/wireless/ath/ath10k/Makefile b/drivers/net/wireless/ath/ath10k/Makefile
new file mode 100644
index 0000000..a4179f4
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/Makefile
@@ -0,0 +1,20 @@
+obj-$(CONFIG_ATH10K) += ath10k_core.o
+ath10k_core-y += mac.o \
+ debug.o \
+ core.o \
+ htc.o \
+ htt.o \
+ htt_rx.o \
+ htt_tx.o \
+ txrx.o \
+ wmi.o \
+ bmi.o
+
+ath10k_core-$(CONFIG_ATH10K_TRACING) += trace.o
+
+obj-$(CONFIG_ATH10K_PCI) += ath10k_pci.o
+ath10k_pci-y += pci.o \
+ ce.o
+
+# for tracing framework to find trace.h
+CFLAGS_trace.o := -I$(src)
^ permalink raw reply related
* [PATCH 13/29] ath10k: add htt.h
From: Kalle Valo @ 2013-05-15 14:44 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/htt.h | 1352 +++++++++++++++++++++++++++++++++
1 file changed, 1352 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/htt.h
diff --git a/drivers/net/wireless/ath/ath10k/htt.h b/drivers/net/wireless/ath/ath10k/htt.h
new file mode 100644
index 0000000..f3c22df
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/htt.h
@@ -0,0 +1,1352 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _HTT_H_
+#define _HTT_H_
+
+#include <linux/bug.h>
+
+#include "core.h"
+#include "htc.h"
+#include "rx_desc.h"
+
+#define HTT_CURRENT_VERSION_MAJOR 2
+#define HTT_CURRENT_VERSION_MINOR 1
+
+enum htt_dbg_stats_type {
+ HTT_DBG_STATS_WAL_PDEV_TXRX = 1 << 0,
+ HTT_DBG_STATS_RX_REORDER = 1 << 1,
+ HTT_DBG_STATS_RX_RATE_INFO = 1 << 2,
+ HTT_DBG_STATS_TX_PPDU_LOG = 1 << 3,
+ HTT_DBG_STATS_TX_RATE_INFO = 1 << 4,
+ /* bits 5-23 currently reserved */
+
+ HTT_DBG_NUM_STATS /* keep this last */
+};
+
+enum htt_h2t_msg_type { /* host-to-target */
+ HTT_H2T_MSG_TYPE_VERSION_REQ = 0,
+ HTT_H2T_MSG_TYPE_TX_FRM = 1,
+ HTT_H2T_MSG_TYPE_RX_RING_CFG = 2,
+ HTT_H2T_MSG_TYPE_STATS_REQ = 3,
+ HTT_H2T_MSG_TYPE_SYNC = 4,
+ HTT_H2T_MSG_TYPE_AGGR_CFG = 5,
+ HTT_H2T_MSG_TYPE_FRAG_DESC_BANK_CFG = 6,
+ HTT_H2T_MSG_TYPE_MGMT_TX = 7,
+
+ HTT_H2T_NUM_MSGS /* keep this last */
+};
+
+struct htt_cmd_hdr {
+ u8 msg_type;
+} __packed;
+
+struct htt_ver_req {
+ u8 pad[sizeof(u32) - sizeof(struct htt_cmd_hdr)];
+} __packed;
+
+/*
+ * HTT tx MSDU descriptor
+ *
+ * The HTT tx MSDU descriptor is created by the host HTT SW for each
+ * tx MSDU. The HTT tx MSDU descriptor contains the information that
+ * the target firmware needs for the FW's tx processing, particularly
+ * for creating the HW msdu descriptor.
+ * The same HTT tx descriptor is used for HL and LL systems, though
+ * a few fields within the tx descriptor are used only by LL or
+ * only by HL.
+ * The HTT tx descriptor is defined in two manners: by a struct with
+ * bitfields, and by a series of [dword offset, bit mask, bit shift]
+ * definitions.
+ * The target should use the struct def, for simplicitly and clarity,
+ * but the host shall use the bit-mast + bit-shift defs, to be endian-
+ * neutral. Specifically, the host shall use the get/set macros built
+ * around the mask + shift defs.
+ */
+struct htt_data_tx_desc_frag {
+ __le32 paddr;
+ __le32 len;
+} __packed;
+
+enum htt_data_tx_desc_flags0 {
+ HTT_DATA_TX_DESC_FLAGS0_MAC_HDR_PRESENT = 1 << 0,
+ HTT_DATA_TX_DESC_FLAGS0_NO_AGGR = 1 << 1,
+ HTT_DATA_TX_DESC_FLAGS0_NO_ENCRYPT = 1 << 2,
+ HTT_DATA_TX_DESC_FLAGS0_NO_CLASSIFY = 1 << 3,
+ HTT_DATA_TX_DESC_FLAGS0_RSVD0 = 1 << 4
+#define HTT_DATA_TX_DESC_FLAGS0_PKT_TYPE_MASK 0xE0
+#define HTT_DATA_TX_DESC_FLAGS0_PKT_TYPE_LSB 5
+};
+
+enum htt_data_tx_desc_flags1 {
+#define HTT_DATA_TX_DESC_FLAGS1_VDEV_ID_BITS 6
+#define HTT_DATA_TX_DESC_FLAGS1_VDEV_ID_MASK 0x003F
+#define HTT_DATA_TX_DESC_FLAGS1_VDEV_ID_LSB 0
+#define HTT_DATA_TX_DESC_FLAGS1_EXT_TID_BITS 5
+#define HTT_DATA_TX_DESC_FLAGS1_EXT_TID_MASK 0x07C0
+#define HTT_DATA_TX_DESC_FLAGS1_EXT_TID_LSB 6
+ HTT_DATA_TX_DESC_FLAGS1_POSTPONED = 1 << 11,
+ HTT_DATA_TX_DESC_FLAGS1_MORE_IN_BATCH = 1 << 12,
+ HTT_DATA_TX_DESC_FLAGS1_CKSUM_L3_OFFLOAD = 1 << 13,
+ HTT_DATA_TX_DESC_FLAGS1_CKSUM_L4_OFFLOAD = 1 << 14,
+ HTT_DATA_TX_DESC_FLAGS1_RSVD1 = 1 << 15
+};
+
+enum htt_data_tx_ext_tid {
+ HTT_DATA_TX_EXT_TID_NON_QOS_MCAST_BCAST = 16,
+ HTT_DATA_TX_EXT_TID_MGMT = 17,
+ HTT_DATA_TX_EXT_TID_INVALID = 31
+};
+
+#define HTT_INVALID_PEERID 0xFFFF
+
+/*
+ * htt_data_tx_desc - used for data tx path
+ *
+ * Note: vdev_id irrelevant for pkt_type == raw and no_classify == 1.
+ * ext_tid: for qos-data frames (0-15), see %HTT_DATA_TX_EXT_TID_
+ * for special kinds of tids
+ * postponed: only for HL hosts. indicates if this is a resend
+ * (HL hosts manage queues on the host )
+ * more_in_batch: only for HL hosts. indicates if more packets are
+ * pending. this allows target to wait and aggregate
+ */
+struct htt_data_tx_desc {
+ u8 flags0; /* %HTT_DATA_TX_DESC_FLAGS0_ */
+ __le16 flags1; /* %HTT_DATA_TX_DESC_FLAGS1_ */
+ __le16 len;
+ __le16 id;
+ __le32 frags_paddr;
+ __le32 peerid;
+ u8 prefetch[0]; /* start of frame, for FW classification engine */
+} __packed;
+
+enum htt_rx_ring_flags {
+ HTT_RX_RING_FLAGS_MAC80211_HDR = 1 << 0,
+ HTT_RX_RING_FLAGS_MSDU_PAYLOAD = 1 << 1,
+ HTT_RX_RING_FLAGS_PPDU_START = 1 << 2,
+ HTT_RX_RING_FLAGS_PPDU_END = 1 << 3,
+ HTT_RX_RING_FLAGS_MPDU_START = 1 << 4,
+ HTT_RX_RING_FLAGS_MPDU_END = 1 << 5,
+ HTT_RX_RING_FLAGS_MSDU_START = 1 << 6,
+ HTT_RX_RING_FLAGS_MSDU_END = 1 << 7,
+ HTT_RX_RING_FLAGS_RX_ATTENTION = 1 << 8,
+ HTT_RX_RING_FLAGS_FRAG_INFO = 1 << 9,
+ HTT_RX_RING_FLAGS_UNICAST_RX = 1 << 10,
+ HTT_RX_RING_FLAGS_MULTICAST_RX = 1 << 11,
+ HTT_RX_RING_FLAGS_CTRL_RX = 1 << 12,
+ HTT_RX_RING_FLAGS_MGMT_RX = 1 << 13,
+ HTT_RX_RING_FLAGS_NULL_RX = 1 << 14,
+ HTT_RX_RING_FLAGS_PHY_DATA_RX = 1 << 15
+};
+
+struct htt_rx_ring_setup_ring {
+ __le32 fw_idx_shadow_reg_paddr;
+ __le32 rx_ring_base_paddr;
+ __le16 rx_ring_len; /* in 4-byte words */
+ __le16 rx_ring_bufsize; /* rx skb size - in bytes */
+ __le16 flags; /* %HTT_RX_RING_FLAGS_ */
+ __le16 fw_idx_init_val;
+
+ /* the following offsets are in 4-byte units */
+ __le16 mac80211_hdr_offset;
+ __le16 msdu_payload_offset;
+ __le16 ppdu_start_offset;
+ __le16 ppdu_end_offset;
+ __le16 mpdu_start_offset;
+ __le16 mpdu_end_offset;
+ __le16 msdu_start_offset;
+ __le16 msdu_end_offset;
+ __le16 rx_attention_offset;
+ __le16 frag_info_offset;
+} __packed;
+
+struct htt_rx_ring_setup_hdr {
+ u8 num_rings; /* supported values: 1, 2 */
+ __le16 rsvd0;
+} __packed;
+
+struct htt_rx_ring_setup {
+ struct htt_rx_ring_setup_hdr hdr;
+ struct htt_rx_ring_setup_ring rings[0];
+} __packed;
+
+/*
+ * htt_stats_req - request target to send specified statistics
+ *
+ * @msg_type: hardcoded %HTT_H2T_MSG_TYPE_STATS_REQ
+ * @upload_types: see %htt_dbg_stats_type. this is 24bit field actually
+ * so make sure its little-endian.
+ * @reset_types: see %htt_dbg_stats_type. this is 24bit field actually
+ * so make sure its little-endian.
+ * @cfg_val: stat_type specific configuration
+ * @stat_type: see %htt_dbg_stats_type
+ * @cookie_lsb: used for confirmation message from target->host
+ * @cookie_msb: ditto as %cookie
+ */
+struct htt_stats_req {
+ u8 upload_types[3];
+ u8 rsvd0;
+ u8 reset_types[3];
+ struct {
+ u8 mpdu_bytes;
+ u8 mpdu_num_msdus;
+ u8 msdu_bytes;
+ } __packed;
+ u8 stat_type;
+ __le32 cookie_lsb;
+ __le32 cookie_msb;
+} __packed;
+
+#define HTT_STATS_REQ_CFG_STAT_TYPE_INVALID 0xff
+
+/*
+ * htt_oob_sync_req - request out-of-band sync
+ *
+ * The HTT SYNC tells the target to suspend processing of subsequent
+ * HTT host-to-target messages until some other target agent locally
+ * informs the target HTT FW that the current sync counter is equal to
+ * or greater than (in a modulo sense) the sync counter specified in
+ * the SYNC message.
+ *
+ * This allows other host-target components to synchronize their operation
+ * with HTT, e.g. to ensure that tx frames don't get transmitted until a
+ * security key has been downloaded to and activated by the target.
+ * In the absence of any explicit synchronization counter value
+ * specification, the target HTT FW will use zero as the default current
+ * sync value.
+ *
+ * The HTT target FW will suspend its host->target message processing as long
+ * as 0 < (in-band sync counter - out-of-band sync counter) & 0xff < 128.
+ */
+struct htt_oob_sync_req {
+ u8 sync_count;
+ __le16 rsvd0;
+} __packed;
+
+#define HTT_AGGR_CONF_MAX_NUM_AMSDU_SUBFRAMES_MASK 0x1F
+#define HTT_AGGR_CONF_MAX_NUM_AMSDU_SUBFRAMES_LSB 0
+
+struct htt_aggr_conf {
+ u8 max_num_ampdu_subframes;
+ union {
+ /* dont use bitfields; undefined behaviour */
+ u8 flags; /* see %HTT_AGGR_CONF_MAX_NUM_AMSDU_SUBFRAMES_ */
+ u8 max_num_amsdu_subframes:5;
+ } __packed;
+} __packed;
+
+#define HTT_MGMT_FRM_HDR_DOWNLOAD_LEN 32
+
+struct htt_mgmt_tx_desc {
+ u8 pad[sizeof(u32) - sizeof(struct htt_cmd_hdr)];
+ __le32 msdu_paddr;
+ __le32 desc_id;
+ __le32 len;
+ __le32 vdev_id;
+ u8 hdr[HTT_MGMT_FRM_HDR_DOWNLOAD_LEN];
+} __packed;
+
+enum htt_mgmt_tx_status {
+ HTT_MGMT_TX_STATUS_OK = 0,
+ HTT_MGMT_TX_STATUS_RETRY = 1,
+ HTT_MGMT_TX_STATUS_DROP = 2
+};
+
+/*=== target -> host messages ===============================================*/
+
+
+enum htt_t2h_msg_type {
+ HTT_T2H_MSG_TYPE_VERSION_CONF = 0x0,
+ HTT_T2H_MSG_TYPE_RX_IND = 0x1,
+ HTT_T2H_MSG_TYPE_RX_FLUSH = 0x2,
+ HTT_T2H_MSG_TYPE_PEER_MAP = 0x3,
+ HTT_T2H_MSG_TYPE_PEER_UNMAP = 0x4,
+ HTT_T2H_MSG_TYPE_RX_ADDBA = 0x5,
+ HTT_T2H_MSG_TYPE_RX_DELBA = 0x6,
+ HTT_T2H_MSG_TYPE_TX_COMPL_IND = 0x7,
+ HTT_T2H_MSG_TYPE_PKTLOG = 0x8,
+ HTT_T2H_MSG_TYPE_STATS_CONF = 0x9,
+ HTT_T2H_MSG_TYPE_RX_FRAG_IND = 0xa,
+ HTT_T2H_MSG_TYPE_SEC_IND = 0xb,
+ HTT_T2H_MSG_TYPE_RC_UPDATE_IND = 0xc,
+ HTT_T2H_MSG_TYPE_TX_INSPECT_IND = 0xd,
+ HTT_T2H_MSG_TYPE_MGMT_TX_COMPLETION = 0xe,
+ HTT_T2H_MSG_TYPE_TEST,
+ /* keep this last */
+ HTT_T2H_NUM_MSGS
+};
+
+/*
+ * htt_resp_hdr - header for target-to-host messages
+ *
+ * msg_type: see htt_t2h_msg_type
+ */
+struct htt_resp_hdr {
+ u8 msg_type;
+} __packed;
+
+#define HTT_RESP_HDR_MSG_TYPE_OFFSET 0
+#define HTT_RESP_HDR_MSG_TYPE_MASK 0xff
+#define HTT_RESP_HDR_MSG_TYPE_LSB 0
+
+/* htt_ver_resp - response sent for htt_ver_req */
+struct htt_ver_resp {
+ u8 minor;
+ u8 major;
+ u8 rsvd0;
+} __packed;
+
+struct htt_mgmt_tx_completion {
+ u8 rsvd0;
+ u8 rsvd1;
+ u8 rsvd2;
+ __le32 desc_id;
+ __le32 status;
+} __packed;
+
+#define HTT_RX_INDICATION_INFO0_EXT_TID_MASK (0x3F)
+#define HTT_RX_INDICATION_INFO0_EXT_TID_LSB (0)
+#define HTT_RX_INDICATION_INFO0_FLUSH_VALID (1 << 6)
+#define HTT_RX_INDICATION_INFO0_RELEASE_VALID (1 << 7)
+
+#define HTT_RX_INDICATION_INFO1_FLUSH_START_SEQNO_MASK 0x0000003F
+#define HTT_RX_INDICATION_INFO1_FLUSH_START_SEQNO_LSB 0
+#define HTT_RX_INDICATION_INFO1_FLUSH_END_SEQNO_MASK 0x00000FC0
+#define HTT_RX_INDICATION_INFO1_FLUSH_END_SEQNO_LSB 6
+#define HTT_RX_INDICATION_INFO1_RELEASE_START_SEQNO_MASK 0x0003F000
+#define HTT_RX_INDICATION_INFO1_RELEASE_START_SEQNO_LSB 12
+#define HTT_RX_INDICATION_INFO1_RELEASE_END_SEQNO_MASK 0x00FC0000
+#define HTT_RX_INDICATION_INFO1_RELEASE_END_SEQNO_LSB 18
+#define HTT_RX_INDICATION_INFO1_NUM_MPDU_RANGES_MASK 0xFF000000
+#define HTT_RX_INDICATION_INFO1_NUM_MPDU_RANGES_LSB 24
+
+struct htt_rx_indication_hdr {
+ u8 info0; /* %HTT_RX_INDICATION_INFO0_ */
+ __le16 peer_id;
+ __le32 info1; /* %HTT_RX_INDICATION_INFO1_ */
+} __packed;
+
+#define HTT_RX_INDICATION_INFO0_PHY_ERR_VALID (1 << 0)
+#define HTT_RX_INDICATION_INFO0_LEGACY_RATE_MASK (0x1E)
+#define HTT_RX_INDICATION_INFO0_LEGACY_RATE_LSB (1)
+#define HTT_RX_INDICATION_INFO0_LEGACY_RATE_CCK (1 << 5)
+#define HTT_RX_INDICATION_INFO0_END_VALID (1 << 6)
+#define HTT_RX_INDICATION_INFO0_START_VALID (1 << 7)
+
+#define HTT_RX_INDICATION_INFO1_VHT_SIG_A1_MASK 0x00FFFFFF
+#define HTT_RX_INDICATION_INFO1_VHT_SIG_A1_LSB 0
+#define HTT_RX_INDICATION_INFO1_PREAMBLE_TYPE_MASK 0xFF000000
+#define HTT_RX_INDICATION_INFO1_PREAMBLE_TYPE_LSB 24
+
+#define HTT_RX_INDICATION_INFO2_VHT_SIG_A1_MASK 0x00FFFFFF
+#define HTT_RX_INDICATION_INFO2_VHT_SIG_A1_LSB 0
+#define HTT_RX_INDICATION_INFO2_SERVICE_MASK 0xFF000000
+#define HTT_RX_INDICATION_INFO2_SERVICE_LSB 24
+
+enum htt_rx_legacy_rate {
+ HTT_RX_OFDM_48 = 0,
+ HTT_RX_OFDM_24 = 1,
+ HTT_RX_OFDM_12,
+ HTT_RX_OFDM_6,
+ HTT_RX_OFDM_54,
+ HTT_RX_OFDM_36,
+ HTT_RX_OFDM_18,
+ HTT_RX_OFDM_9,
+
+ /* long preamble */
+ HTT_RX_CCK_11_LP = 0,
+ HTT_RX_CCK_5_5_LP = 1,
+ HTT_RX_CCK_2_LP,
+ HTT_RX_CCK_1_LP,
+ /* short preamble */
+ HTT_RX_CCK_11_SP,
+ HTT_RX_CCK_5_5_SP,
+ HTT_RX_CCK_2_SP
+};
+
+enum htt_rx_legacy_rate_type {
+ HTT_RX_LEGACY_RATE_OFDM = 0,
+ HTT_RX_LEGACY_RATE_CCK
+};
+
+enum htt_rx_preamble_type {
+ HTT_RX_LEGACY = 0x4,
+ HTT_RX_HT = 0x8,
+ HTT_RX_HT_WITH_TXBF = 0x9,
+ HTT_RX_VHT = 0xC,
+ HTT_RX_VHT_WITH_TXBF = 0xD,
+};
+
+/*
+ * Fields: phy_err_valid, phy_err_code, tsf,
+ * usec_timestamp, sub_usec_timestamp
+ * ..are valid only if end_valid == 1.
+ *
+ * Fields: rssi_chains, legacy_rate_type,
+ * legacy_rate_cck, preamble_type, service,
+ * vht_sig_*
+ * ..are valid only if start_valid == 1;
+ */
+struct htt_rx_indication_ppdu {
+ u8 combined_rssi;
+ u8 sub_usec_timestamp;
+ u8 phy_err_code;
+ u8 info0; /* HTT_RX_INDICATION_INFO0_ */
+ struct {
+ u8 pri20_db;
+ u8 ext20_db;
+ u8 ext40_db;
+ u8 ext80_db;
+ } __packed rssi_chains[4];
+ __le32 tsf;
+ __le32 usec_timestamp;
+ __le32 info1; /* HTT_RX_INDICATION_INFO1_ */
+ __le32 info2; /* HTT_RX_INDICATION_INFO2_ */
+} __packed;
+
+enum htt_rx_mpdu_status {
+ HTT_RX_IND_MPDU_STATUS_UNKNOWN = 0x0,
+ HTT_RX_IND_MPDU_STATUS_OK,
+ HTT_RX_IND_MPDU_STATUS_ERR_FCS,
+ HTT_RX_IND_MPDU_STATUS_ERR_DUP,
+ HTT_RX_IND_MPDU_STATUS_ERR_REPLAY,
+ HTT_RX_IND_MPDU_STATUS_ERR_INV_PEER,
+ /* only accept EAPOL frames */
+ HTT_RX_IND_MPDU_STATUS_UNAUTH_PEER,
+ HTT_RX_IND_MPDU_STATUS_OUT_OF_SYNC,
+ /* Non-data in promiscous mode */
+ HTT_RX_IND_MPDU_STATUS_MGMT_CTRL,
+ HTT_RX_IND_MPDU_STATUS_TKIP_MIC_ERR,
+ HTT_RX_IND_MPDU_STATUS_DECRYPT_ERR,
+ HTT_RX_IND_MPDU_STATUS_MPDU_LENGTH_ERR,
+ HTT_RX_IND_MPDU_STATUS_ENCRYPT_REQUIRED_ERR,
+ HTT_RX_IND_MPDU_STATUS_PRIVACY_ERR,
+
+ /*
+ * MISC: discard for unspecified reasons.
+ * Leave this enum value last.
+ */
+ HTT_RX_IND_MPDU_STATUS_ERR_MISC = 0xFF
+};
+
+struct htt_rx_indication_mpdu_range {
+ u8 mpdu_count;
+ u8 mpdu_range_status; /* %htt_rx_mpdu_status */
+ u8 pad0;
+ u8 pad1;
+} __packed;
+
+struct htt_rx_indication_prefix {
+ __le16 fw_rx_desc_bytes;
+ u8 pad0;
+ u8 pad1;
+};
+
+struct htt_rx_indication {
+ struct htt_rx_indication_hdr hdr;
+ struct htt_rx_indication_ppdu ppdu;
+ struct htt_rx_indication_prefix prefix;
+
+ /*
+ * the following fields are both dynamically sized, so
+ * take care addressing them
+ */
+
+ /* the size of this is %fw_rx_desc_bytes */
+ struct fw_rx_desc_base fw_desc;
+
+ /*
+ * %mpdu_ranges starts after &%prefix + roundup(%fw_rx_desc_bytes, 4)
+ * and has %num_mpdu_ranges elements.
+ */
+ struct htt_rx_indication_mpdu_range mpdu_ranges[0];
+} __packed;
+
+static inline struct htt_rx_indication_mpdu_range *
+ htt_rx_ind_get_mpdu_ranges(struct htt_rx_indication *rx_ind)
+{
+ void *ptr = rx_ind;
+
+ ptr += sizeof(rx_ind->hdr)
+ + sizeof(rx_ind->ppdu)
+ + sizeof(rx_ind->prefix)
+ + roundup(__le16_to_cpu(rx_ind->prefix.fw_rx_desc_bytes), 4);
+ return ptr;
+}
+
+enum htt_rx_flush_mpdu_status {
+ HTT_RX_FLUSH_MPDU_DISCARD = 0,
+ HTT_RX_FLUSH_MPDU_REORDER = 1,
+};
+
+/*
+ * htt_rx_flush - discard or reorder given range of mpdus
+ *
+ * Note: host must check if all sequence numbers between
+ * [seq_num_start, seq_num_end-1] are valid.
+ */
+struct htt_rx_flush {
+ __le16 peer_id;
+ u8 tid;
+ u8 rsvd0;
+ u8 mpdu_status; /* %htt_rx_flush_mpdu_status */
+ u8 seq_num_start; /* it is 6 LSBs of 802.11 seq no */
+ u8 seq_num_end; /* it is 6 LSBs of 802.11 seq no */
+};
+
+struct htt_rx_peer_map {
+ u8 vdev_id;
+ __le16 peer_id;
+ u8 addr[6];
+ u8 rsvd0;
+ u8 rsvd1;
+} __packed;
+
+struct htt_rx_peer_unmap {
+ u8 rsvd0;
+ __le16 peer_id;
+} __packed;
+
+enum htt_security_types {
+ HTT_SECURITY_NONE,
+ HTT_SECURITY_WEP128,
+ HTT_SECURITY_WEP104,
+ HTT_SECURITY_WEP40,
+ HTT_SECURITY_TKIP,
+ HTT_SECURITY_TKIP_NOMIC,
+ HTT_SECURITY_AES_CCMP,
+ HTT_SECURITY_WAPI,
+
+ HTT_NUM_SECURITY_TYPES /* keep this last! */
+};
+
+enum htt_security_flags {
+#define HTT_SECURITY_TYPE_MASK 0x7F
+#define HTT_SECURITY_TYPE_LSB 0
+ HTT_SECURITY_IS_UNICAST = 1 << 7
+};
+
+struct htt_security_indication {
+ union {
+ /* dont use bitfields; undefined behaviour */
+ u8 flags; /* %htt_security_flags */
+ struct {
+ u8 security_type:7, /* %htt_security_types */
+ is_unicast:1;
+ } __packed;
+ } __packed;
+ __le16 peer_id;
+ u8 michael_key[8];
+ u8 wapi_rsc[16];
+} __packed;
+
+#define HTT_RX_BA_INFO0_TID_MASK 0x000F
+#define HTT_RX_BA_INFO0_TID_LSB 0
+#define HTT_RX_BA_INFO0_PEER_ID_MASK 0xFFF0
+#define HTT_RX_BA_INFO0_PEER_ID_LSB 4
+
+struct htt_rx_addba {
+ u8 window_size;
+ __le16 info0; /* %HTT_RX_BA_INFO0_ */
+} __packed;
+
+struct htt_rx_delba {
+ u8 rsvd0;
+ __le16 info0; /* %HTT_RX_BA_INFO0_ */
+} __packed;
+
+enum htt_data_tx_status {
+ HTT_DATA_TX_STATUS_OK = 0,
+ HTT_DATA_TX_STATUS_DISCARD = 1,
+ HTT_DATA_TX_STATUS_NO_ACK = 2,
+ HTT_DATA_TX_STATUS_POSTPONE = 3, /* HL only */
+ HTT_DATA_TX_STATUS_DOWNLOAD_FAIL = 128
+};
+
+enum htt_data_tx_flags {
+#define HTT_DATA_TX_STATUS_MASK 0x07
+#define HTT_DATA_TX_STATUS_LSB 0
+#define HTT_DATA_TX_TID_MASK 0x78
+#define HTT_DATA_TX_TID_LSB 3
+ HTT_DATA_TX_TID_INVALID = 1 << 7
+};
+
+#define HTT_TX_COMPL_INV_MSDU_ID 0xFFFF
+
+struct htt_data_tx_completion {
+ union {
+ u8 flags;
+ struct {
+ u8 status:3,
+ tid:4,
+ tid_invalid:1;
+ } __packed;
+ } __packed;
+ u8 num_msdus;
+ u8 rsvd0;
+ __le16 msdus[0]; /* variable length based on %num_msdus */
+} __packed;
+
+struct htt_tx_compl_ind_base {
+ u32 hdr;
+ u16 payload[1/*or more*/];
+} __packed;
+
+struct htt_rc_tx_done_params {
+ u32 rate_code;
+ u32 rate_code_flags;
+ u32 flags;
+ u32 num_enqued; /* 1 for non-AMPDU */
+ u32 num_retries;
+ u32 num_failed; /* for AMPDU */
+ u32 ack_rssi;
+ u32 time_stamp;
+ u32 is_probe;
+};
+
+struct htt_rc_update {
+ u8 vdev_id;
+ __le16 peer_id;
+ u8 addr[6];
+ u8 num_elems;
+ u8 rsvd0;
+ struct htt_rc_tx_done_params params[0]; /* variable length %num_elems */
+} __packed;
+
+/* see htt_rx_indication for similar fields and descriptions */
+struct htt_rx_fragment_indication {
+ union {
+ u8 info0; /* %HTT_RX_FRAG_IND_INFO0_ */
+ struct {
+ u8 ext_tid:5,
+ flush_valid:1;
+ } __packed;
+ } __packed;
+ __le16 peer_id;
+ __le32 info1; /* %HTT_RX_FRAG_IND_INFO1_ */
+ __le16 fw_rx_desc_bytes;
+ __le16 rsvd0;
+
+ u8 fw_msdu_rx_desc[0];
+} __packed;
+
+#define HTT_RX_FRAG_IND_INFO0_EXT_TID_MASK 0x1F
+#define HTT_RX_FRAG_IND_INFO0_EXT_TID_LSB 0
+#define HTT_RX_FRAG_IND_INFO0_FLUSH_VALID_MASK 0x20
+#define HTT_RX_FRAG_IND_INFO0_FLUSH_VALID_LSB 5
+
+#define HTT_RX_FRAG_IND_INFO1_FLUSH_SEQ_NUM_START_MASK 0x0000003F
+#define HTT_RX_FRAG_IND_INFO1_FLUSH_SEQ_NUM_START_LSB 0
+#define HTT_RX_FRAG_IND_INFO1_FLUSH_SEQ_NUM_END_MASK 0x00000FC0
+#define HTT_RX_FRAG_IND_INFO1_FLUSH_SEQ_NUM_END_LSB 6
+
+/*
+ * target -> host test message definition
+ *
+ * The following field definitions describe the format of the test
+ * message sent from the target to the host.
+ * The message consists of a 4-octet header, followed by a variable
+ * number of 32-bit integer values, followed by a variable number
+ * of 8-bit character values.
+ *
+ * |31 16|15 8|7 0|
+ * |-----------------------------------------------------------|
+ * | num chars | num ints | msg type |
+ * |-----------------------------------------------------------|
+ * | int 0 |
+ * |-----------------------------------------------------------|
+ * | int 1 |
+ * |-----------------------------------------------------------|
+ * | ... |
+ * |-----------------------------------------------------------|
+ * | char 3 | char 2 | char 1 | char 0 |
+ * |-----------------------------------------------------------|
+ * | | | ... | char 4 |
+ * |-----------------------------------------------------------|
+ * - MSG_TYPE
+ * Bits 7:0
+ * Purpose: identifies this as a test message
+ * Value: HTT_MSG_TYPE_TEST
+ * - NUM_INTS
+ * Bits 15:8
+ * Purpose: indicate how many 32-bit integers follow the message header
+ * - NUM_CHARS
+ * Bits 31:16
+ * Purpose: indicate how many 8-bit charaters follow the series of integers
+ */
+struct htt_rx_test {
+ u8 num_ints;
+ __le16 num_chars;
+
+ /* payload consists of 2 lists:
+ * a) num_ints * sizeof(__le32)
+ * b) num_chars * sizeof(u8) aligned to 4bytes */
+ u8 payload[0];
+} __packed;
+
+static inline __le32 *htt_rx_test_get_ints(struct htt_rx_test *rx_test)
+{
+ return (__le32 *)rx_test->payload;
+}
+
+static inline u8 *htt_rx_test_get_chars(struct htt_rx_test *rx_test)
+{
+ return rx_test->payload + (rx_test->num_ints * sizeof(__le32));
+}
+
+/*
+ * target -> host packet log message
+ *
+ * The following field definitions describe the format of the packet log
+ * message sent from the target to the host.
+ * The message consists of a 4-octet header,followed by a variable number
+ * of 32-bit character values.
+ *
+ * |31 24|23 16|15 8|7 0|
+ * |-----------------------------------------------------------|
+ * | | | | msg type |
+ * |-----------------------------------------------------------|
+ * | payload |
+ * |-----------------------------------------------------------|
+ * - MSG_TYPE
+ * Bits 7:0
+ * Purpose: identifies this as a test message
+ * Value: HTT_MSG_TYPE_PACKETLOG
+ */
+struct htt_pktlog_msg {
+ u8 pad[3];
+ __le32 payload[1 /* or more */];
+} __packed;
+
+struct htt_dbg_stats_rx_reorder_stats {
+ /* Non QoS MPDUs received */
+ __le32 deliver_non_qos;
+
+ /* MPDUs received in-order */
+ __le32 deliver_in_order;
+
+ /* Flush due to reorder timer expired */
+ __le32 deliver_flush_timeout;
+
+ /* Flush due to move out of window */
+ __le32 deliver_flush_oow;
+
+ /* Flush due to DELBA */
+ __le32 deliver_flush_delba;
+
+ /* MPDUs dropped due to FCS error */
+ __le32 fcs_error;
+
+ /* MPDUs dropped due to monitor mode non-data packet */
+ __le32 mgmt_ctrl;
+
+ /* MPDUs dropped due to invalid peer */
+ __le32 invalid_peer;
+
+ /* MPDUs dropped due to duplication (non aggregation) */
+ __le32 dup_non_aggr;
+
+ /* MPDUs dropped due to processed before */
+ __le32 dup_past;
+
+ /* MPDUs dropped due to duplicate in reorder queue */
+ __le32 dup_in_reorder;
+
+ /* Reorder timeout happened */
+ __le32 reorder_timeout;
+
+ /* invalid bar ssn */
+ __le32 invalid_bar_ssn;
+
+ /* reorder reset due to bar ssn */
+ __le32 ssn_reset;
+};
+
+struct htt_dbg_stats_wal_tx_stats {
+ /* Num HTT cookies queued to dispatch list */
+ __le32 comp_queued;
+
+ /* Num HTT cookies dispatched */
+ __le32 comp_delivered;
+
+ /* Num MSDU queued to WAL */
+ __le32 msdu_enqued;
+
+ /* Num MPDU queue to WAL */
+ __le32 mpdu_enqued;
+
+ /* Num MSDUs dropped by WMM limit */
+ __le32 wmm_drop;
+
+ /* Num Local frames queued */
+ __le32 local_enqued;
+
+ /* Num Local frames done */
+ __le32 local_freed;
+
+ /* Num queued to HW */
+ __le32 hw_queued;
+
+ /* Num PPDU reaped from HW */
+ __le32 hw_reaped;
+
+ /* Num underruns */
+ __le32 underrun;
+
+ /* Num PPDUs cleaned up in TX abort */
+ __le32 tx_abort;
+
+ /* Num MPDUs requed by SW */
+ __le32 mpdus_requed;
+
+ /* excessive retries */
+ __le32 tx_ko;
+
+ /* data hw rate code */
+ __le32 data_rc;
+
+ /* Scheduler self triggers */
+ __le32 self_triggers;
+
+ /* frames dropped due to excessive sw retries */
+ __le32 sw_retry_failure;
+
+ /* illegal rate phy errors */
+ __le32 illgl_rate_phy_err;
+
+ /* wal pdev continous xretry */
+ __le32 pdev_cont_xretry;
+
+ /* wal pdev continous xretry */
+ __le32 pdev_tx_timeout;
+
+ /* wal pdev resets */
+ __le32 pdev_resets;
+
+ __le32 phy_underrun;
+
+ /* MPDU is more than txop limit */
+ __le32 txop_ovf;
+} __packed;
+
+struct htt_dbg_stats_wal_rx_stats {
+ /* Cnts any change in ring routing mid-ppdu */
+ __le32 mid_ppdu_route_change;
+
+ /* Total number of statuses processed */
+ __le32 status_rcvd;
+
+ /* Extra frags on rings 0-3 */
+ __le32 r0_frags;
+ __le32 r1_frags;
+ __le32 r2_frags;
+ __le32 r3_frags;
+
+ /* MSDUs / MPDUs delivered to HTT */
+ __le32 htt_msdus;
+ __le32 htt_mpdus;
+
+ /* MSDUs / MPDUs delivered to local stack */
+ __le32 loc_msdus;
+ __le32 loc_mpdus;
+
+ /* AMSDUs that have more MSDUs than the status ring size */
+ __le32 oversize_amsdu;
+
+ /* Number of PHY errors */
+ __le32 phy_errs;
+
+ /* Number of PHY errors drops */
+ __le32 phy_err_drop;
+
+ /* Number of mpdu errors - FCS, MIC, ENC etc. */
+ __le32 mpdu_errs;
+} __packed;
+
+struct htt_dbg_stats_wal_peer_stats {
+ __le32 dummy; /* REMOVE THIS ONCE REAL PEER STAT COUNTERS ARE ADDED */
+} __packed;
+
+struct htt_dbg_stats_wal_pdev_txrx {
+ struct htt_dbg_stats_wal_tx_stats tx_stats;
+ struct htt_dbg_stats_wal_rx_stats rx_stats;
+ struct htt_dbg_stats_wal_peer_stats peer_stats;
+} __packed;
+
+struct htt_dbg_stats_rx_rate_info {
+ __le32 mcs[10];
+ __le32 sgi[10];
+ __le32 nss[4];
+ __le32 stbc[10];
+ __le32 bw[3];
+ __le32 pream[6];
+ __le32 ldpc;
+ __le32 txbf;
+};
+
+/*
+ * htt_dbg_stats_status -
+ * present - The requested stats have been delivered in full.
+ * This indicates that either the stats information was contained
+ * in its entirety within this message, or else this message
+ * completes the delivery of the requested stats info that was
+ * partially delivered through earlier STATS_CONF messages.
+ * partial - The requested stats have been delivered in part.
+ * One or more subsequent STATS_CONF messages with the same
+ * cookie value will be sent to deliver the remainder of the
+ * information.
+ * error - The requested stats could not be delivered, for example due
+ * to a shortage of memory to construct a message holding the
+ * requested stats.
+ * invalid - The requested stat type is either not recognized, or the
+ * target is configured to not gather the stats type in question.
+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ * series_done - This special value indicates that no further stats info
+ * elements are present within a series of stats info elems
+ * (within a stats upload confirmation message).
+ */
+enum htt_dbg_stats_status {
+ HTT_DBG_STATS_STATUS_PRESENT = 0,
+ HTT_DBG_STATS_STATUS_PARTIAL = 1,
+ HTT_DBG_STATS_STATUS_ERROR = 2,
+ HTT_DBG_STATS_STATUS_INVALID = 3,
+ HTT_DBG_STATS_STATUS_SERIES_DONE = 7
+};
+
+/*
+ * target -> host statistics upload
+ *
+ * The following field definitions describe the format of the HTT target
+ * to host stats upload confirmation message.
+ * The message contains a cookie echoed from the HTT host->target stats
+ * upload request, which identifies which request the confirmation is
+ * for, and a series of tag-length-value stats information elements.
+ * The tag-length header for each stats info element also includes a
+ * status field, to indicate whether the request for the stat type in
+ * question was fully met, partially met, unable to be met, or invalid
+ * (if the stat type in question is disabled in the target).
+ * A special value of all 1's in this status field is used to indicate
+ * the end of the series of stats info elements.
+ *
+ *
+ * |31 16|15 8|7 5|4 0|
+ * |------------------------------------------------------------|
+ * | reserved | msg type |
+ * |------------------------------------------------------------|
+ * | cookie LSBs |
+ * |------------------------------------------------------------|
+ * | cookie MSBs |
+ * |------------------------------------------------------------|
+ * | stats entry length | reserved | S |stat type|
+ * |------------------------------------------------------------|
+ * | |
+ * | type-specific stats info |
+ * | |
+ * |------------------------------------------------------------|
+ * | stats entry length | reserved | S |stat type|
+ * |------------------------------------------------------------|
+ * | |
+ * | type-specific stats info |
+ * | |
+ * |------------------------------------------------------------|
+ * | n/a | reserved | 111 | n/a |
+ * |------------------------------------------------------------|
+ * Header fields:
+ * - MSG_TYPE
+ * Bits 7:0
+ * Purpose: identifies this is a statistics upload confirmation message
+ * Value: 0x9
+ * - COOKIE_LSBS
+ * Bits 31:0
+ * Purpose: Provide a mechanism to match a target->host stats confirmation
+ * message with its preceding host->target stats request message.
+ * Value: LSBs of the opaque cookie specified by the host-side requestor
+ * - COOKIE_MSBS
+ * Bits 31:0
+ * Purpose: Provide a mechanism to match a target->host stats confirmation
+ * message with its preceding host->target stats request message.
+ * Value: MSBs of the opaque cookie specified by the host-side requestor
+ *
+ * Stats Information Element tag-length header fields:
+ * - STAT_TYPE
+ * Bits 4:0
+ * Purpose: identifies the type of statistics info held in the
+ * following information element
+ * Value: htt_dbg_stats_type
+ * - STATUS
+ * Bits 7:5
+ * Purpose: indicate whether the requested stats are present
+ * Value: htt_dbg_stats_status, including a special value (0x7) to mark
+ * the completion of the stats entry series
+ * - LENGTH
+ * Bits 31:16
+ * Purpose: indicate the stats information size
+ * Value: This field specifies the number of bytes of stats information
+ * that follows the element tag-length header.
+ * It is expected but not required that this length is a multiple of
+ * 4 bytes. Even if the length is not an integer multiple of 4, the
+ * subsequent stats entry header will begin on a 4-byte aligned
+ * boundary.
+ */
+
+#define HTT_STATS_CONF_ITEM_INFO_STAT_TYPE_MASK 0x1F
+#define HTT_STATS_CONF_ITEM_INFO_STAT_TYPE_LSB 0
+#define HTT_STATS_CONF_ITEM_INFO_STATUS_MASK 0xE0
+#define HTT_STATS_CONF_ITEM_INFO_STATUS_LSB 5
+
+struct htt_stats_conf_item {
+ union {
+ u8 info;
+ struct {
+ u8 stat_type:5; /* %HTT_DBG_STATS_ */
+ u8 status:3; /* %HTT_DBG_STATS_STATUS_ */
+ } __packed;
+ } __packed;
+ u8 pad;
+ __le16 length;
+ u8 payload[0]; /* roundup(length, 4) long */
+} __packed;
+
+struct htt_stats_conf {
+ u8 pad[3];
+ __le32 cookie_lsb;
+ __le32 cookie_msb;
+
+ /* each item has variable length! */
+ struct htt_stats_conf_item items[0];
+} __packed;
+
+static inline struct htt_stats_conf_item *htt_stats_conf_next_item(
+ const struct htt_stats_conf_item *item)
+{
+ return (void *)item + sizeof(*item) + roundup(item->length, 4);
+}
+/*
+ * host -> target FRAG DESCRIPTOR/MSDU_EXT DESC bank
+ *
+ * The following field definitions describe the format of the HTT host
+ * to target frag_desc/msdu_ext bank configuration message.
+ * The message contains the based address and the min and max id of the
+ * MSDU_EXT/FRAG_DESC that will be used by the HTT to map MSDU DESC and
+ * MSDU_EXT/FRAG_DESC.
+ * HTT will use id in HTT descriptor instead sending the frag_desc_ptr.
+ * For QCA988X HW the firmware will use fragment_desc_ptr but in WIFI2.0
+ * the hardware does the mapping/translation.
+ *
+ * Total banks that can be configured is configured to 16.
+ *
+ * This should be called before any TX has be initiated by the HTT
+ *
+ * |31 16|15 8|7 5|4 0|
+ * |------------------------------------------------------------|
+ * | DESC_SIZE | NUM_BANKS | RES |SWP|pdev| msg type |
+ * |------------------------------------------------------------|
+ * | BANK0_BASE_ADDRESS |
+ * |------------------------------------------------------------|
+ * | ... |
+ * |------------------------------------------------------------|
+ * | BANK15_BASE_ADDRESS |
+ * |------------------------------------------------------------|
+ * | BANK0_MAX_ID | BANK0_MIN_ID |
+ * |------------------------------------------------------------|
+ * | ... |
+ * |------------------------------------------------------------|
+ * | BANK15_MAX_ID | BANK15_MIN_ID |
+ * |------------------------------------------------------------|
+ * Header fields:
+ * - MSG_TYPE
+ * Bits 7:0
+ * Value: 0x6
+ * - BANKx_BASE_ADDRESS
+ * Bits 31:0
+ * Purpose: Provide a mechanism to specify the base address of the MSDU_EXT
+ * bank physical/bus address.
+ * - BANKx_MIN_ID
+ * Bits 15:0
+ * Purpose: Provide a mechanism to specify the min index that needs to
+ * mapped.
+ * - BANKx_MAX_ID
+ * Bits 31:16
+ * Purpose: Provide a mechanism to specify the max index that needs to
+ *
+ */
+struct htt_frag_desc_bank_id {
+ __le16 bank_min_id;
+ __le16 bank_max_id;
+} __packed;
+
+/* real is 16 but it wouldn't fit in the max htt message size
+ * so we use a conservatively safe value for now */
+#define HTT_FRAG_DESC_BANK_MAX 4
+
+#define HTT_FRAG_DESC_BANK_CFG_INFO_PDEV_ID_MASK 0x03
+#define HTT_FRAG_DESC_BANK_CFG_INFO_PDEV_ID_LSB 0
+#define HTT_FRAG_DESC_BANK_CFG_INFO_SWAP (1 << 2)
+
+struct htt_frag_desc_bank_cfg {
+ u8 info; /* HTT_FRAG_DESC_BANK_CFG_INFO_ */
+ u8 num_banks;
+ u8 desc_size;
+ __le32 bank_base_addrs[HTT_FRAG_DESC_BANK_MAX];
+ struct htt_frag_desc_bank_id bank_id[HTT_FRAG_DESC_BANK_MAX];
+} __packed;
+
+union htt_rx_pn_t {
+ /* WEP: 24-bit PN */
+ u32 pn24;
+
+ /* TKIP or CCMP: 48-bit PN */
+ u_int64_t pn48;
+
+ /* WAPI: 128-bit PN */
+ u_int64_t pn128[2];
+};
+
+struct htt_cmd {
+ struct htt_cmd_hdr hdr;
+ union {
+ struct htt_ver_req ver_req;
+ struct htt_mgmt_tx_desc mgmt_tx;
+ struct htt_data_tx_desc data_tx;
+ struct htt_rx_ring_setup rx_setup;
+ struct htt_stats_req stats_req;
+ struct htt_oob_sync_req oob_sync_req;
+ struct htt_aggr_conf aggr_conf;
+ struct htt_frag_desc_bank_cfg frag_desc_bank_cfg;
+ };
+} __packed;
+
+struct htt_resp {
+ struct htt_resp_hdr hdr;
+ union {
+ struct htt_ver_resp ver_resp;
+ struct htt_mgmt_tx_completion mgmt_tx_completion;
+ struct htt_data_tx_completion data_tx_completion;
+ struct htt_rx_indication rx_ind;
+ struct htt_rx_fragment_indication rx_frag_ind;
+ struct htt_rx_peer_map peer_map;
+ struct htt_rx_peer_unmap peer_unmap;
+ struct htt_rx_flush rx_flush;
+ struct htt_rx_addba rx_addba;
+ struct htt_rx_delba rx_delba;
+ struct htt_security_indication security_indication;
+ struct htt_rc_update rc_update;
+ struct htt_rx_test rx_test;
+ struct htt_pktlog_msg pktlog_msg;
+ struct htt_stats_conf stats_conf;
+ };
+} __packed;
+
+
+/*** host side structures follow ***/
+
+struct htt_tx_done {
+ u32 msdu_id;
+ bool discard;
+ bool no_ack;
+};
+
+struct htt_peer_map_event {
+ u8 vdev_id;
+ u16 peer_id;
+ u8 addr[ETH_ALEN];
+};
+
+struct htt_peer_unmap_event {
+ u16 peer_id;
+};
+
+struct htt_rx_info {
+ struct sk_buff *skb;
+ enum htt_rx_mpdu_status status;
+ enum htt_rx_mpdu_encrypt_type encrypt_type;
+ s8 signal;
+ struct {
+ u8 info0;
+ u32 info1;
+ u32 info2;
+ } rate;
+ bool fcs_err;
+};
+
+#define HTT_MAX_NUM_PENDING_TX 512 /* FIXME: find proper value? */
+
+struct ath10k_htt {
+ struct ath10k *ar;
+ enum ath10k_htc_ep_id eid;
+
+ int max_throughput_mbps;
+ u8 target_version_major;
+ u8 target_version_minor;
+ struct completion target_version_received;
+
+ struct {
+ /*
+ * Ring of network buffer objects - This ring is
+ * used exclusively by the host SW. This ring
+ * mirrors the dev_addrs_ring that is shared
+ * between the host SW and the MAC HW. The host SW
+ * uses this netbufs ring to locate the network
+ * buffer objects whose data buffers the HW has
+ * filled.
+ */
+ struct sk_buff **netbufs_ring;
+ /*
+ * Ring of buffer addresses -
+ * This ring holds the "physical" device address of the
+ * rx buffers the host SW provides for the MAC HW to
+ * fill.
+ */
+ __le32 *paddrs_ring;
+
+ /*
+ * Base address of ring, as a "physical" device address
+ * rather than a CPU address.
+ */
+ dma_addr_t base_paddr;
+
+ /* how many elems in the ring (power of 2) */
+ int size;
+
+ /* size - 1 */
+ unsigned size_mask;
+
+ /* how many rx buffers to keep in the ring */
+ int fill_level;
+
+ /* how many rx buffers (full+empty) are in the ring */
+ int fill_cnt;
+
+ /*
+ * alloc_idx - where HTT SW has deposited empty buffers
+ * This is allocated in consistent mem, so that the FW can
+ * read this variable, and program the HW's FW_IDX reg with
+ * the value of this shadow register.
+ */
+ struct {
+ __le32 *vaddr;
+ dma_addr_t paddr;
+ } alloc_idx;
+
+ /* where HTT SW has processed bufs filled by rx MAC DMA */
+ struct {
+ unsigned msdu_payld;
+ } sw_rd_idx;
+
+ /*
+ * refill_retry_timer - timer triggered when the ring is
+ * not refilled to the level expected
+ */
+ struct timer_list refill_retry_timer;
+
+ /* Protects access to all rx ring buffer state variables */
+ spinlock_t lock;
+ } rx_ring;
+
+ unsigned int prefetch_len;
+
+ /* Protects access to %pending_tx, %used_msdu_ids */
+ spinlock_t tx_lock;
+ struct sk_buff *pending_tx[HTT_MAX_NUM_PENDING_TX];
+ DECLARE_BITMAP(used_msdu_ids, HTT_MAX_NUM_PENDING_TX);
+ wait_queue_head_t empty_tx_wq;
+
+ /* set if host-fw communication goes haywire
+ * used to avoid further failures */
+ bool rx_confused;
+};
+
+#define RX_HTT_HDR_STATUS_LEN 64
+
+/* This structure layout is programmed via rx ring setup
+ * so that FW knows how to transfer the rx descriptor to the host.
+ * Buffers like this are placed on the rx ring. */
+struct htt_rx_desc {
+ union {
+ /* This field is filled on the host using the msdu buffer
+ * from htt_rx_indication */
+ struct fw_rx_desc_base fw_desc;
+ u32 pad;
+ } __packed;
+ struct {
+ struct rx_attention attention;
+ struct rx_frag_info frag_info;
+ struct rx_mpdu_start mpdu_start;
+ struct rx_msdu_start msdu_start;
+ struct rx_msdu_end msdu_end;
+ struct rx_mpdu_end mpdu_end;
+ struct rx_ppdu_start ppdu_start;
+ struct rx_ppdu_end ppdu_end;
+ } __packed;
+ u8 rx_hdr_status[RX_HTT_HDR_STATUS_LEN];
+ u8 msdu_payload[0];
+};
+
+#define HTT_RX_DESC_ALIGN 8
+
+#define HTT_MAC_ADDR_LEN 6
+
+/*
+ * HTT_MAX_SEND_QUEUE_DEPTH -
+ * How many packets HTC should allow to accumulate in a send queue
+ * before calling the ep_send_full callback to see whether to retain
+ * or drop packets.
+ * This is not relevant for LL, where tx descriptors should be immediately
+ * downloaded to the target.
+ * This is not very relevant for HL either, since it is anticipated that
+ * the HL tx download scheduler will not work this far in advance - rather,
+ * it will make its decisions just-in-time, so it can be responsive to
+ * changing conditions.
+ * Hence, this queue depth threshold spec is mostly just a formality.
+ */
+#define HTT_MAX_SEND_QUEUE_DEPTH (HTT_MAX_NUM_PENDING_TX)
+
+/*
+ * FIX THIS
+ * Should be: sizeof(struct htt_host_rx_desc) + max rx MSDU size,
+ * rounded up to a cache line size.
+ */
+#define HTT_RX_BUF_SIZE 1920
+#define HTT_RX_MSDU_SIZE (HTT_RX_BUF_SIZE - (int)sizeof(struct htt_rx_desc))
+
+/*
+ * DMA_MAP expects the buffer to be an integral number of cache lines.
+ * Rather than checking the actual cache line size, this code makes a
+ * conservative estimate of what the cache line size could be.
+ */
+#define HTT_LOG2_MAX_CACHE_LINE_SIZE 7 /* 2^7 = 128 */
+#define HTT_MAX_CACHE_LINE_SIZE_MASK ((1 << HTT_LOG2_MAX_CACHE_LINE_SIZE) - 1)
+
+struct ath10k_htt *ath10k_htt_attach(struct ath10k *ar);
+int ath10k_htt_attach_target(struct ath10k_htt *htt);
+void ath10k_htt_detach(struct ath10k_htt *htt);
+
+void ath10k_htt_tx_attach(struct ath10k_htt *htt);
+void ath10k_htt_tx_detach(struct ath10k_htt *htt);
+int ath10k_htt_rx_attach(struct ath10k_htt *htt);
+void ath10k_htt_rx_detach(struct ath10k_htt *htt);
+void ath10k_htt_htc_tx_complete(struct ath10k *ar, struct sk_buff *skb);
+void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb);
+int ath10k_htt_h2t_ver_req_msg(struct ath10k_htt *htt);
+int ath10k_htt_send_rx_ring_cfg_ll(struct ath10k_htt *htt);
+
+int ath10k_htt_tx_alloc_msdu_id(struct ath10k_htt *htt);
+void ath10k_htt_tx_free_msdu_id(struct ath10k_htt *htt, u16 msdu_id);
+int ath10k_htt_mgmt_tx(struct ath10k_htt *htt, struct sk_buff *);
+int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *);
+#endif
^ permalink raw reply related
* [PATCH 01/29] ath10k: add bmi.c
From: Kalle Valo @ 2013-05-15 14:42 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/bmi.c | 295 +++++++++++++++++++++++++++++++++
1 file changed, 295 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/bmi.c
diff --git a/drivers/net/wireless/ath/ath10k/bmi.c b/drivers/net/wireless/ath/ath10k/bmi.c
new file mode 100644
index 0000000..1a2ef51
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/bmi.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "bmi.h"
+#include "hif.h"
+#include "debug.h"
+#include "htc.h"
+
+int ath10k_bmi_done(struct ath10k *ar)
+{
+ struct bmi_cmd cmd;
+ u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.done);
+ int ret;
+
+ if (ar->bmi.done_sent) {
+ ath10k_dbg(ATH10K_DBG_CORE, "%s skipped\n", __func__);
+ return 0;
+ }
+
+ ar->bmi.done_sent = true;
+ cmd.id = __cpu_to_le32(BMI_DONE);
+
+ ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, NULL, NULL);
+ if (ret) {
+ ath10k_warn("unable to write to the device: %d\n", ret);
+ return ret;
+ }
+
+ ath10k_dbg(ATH10K_DBG_CORE, "BMI done\n");
+ return 0;
+}
+
+int ath10k_bmi_get_target_info(struct ath10k *ar,
+ struct bmi_target_info *target_info)
+{
+ struct bmi_cmd cmd;
+ union bmi_resp resp;
+ u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.get_target_info);
+ u32 resplen = sizeof(resp.get_target_info);
+ int ret;
+
+ if (ar->bmi.done_sent) {
+ ath10k_warn("BMI Get Target Info Command disallowed\n");
+ return -EBUSY;
+ }
+
+ cmd.id = __cpu_to_le32(BMI_GET_TARGET_INFO);
+
+ ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, &resp, &resplen);
+ if (ret) {
+ ath10k_warn("unable to get target info from device\n");
+ return ret;
+ }
+
+ if (resplen < sizeof(resp.get_target_info)) {
+ ath10k_warn("invalid get_target_info response length (%d)\n",
+ resplen);
+ return -EIO;
+ }
+
+ target_info->version = __le32_to_cpu(resp.get_target_info.version);
+ target_info->type = __le32_to_cpu(resp.get_target_info.type);
+ return 0;
+}
+
+int ath10k_bmi_read_memory(struct ath10k *ar,
+ u32 address, void *buffer, u32 length)
+{
+ struct bmi_cmd cmd;
+ union bmi_resp resp;
+ u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.read_mem);
+ u32 rxlen;
+ int ret;
+
+ if (ar->bmi.done_sent) {
+ ath10k_warn("command disallowed\n");
+ return -EBUSY;
+ }
+
+ ath10k_dbg(ATH10K_DBG_CORE,
+ "%s: (device: 0x%p, address: 0x%x, length: %d)\n",
+ __func__, ar, address, length);
+
+ while (length) {
+ rxlen = min_t(u32, length, BMI_MAX_DATA_SIZE);
+
+ cmd.id = __cpu_to_le32(BMI_READ_MEMORY);
+ cmd.read_mem.addr = __cpu_to_le32(address);
+ cmd.read_mem.len = __cpu_to_le32(rxlen);
+
+ ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen,
+ &resp, &rxlen);
+ if (ret) {
+ ath10k_warn("unable to read from the device\n");
+ return ret;
+ }
+
+ memcpy(buffer, resp.read_mem.payload, rxlen);
+ address += rxlen;
+ buffer += rxlen;
+ length -= rxlen;
+ }
+
+ return 0;
+}
+
+int ath10k_bmi_write_memory(struct ath10k *ar,
+ u32 address, const void *buffer, u32 length)
+{
+ struct bmi_cmd cmd;
+ u32 hdrlen = sizeof(cmd.id) + sizeof(cmd.write_mem);
+ u32 txlen;
+ int ret;
+
+ if (ar->bmi.done_sent) {
+ ath10k_warn("command disallowed\n");
+ return -EBUSY;
+ }
+
+ ath10k_dbg(ATH10K_DBG_CORE,
+ "%s: (device: 0x%p, address: 0x%x, length: %d)\n",
+ __func__, ar, address, length);
+
+ while (length) {
+ txlen = min(length, BMI_MAX_DATA_SIZE - hdrlen);
+
+ /* copy before roundup to avoid reading beyond buffer*/
+ memcpy(cmd.write_mem.payload, buffer, txlen);
+ txlen = roundup(txlen, 4);
+
+ cmd.id = __cpu_to_le32(BMI_WRITE_MEMORY);
+ cmd.write_mem.addr = __cpu_to_le32(address);
+ cmd.write_mem.len = __cpu_to_le32(txlen);
+
+ ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, hdrlen + txlen,
+ NULL, NULL);
+ if (ret) {
+ ath10k_warn("unable to write to the device\n");
+ return ret;
+ }
+
+ /* fixup roundup() so `length` zeroes out for last chunk */
+ txlen = min(txlen, length);
+
+ address += txlen;
+ buffer += txlen;
+ length -= txlen;
+ }
+
+ return 0;
+}
+
+int ath10k_bmi_execute(struct ath10k *ar, u32 address, u32 *param)
+{
+ struct bmi_cmd cmd;
+ union bmi_resp resp;
+ u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.execute);
+ u32 resplen = sizeof(resp.execute);
+ int ret;
+
+ if (ar->bmi.done_sent) {
+ ath10k_warn("command disallowed\n");
+ return -EBUSY;
+ }
+
+ ath10k_dbg(ATH10K_DBG_CORE,
+ "%s: (device: 0x%p, address: 0x%x, param: %d)\n",
+ __func__, ar, address, *param);
+
+ cmd.id = __cpu_to_le32(BMI_EXECUTE);
+ cmd.execute.addr = __cpu_to_le32(address);
+ cmd.execute.param = __cpu_to_le32(*param);
+
+ ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, &resp, &resplen);
+ if (ret) {
+ ath10k_warn("unable to read from the device\n");
+ return ret;
+ }
+
+ if (resplen < sizeof(resp.execute)) {
+ ath10k_warn("invalid execute response length (%d)\n",
+ resplen);
+ return ret;
+ }
+
+ *param = __le32_to_cpu(resp.execute.result);
+ return 0;
+}
+
+int ath10k_bmi_lz_data(struct ath10k *ar, const void *buffer, u32 length)
+{
+ struct bmi_cmd cmd;
+ u32 hdrlen = sizeof(cmd.id) + sizeof(cmd.lz_data);
+ u32 txlen;
+ int ret;
+
+ if (ar->bmi.done_sent) {
+ ath10k_warn("command disallowed\n");
+ return -EBUSY;
+ }
+
+ while (length) {
+ txlen = min(length, BMI_MAX_DATA_SIZE - hdrlen);
+
+ WARN_ON_ONCE(txlen & 3);
+
+ cmd.id = __cpu_to_le32(BMI_LZ_DATA);
+ cmd.lz_data.len = __cpu_to_le32(txlen);
+ memcpy(cmd.lz_data.payload, buffer, txlen);
+
+ ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, hdrlen + txlen,
+ NULL, NULL);
+ if (ret) {
+ ath10k_warn("unable to write to the device\n");
+ return ret;
+ }
+
+ buffer += txlen;
+ length -= txlen;
+ }
+
+ return 0;
+}
+
+int ath10k_bmi_lz_stream_start(struct ath10k *ar, u32 address)
+{
+ struct bmi_cmd cmd;
+ u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.lz_start);
+ int ret;
+
+ if (ar->bmi.done_sent) {
+ ath10k_warn("command disallowed\n");
+ return -EBUSY;
+ }
+
+ cmd.id = __cpu_to_le32(BMI_LZ_STREAM_START);
+ cmd.lz_start.addr = __cpu_to_le32(address);
+
+ ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, NULL, NULL);
+ if (ret) {
+ ath10k_warn("unable to Start LZ Stream to the device\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+int ath10k_bmi_fast_download(struct ath10k *ar,
+ u32 address, const void *buffer, u32 length)
+{
+ u8 trailer[4] = {};
+ u32 head_len = rounddown(length, 4);
+ u32 trailer_len = length - head_len;
+ int ret;
+
+ ret = ath10k_bmi_lz_stream_start(ar, address);
+ if (ret)
+ return ret;
+
+ /* copy the last word into a zero padded buffer */
+ if (trailer_len > 0)
+ memcpy(trailer, buffer + head_len, trailer_len);
+
+ ret = ath10k_bmi_lz_data(ar, buffer, head_len);
+ if (ret)
+ return ret;
+
+ if (trailer_len > 0)
+ ret = ath10k_bmi_lz_data(ar, trailer, 4);
+
+ if (ret != 0)
+ return ret;
+
+ /*
+ * Close compressed stream and open a new (fake) one.
+ * This serves mainly to flush Target caches.
+ */
+ ret = ath10k_bmi_lz_stream_start(ar, 0x00);
+
+ return ret;
+}
^ permalink raw reply related
* [PATCH 04/29] ath10k: add ce.h
From: Kalle Valo @ 2013-05-15 14:43 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/ce.h | 516 ++++++++++++++++++++++++++++++++++
1 file changed, 516 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/ce.h
diff --git a/drivers/net/wireless/ath/ath10k/ce.h b/drivers/net/wireless/ath/ath10k/ce.h
new file mode 100644
index 0000000..c17f07c
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/ce.h
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _CE_H_
+#define _CE_H_
+
+#include "hif.h"
+
+
+/* Maximum number of Copy Engine's supported */
+#define CE_COUNT_MAX 8
+#define CE_HTT_H2T_MSG_SRC_NENTRIES 2048
+
+/* Descriptor rings must be aligned to this boundary */
+#define CE_DESC_RING_ALIGN 8
+#define CE_SENDLIST_ITEMS_MAX 12
+#define CE_SEND_FLAG_GATHER 0x00010000
+
+/*
+ * Copy Engine support: low-level Target-side Copy Engine API.
+ * This is a hardware access layer used by code that understands
+ * how to use copy engines.
+ */
+
+struct ce_state;
+
+
+/* Copy Engine operational state */
+enum ce_op_state {
+ CE_UNUSED,
+ CE_PAUSED,
+ CE_RUNNING,
+};
+
+#define CE_DESC_FLAGS_GATHER (1 << 0)
+#define CE_DESC_FLAGS_BYTE_SWAP (1 << 1)
+#define CE_DESC_FLAGS_META_DATA_MASK 0xFFFC
+#define CE_DESC_FLAGS_META_DATA_LSB 3
+
+struct ce_desc {
+ __le32 addr;
+ __le16 nbytes;
+ __le16 flags; /* %CE_DESC_FLAGS_ */
+};
+
+/* Copy Engine Ring internal state */
+struct ce_ring_state {
+ /* Number of entries in this ring; must be power of 2 */
+ unsigned int nentries;
+ unsigned int nentries_mask;
+
+ /*
+ * For dest ring, this is the next index to be processed
+ * by software after it was/is received into.
+ *
+ * For src ring, this is the last descriptor that was sent
+ * and completion processed by software.
+ *
+ * Regardless of src or dest ring, this is an invariant
+ * (modulo ring size):
+ * write index >= read index >= sw_index
+ */
+ unsigned int sw_index;
+ /* cached copy */
+ unsigned int write_index;
+ /*
+ * For src ring, this is the next index not yet processed by HW.
+ * This is a cached copy of the real HW index (read index), used
+ * for avoiding reading the HW index register more often than
+ * necessary.
+ * This extends the invariant:
+ * write index >= read index >= hw_index >= sw_index
+ *
+ * For dest ring, this is currently unused.
+ */
+ /* cached copy */
+ unsigned int hw_index;
+
+ /* Start of DMA-coherent area reserved for descriptors */
+ /* Host address space */
+ void *base_addr_owner_space_unaligned;
+ /* CE address space */
+ u32 base_addr_ce_space_unaligned;
+
+ /*
+ * Actual start of descriptors.
+ * Aligned to descriptor-size boundary.
+ * Points into reserved DMA-coherent area, above.
+ */
+ /* Host address space */
+ void *base_addr_owner_space;
+
+ /* CE address space */
+ u32 base_addr_ce_space;
+ /*
+ * Start of shadow copy of descriptors, within regular memory.
+ * Aligned to descriptor-size boundary.
+ */
+ void *shadow_base_unaligned;
+ struct ce_desc *shadow_base;
+
+ void **per_transfer_context;
+};
+
+/* Copy Engine internal state */
+struct ce_state {
+ struct ath10k *ar;
+ unsigned int id;
+
+ unsigned int attr_flags;
+
+ u32 ctrl_addr;
+ enum ce_op_state state;
+
+ void (*send_cb) (struct ce_state *ce_state,
+ void *per_transfer_send_context,
+ u32 buffer,
+ unsigned int nbytes,
+ unsigned int transfer_id);
+ void (*recv_cb) (struct ce_state *ce_state,
+ void *per_transfer_recv_context,
+ u32 buffer,
+ unsigned int nbytes,
+ unsigned int transfer_id,
+ unsigned int flags);
+
+ unsigned int src_sz_max;
+ struct ce_ring_state *src_ring;
+ struct ce_ring_state *dest_ring;
+};
+
+struct ce_sendlist_item {
+ /* e.g. buffer or desc list */
+ dma_addr_t data;
+ union {
+ /* simple buffer */
+ unsigned int nbytes;
+ /* Rx descriptor list */
+ unsigned int ndesc;
+ } u;
+ /* externally-specified flags; OR-ed with internal flags */
+ u32 flags;
+};
+
+struct ce_sendlist {
+ unsigned int num_items;
+ struct ce_sendlist_item item[CE_SENDLIST_ITEMS_MAX];
+};
+
+/* Copy Engine settable attributes */
+struct ce_attr;
+
+/*==================Send====================*/
+
+/* ath10k_ce_send flags */
+#define CE_SEND_FLAG_BYTE_SWAP 1
+
+/*
+ * Queue a source buffer to be sent to an anonymous destination buffer.
+ * ce - which copy engine to use
+ * buffer - address of buffer
+ * nbytes - number of bytes to send
+ * transfer_id - arbitrary ID; reflected to destination
+ * flags - CE_SEND_FLAG_* values
+ * Returns 0 on success; otherwise an error status.
+ *
+ * Note: If no flags are specified, use CE's default data swap mode.
+ *
+ * Implementation note: pushes 1 buffer to Source ring
+ */
+int ath10k_ce_send(struct ce_state *ce_state,
+ void *per_transfer_send_context,
+ u32 buffer,
+ unsigned int nbytes,
+ /* 14 bits */
+ unsigned int transfer_id,
+ unsigned int flags);
+
+void ath10k_ce_send_cb_register(struct ce_state *ce_state,
+ void (*send_cb) (struct ce_state *ce_state,
+ void *transfer_context,
+ u32 buffer,
+ unsigned int nbytes,
+ unsigned int transfer_id),
+ int disable_interrupts);
+
+/* Append a simple buffer (address/length) to a sendlist. */
+void ath10k_ce_sendlist_buf_add(struct ce_sendlist *sendlist,
+ u32 buffer,
+ unsigned int nbytes,
+ /* OR-ed with internal flags */
+ u32 flags);
+
+/*
+ * Queue a "sendlist" of buffers to be sent using gather to a single
+ * anonymous destination buffer
+ * ce - which copy engine to use
+ * sendlist - list of simple buffers to send using gather
+ * transfer_id - arbitrary ID; reflected to destination
+ * Returns 0 on success; otherwise an error status.
+ *
+ * Implemenation note: Pushes multiple buffers with Gather to Source ring.
+ */
+int ath10k_ce_sendlist_send(struct ce_state *ce_state,
+ void *per_transfer_send_context,
+ struct ce_sendlist *sendlist,
+ /* 14 bits */
+ unsigned int transfer_id);
+
+/*==================Recv=======================*/
+
+/*
+ * Make a buffer available to receive. The buffer must be at least of a
+ * minimal size appropriate for this copy engine (src_sz_max attribute).
+ * ce - which copy engine to use
+ * per_transfer_recv_context - context passed back to caller's recv_cb
+ * buffer - address of buffer in CE space
+ * Returns 0 on success; otherwise an error status.
+ *
+ * Implemenation note: Pushes a buffer to Dest ring.
+ */
+int ath10k_ce_recv_buf_enqueue(struct ce_state *ce_state,
+ void *per_transfer_recv_context,
+ u32 buffer);
+
+void ath10k_ce_recv_cb_register(struct ce_state *ce_state,
+ void (*recv_cb) (struct ce_state *ce_state,
+ void *transfer_context,
+ u32 buffer,
+ unsigned int nbytes,
+ unsigned int transfer_id,
+ unsigned int flags));
+
+/* recv flags */
+/* Data is byte-swapped */
+#define CE_RECV_FLAG_SWAPPED 1
+
+/*
+ * Supply data for the next completed unprocessed receive descriptor.
+ * Pops buffer from Dest ring.
+ */
+int ath10k_ce_completed_recv_next(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp,
+ unsigned int *nbytesp,
+ unsigned int *transfer_idp,
+ unsigned int *flagsp);
+/*
+ * Supply data for the next completed unprocessed send descriptor.
+ * Pops 1 completed send buffer from Source ring.
+ */
+int ath10k_ce_completed_send_next(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp,
+ unsigned int *nbytesp,
+ unsigned int *transfer_idp);
+
+/*==================CE Engine Initialization=======================*/
+
+/* Initialize an instance of a CE */
+struct ce_state *ath10k_ce_init(struct ath10k *ar,
+ unsigned int ce_id,
+ const struct ce_attr *attr);
+
+/*==================CE Engine Shutdown=======================*/
+/*
+ * Support clean shutdown by allowing the caller to revoke
+ * receive buffers. Target DMA must be stopped before using
+ * this API.
+ */
+int ath10k_ce_revoke_recv_next(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp);
+
+/*
+ * Support clean shutdown by allowing the caller to cancel
+ * pending sends. Target DMA must be stopped before using
+ * this API.
+ */
+int ath10k_ce_cancel_send_next(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp,
+ unsigned int *nbytesp,
+ unsigned int *transfer_idp);
+
+void ath10k_ce_deinit(struct ce_state *ce_state);
+
+/*==================CE Interrupt Handlers====================*/
+void ath10k_ce_per_engine_service_any(struct ath10k *ar);
+void ath10k_ce_per_engine_service(struct ath10k *ar, unsigned int ce_id);
+void ath10k_ce_disable_interrupts(struct ath10k *ar);
+
+/* ce_attr.flags values */
+/* Use NonSnooping PCIe accesses? */
+#define CE_ATTR_NO_SNOOP 1
+
+/* Byte swap data words */
+#define CE_ATTR_BYTE_SWAP_DATA 2
+
+/* Swizzle descriptors? */
+#define CE_ATTR_SWIZZLE_DESCRIPTORS 4
+
+/* no interrupt on copy completion */
+#define CE_ATTR_DIS_INTR 8
+
+/* Attributes of an instance of a Copy Engine */
+struct ce_attr {
+ /* CE_ATTR_* values */
+ unsigned int flags;
+
+ /* currently not in use */
+ unsigned int priority;
+
+ /* #entries in source ring - Must be a power of 2 */
+ unsigned int src_nentries;
+
+ /*
+ * Max source send size for this CE.
+ * This is also the minimum size of a destination buffer.
+ */
+ unsigned int src_sz_max;
+
+ /* #entries in destination ring - Must be a power of 2 */
+ unsigned int dest_nentries;
+
+ /* Future use */
+ void *reserved;
+};
+
+/*
+ * When using sendlist_send to transfer multiple buffer fragments, the
+ * transfer context of each fragment, except last one, will be filled
+ * with CE_SENDLIST_ITEM_CTXT. ce_completed_send will return success for
+ * each fragment done with send and the transfer context would be
+ * CE_SENDLIST_ITEM_CTXT. Upper layer could use this to identify the
+ * status of a send completion.
+ */
+#define CE_SENDLIST_ITEM_CTXT ((void *)0xcecebeef)
+
+#define SR_BA_ADDRESS 0x0000
+#define SR_SIZE_ADDRESS 0x0004
+#define DR_BA_ADDRESS 0x0008
+#define DR_SIZE_ADDRESS 0x000c
+#define CE_CMD_ADDRESS 0x0018
+
+#define CE_CTRL1_DST_RING_BYTE_SWAP_EN_MSB 17
+#define CE_CTRL1_DST_RING_BYTE_SWAP_EN_LSB 17
+#define CE_CTRL1_DST_RING_BYTE_SWAP_EN_MASK 0x00020000
+#define CE_CTRL1_DST_RING_BYTE_SWAP_EN_SET(x) \
+ (((0 | (x)) << CE_CTRL1_DST_RING_BYTE_SWAP_EN_LSB) & \
+ CE_CTRL1_DST_RING_BYTE_SWAP_EN_MASK)
+
+#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MSB 16
+#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_LSB 16
+#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MASK 0x00010000
+#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_GET(x) \
+ (((x) & CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MASK) >> \
+ CE_CTRL1_SRC_RING_BYTE_SWAP_EN_LSB)
+#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_SET(x) \
+ (((0 | (x)) << CE_CTRL1_SRC_RING_BYTE_SWAP_EN_LSB) & \
+ CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MASK)
+
+#define CE_CTRL1_DMAX_LENGTH_MSB 15
+#define CE_CTRL1_DMAX_LENGTH_LSB 0
+#define CE_CTRL1_DMAX_LENGTH_MASK 0x0000ffff
+#define CE_CTRL1_DMAX_LENGTH_GET(x) \
+ (((x) & CE_CTRL1_DMAX_LENGTH_MASK) >> CE_CTRL1_DMAX_LENGTH_LSB)
+#define CE_CTRL1_DMAX_LENGTH_SET(x) \
+ (((0 | (x)) << CE_CTRL1_DMAX_LENGTH_LSB) & CE_CTRL1_DMAX_LENGTH_MASK)
+
+#define CE_CTRL1_ADDRESS 0x0010
+#define CE_CTRL1_HW_MASK 0x0007ffff
+#define CE_CTRL1_SW_MASK 0x0007ffff
+#define CE_CTRL1_HW_WRITE_MASK 0x00000000
+#define CE_CTRL1_SW_WRITE_MASK 0x0007ffff
+#define CE_CTRL1_RSTMASK 0xffffffff
+#define CE_CTRL1_RESET 0x00000080
+
+#define CE_CMD_HALT_STATUS_MSB 3
+#define CE_CMD_HALT_STATUS_LSB 3
+#define CE_CMD_HALT_STATUS_MASK 0x00000008
+#define CE_CMD_HALT_STATUS_GET(x) \
+ (((x) & CE_CMD_HALT_STATUS_MASK) >> CE_CMD_HALT_STATUS_LSB)
+#define CE_CMD_HALT_STATUS_SET(x) \
+ (((0 | (x)) << CE_CMD_HALT_STATUS_LSB) & CE_CMD_HALT_STATUS_MASK)
+#define CE_CMD_HALT_STATUS_RESET 0
+#define CE_CMD_HALT_MSB 0
+#define CE_CMD_HALT_MASK 0x00000001
+
+#define HOST_IE_COPY_COMPLETE_MSB 0
+#define HOST_IE_COPY_COMPLETE_LSB 0
+#define HOST_IE_COPY_COMPLETE_MASK 0x00000001
+#define HOST_IE_COPY_COMPLETE_GET(x) \
+ (((x) & HOST_IE_COPY_COMPLETE_MASK) >> HOST_IE_COPY_COMPLETE_LSB)
+#define HOST_IE_COPY_COMPLETE_SET(x) \
+ (((0 | (x)) << HOST_IE_COPY_COMPLETE_LSB) & HOST_IE_COPY_COMPLETE_MASK)
+#define HOST_IE_COPY_COMPLETE_RESET 0
+#define HOST_IE_ADDRESS 0x002c
+
+#define HOST_IS_DST_RING_LOW_WATERMARK_MASK 0x00000010
+#define HOST_IS_DST_RING_HIGH_WATERMARK_MASK 0x00000008
+#define HOST_IS_SRC_RING_LOW_WATERMARK_MASK 0x00000004
+#define HOST_IS_SRC_RING_HIGH_WATERMARK_MASK 0x00000002
+#define HOST_IS_COPY_COMPLETE_MASK 0x00000001
+#define HOST_IS_ADDRESS 0x0030
+
+#define MISC_IE_ADDRESS 0x0034
+
+#define MISC_IS_AXI_ERR_MASK 0x00000400
+
+#define MISC_IS_DST_ADDR_ERR_MASK 0x00000200
+#define MISC_IS_SRC_LEN_ERR_MASK 0x00000100
+#define MISC_IS_DST_MAX_LEN_VIO_MASK 0x00000080
+#define MISC_IS_DST_RING_OVERFLOW_MASK 0x00000040
+#define MISC_IS_SRC_RING_OVERFLOW_MASK 0x00000020
+
+#define MISC_IS_ADDRESS 0x0038
+
+#define SR_WR_INDEX_ADDRESS 0x003c
+
+#define DST_WR_INDEX_ADDRESS 0x0040
+
+#define CURRENT_SRRI_ADDRESS 0x0044
+
+#define CURRENT_DRRI_ADDRESS 0x0048
+
+#define SRC_WATERMARK_LOW_MSB 31
+#define SRC_WATERMARK_LOW_LSB 16
+#define SRC_WATERMARK_LOW_MASK 0xffff0000
+#define SRC_WATERMARK_LOW_GET(x) \
+ (((x) & SRC_WATERMARK_LOW_MASK) >> SRC_WATERMARK_LOW_LSB)
+#define SRC_WATERMARK_LOW_SET(x) \
+ (((0 | (x)) << SRC_WATERMARK_LOW_LSB) & SRC_WATERMARK_LOW_MASK)
+#define SRC_WATERMARK_LOW_RESET 0
+#define SRC_WATERMARK_HIGH_MSB 15
+#define SRC_WATERMARK_HIGH_LSB 0
+#define SRC_WATERMARK_HIGH_MASK 0x0000ffff
+#define SRC_WATERMARK_HIGH_GET(x) \
+ (((x) & SRC_WATERMARK_HIGH_MASK) >> SRC_WATERMARK_HIGH_LSB)
+#define SRC_WATERMARK_HIGH_SET(x) \
+ (((0 | (x)) << SRC_WATERMARK_HIGH_LSB) & SRC_WATERMARK_HIGH_MASK)
+#define SRC_WATERMARK_HIGH_RESET 0
+#define SRC_WATERMARK_ADDRESS 0x004c
+
+#define DST_WATERMARK_LOW_LSB 16
+#define DST_WATERMARK_LOW_MASK 0xffff0000
+#define DST_WATERMARK_LOW_SET(x) \
+ (((0 | (x)) << DST_WATERMARK_LOW_LSB) & DST_WATERMARK_LOW_MASK)
+#define DST_WATERMARK_LOW_RESET 0
+#define DST_WATERMARK_HIGH_MSB 15
+#define DST_WATERMARK_HIGH_LSB 0
+#define DST_WATERMARK_HIGH_MASK 0x0000ffff
+#define DST_WATERMARK_HIGH_GET(x) \
+ (((x) & DST_WATERMARK_HIGH_MASK) >> DST_WATERMARK_HIGH_LSB)
+#define DST_WATERMARK_HIGH_SET(x) \
+ (((0 | (x)) << DST_WATERMARK_HIGH_LSB) & DST_WATERMARK_HIGH_MASK)
+#define DST_WATERMARK_HIGH_RESET 0
+#define DST_WATERMARK_ADDRESS 0x0050
+
+
+static inline u32 ath10k_ce_base_address(unsigned int ce_id)
+{
+ return CE0_BASE_ADDRESS + (CE1_BASE_ADDRESS - CE0_BASE_ADDRESS) * ce_id;
+}
+
+#define CE_WATERMARK_MASK (HOST_IS_SRC_RING_LOW_WATERMARK_MASK | \
+ HOST_IS_SRC_RING_HIGH_WATERMARK_MASK | \
+ HOST_IS_DST_RING_LOW_WATERMARK_MASK | \
+ HOST_IS_DST_RING_HIGH_WATERMARK_MASK)
+
+#define CE_ERROR_MASK (MISC_IS_AXI_ERR_MASK | \
+ MISC_IS_DST_ADDR_ERR_MASK | \
+ MISC_IS_SRC_LEN_ERR_MASK | \
+ MISC_IS_DST_MAX_LEN_VIO_MASK | \
+ MISC_IS_DST_RING_OVERFLOW_MASK | \
+ MISC_IS_SRC_RING_OVERFLOW_MASK)
+
+#define CE_SRC_RING_TO_DESC(baddr, idx) \
+ (&(((struct ce_desc *)baddr)[idx]))
+
+#define CE_DEST_RING_TO_DESC(baddr, idx) \
+ (&(((struct ce_desc *)baddr)[idx]))
+
+/* Ring arithmetic (modulus number of entries in ring, which is a pwr of 2). */
+#define CE_RING_DELTA(nentries_mask, fromidx, toidx) \
+ (((int)(toidx)-(int)(fromidx)) & (nentries_mask))
+
+#define CE_RING_IDX_INCR(nentries_mask, idx) (((idx) + 1) & (nentries_mask))
+
+#define CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_LSB 8
+#define CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_MASK 0x0000ff00
+#define CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_GET(x) \
+ (((x) & CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_MASK) >> \
+ CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_LSB)
+#define CE_WRAPPER_INTERRUPT_SUMMARY_ADDRESS 0x0000
+
+#define CE_INTERRUPT_SUMMARY(ar) \
+ CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_GET( \
+ ath10k_pci_read32((ar), CE_WRAPPER_BASE_ADDRESS + \
+ CE_WRAPPER_INTERRUPT_SUMMARY_ADDRESS))
+
+#endif /* _CE_H_ */
^ permalink raw reply related
* [PATCH 24/29] ath10k: add trace.h
From: Kalle Valo @ 2013-05-15 14:46 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/trace.h | 170 +++++++++++++++++++++++++++++++
1 file changed, 170 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/trace.h
diff --git a/drivers/net/wireless/ath/ath10k/trace.h b/drivers/net/wireless/ath/ath10k/trace.h
new file mode 100644
index 0000000..85e806b
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/trace.h
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if !defined(_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
+
+#include <linux/tracepoint.h>
+
+#define _TRACE_H_
+
+/* create empty functions when tracing is disabled */
+#if !defined(CONFIG_ATH10K_TRACING)
+#undef TRACE_EVENT
+#define TRACE_EVENT(name, proto, ...) \
+static inline void trace_ ## name(proto) {}
+#undef DECLARE_EVENT_CLASS
+#define DECLARE_EVENT_CLASS(...)
+#undef DEFINE_EVENT
+#define DEFINE_EVENT(evt_class, name, proto, ...) \
+static inline void trace_ ## name(proto) {}
+#endif /* !CONFIG_ATH10K_TRACING || __CHECKER__ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM ath10k
+
+#define ATH10K_MSG_MAX 200
+
+DECLARE_EVENT_CLASS(ath10k_log_event,
+ TP_PROTO(struct va_format *vaf),
+ TP_ARGS(vaf),
+ TP_STRUCT__entry(
+ __dynamic_array(char, msg, ATH10K_MSG_MAX)
+ ),
+ TP_fast_assign(
+ WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg),
+ ATH10K_MSG_MAX,
+ vaf->fmt,
+ *vaf->va) >= ATH10K_MSG_MAX);
+ ),
+ TP_printk("%s", __get_str(msg))
+);
+
+DEFINE_EVENT(ath10k_log_event, ath10k_log_err,
+ TP_PROTO(struct va_format *vaf),
+ TP_ARGS(vaf)
+);
+
+DEFINE_EVENT(ath10k_log_event, ath10k_log_warn,
+ TP_PROTO(struct va_format *vaf),
+ TP_ARGS(vaf)
+);
+
+DEFINE_EVENT(ath10k_log_event, ath10k_log_info,
+ TP_PROTO(struct va_format *vaf),
+ TP_ARGS(vaf)
+);
+
+TRACE_EVENT(ath10k_log_dbg,
+ TP_PROTO(unsigned int level, struct va_format *vaf),
+ TP_ARGS(level, vaf),
+ TP_STRUCT__entry(
+ __field(unsigned int, level)
+ __dynamic_array(char, msg, ATH10K_MSG_MAX)
+ ),
+ TP_fast_assign(
+ __entry->level = level;
+ WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg),
+ ATH10K_MSG_MAX,
+ vaf->fmt,
+ *vaf->va) >= ATH10K_MSG_MAX);
+ ),
+ TP_printk("%s", __get_str(msg))
+);
+
+TRACE_EVENT(ath10k_log_dbg_dump,
+ TP_PROTO(const char *msg, const char *prefix,
+ const void *buf, size_t buf_len),
+
+ TP_ARGS(msg, prefix, buf, buf_len),
+
+ TP_STRUCT__entry(
+ __string(msg, msg)
+ __string(prefix, prefix)
+ __field(size_t, buf_len)
+ __dynamic_array(u8, buf, buf_len)
+ ),
+
+ TP_fast_assign(
+ __assign_str(msg, msg);
+ __assign_str(prefix, prefix);
+ __entry->buf_len = buf_len;
+ memcpy(__get_dynamic_array(buf), buf, buf_len);
+ ),
+
+ TP_printk(
+ "%s/%s\n", __get_str(prefix), __get_str(msg)
+ )
+);
+
+TRACE_EVENT(ath10k_wmi_cmd,
+ TP_PROTO(int id, void *buf, size_t buf_len),
+
+ TP_ARGS(id, buf, buf_len),
+
+ TP_STRUCT__entry(
+ __field(unsigned int, id)
+ __field(size_t, buf_len)
+ __dynamic_array(u8, buf, buf_len)
+ ),
+
+ TP_fast_assign(
+ __entry->id = id;
+ __entry->buf_len = buf_len;
+ memcpy(__get_dynamic_array(buf), buf, buf_len);
+ ),
+
+ TP_printk(
+ "id %d len %zu",
+ __entry->id,
+ __entry->buf_len
+ )
+);
+
+TRACE_EVENT(ath10k_wmi_event,
+ TP_PROTO(int id, void *buf, size_t buf_len),
+
+ TP_ARGS(id, buf, buf_len),
+
+ TP_STRUCT__entry(
+ __field(unsigned int, id)
+ __field(size_t, buf_len)
+ __dynamic_array(u8, buf, buf_len)
+ ),
+
+ TP_fast_assign(
+ __entry->id = id;
+ __entry->buf_len = buf_len;
+ memcpy(__get_dynamic_array(buf), buf, buf_len);
+ ),
+
+ TP_printk(
+ "id %d len %zu",
+ __entry->id,
+ __entry->buf_len
+ )
+);
+
+#endif /* _TRACE_H_ || TRACE_HEADER_MULTI_READ*/
+
+/* we don't want to use include/trace/events */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
^ permalink raw reply related
* [PATCH 16/29] ath10k: add hw.h
From: Kalle Valo @ 2013-05-15 14:45 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/hw.h | 304 ++++++++++++++++++++++++++++++++++
1 file changed, 304 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/hw.h
diff --git a/drivers/net/wireless/ath/ath10k/hw.h b/drivers/net/wireless/ath/ath10k/hw.h
new file mode 100644
index 0000000..44ed5af
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/hw.h
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _HW_H_
+#define _HW_H_
+
+#include "targaddrs.h"
+
+/* Supported FW version */
+#define SUPPORTED_FW_MAJOR 1
+#define SUPPORTED_FW_MINOR 0
+#define SUPPORTED_FW_RELEASE 0
+#define SUPPORTED_FW_BUILD 629
+
+/* QCA988X 1.0 definitions */
+#define QCA988X_HW_1_0_VERSION 0x4000002c
+#define QCA988X_HW_1_0_FW_DIR "ath10k/QCA988X/hw1.0"
+#define QCA988X_HW_1_0_FW_FILE "firmware.bin"
+#define QCA988X_HW_1_0_OTP_FILE "otp.bin"
+#define QCA988X_HW_1_0_BOARD_DATA_FILE "board.bin"
+#define QCA988X_HW_1_0_PATCH_LOAD_ADDR 0x1234
+
+/* QCA988X 2.0 definitions */
+#define QCA988X_HW_2_0_VERSION 0x4100016c
+#define QCA988X_HW_2_0_FW_DIR "ath10k/QCA988X/hw2.0"
+#define QCA988X_HW_2_0_FW_FILE "firmware.bin"
+#define QCA988X_HW_2_0_OTP_FILE "otp.bin"
+#define QCA988X_HW_2_0_BOARD_DATA_FILE "board.bin"
+#define QCA988X_HW_2_0_PATCH_LOAD_ADDR 0x1234
+
+/* Known pecularities:
+ * - current FW doesn't support raw rx mode (last tested v599)
+ * - current FW dumps upon raw tx mode (last tested v599)
+ * - raw appears in nwifi decap, raw and nwifi appear in ethernet decap
+ * - raw have FCS, nwifi doesn't
+ * - ethernet frames have 802.11 header decapped and parts (base hdr, cipher
+ * param, llc/snap) are aligned to 4byte boundaries each */
+enum ath10k_hw_txrx_mode {
+ ATH10K_HW_TXRX_RAW = 0,
+ ATH10K_HW_TXRX_NATIVE_WIFI = 1,
+ ATH10K_HW_TXRX_ETHERNET = 2,
+};
+
+enum ath10k_mcast2ucast_mode {
+ ATH10K_MCAST2UCAST_DISABLED = 0,
+ ATH10K_MCAST2UCAST_ENABLED = 1,
+};
+
+#define TARGET_NUM_VDEVS 8
+#define TARGET_NUM_PEER_AST 2
+#define TARGET_NUM_WDS_ENTRIES 32
+#define TARGET_DMA_BURST_SIZE 0
+#define TARGET_MAC_AGGR_DELIM 0
+#define TARGET_AST_SKID_LIMIT 16
+#define TARGET_NUM_PEERS 16
+#define TARGET_NUM_OFFLOAD_PEERS 0
+#define TARGET_NUM_OFFLOAD_REORDER_BUFS 0
+#define TARGET_NUM_PEER_KEYS 2
+#define TARGET_NUM_TIDS (2 * ((TARGET_NUM_PEERS) + (TARGET_NUM_VDEVS)))
+#define TARGET_TX_CHAIN_MASK (BIT(0) | BIT(1) | BIT(2))
+#define TARGET_RX_CHAIN_MASK (BIT(0) | BIT(1) | BIT(2))
+#define TARGET_RX_TIMEOUT_LO_PRI 100
+#define TARGET_RX_TIMEOUT_HI_PRI 40
+#define TARGET_RX_DECAP_MODE ATH10K_HW_TXRX_ETHERNET
+#define TARGET_SCAN_MAX_PENDING_REQS 4
+#define TARGET_BMISS_OFFLOAD_MAX_VDEV 3
+#define TARGET_ROAM_OFFLOAD_MAX_VDEV 3
+#define TARGET_ROAM_OFFLOAD_MAX_AP_PROFILES 8
+#define TARGET_GTK_OFFLOAD_MAX_VDEV 3
+#define TARGET_NUM_MCAST_GROUPS 0
+#define TARGET_NUM_MCAST_TABLE_ELEMS 0
+#define TARGET_MCAST2UCAST_MODE ATH10K_MCAST2UCAST_DISABLED
+#define TARGET_TX_DBG_LOG_SIZE 1024
+#define TARGET_RX_SKIP_DEFRAG_TIMEOUT_DUP_DETECTION_CHECK 0
+#define TARGET_VOW_CONFIG 0
+#define TARGET_NUM_MSDU_DESC (1024 + 400)
+#define TARGET_MAX_FRAG_ENTRIES 0
+
+
+/* Number of Copy Engines supported */
+#define CE_COUNT 8
+
+/*
+ * Total number of PCIe MSI interrupts requested for all interrupt sources.
+ * PCIe standard forces this to be a power of 2.
+ * Some Host OS's limit MSI requests that can be granted to 8
+ * so for now we abide by this limit and avoid requesting more
+ * than that.
+ */
+#define MSI_NUM_REQUEST_LOG2 3
+#define MSI_NUM_REQUEST (1<<MSI_NUM_REQUEST_LOG2)
+
+/*
+ * Granted MSIs are assigned as follows:
+ * Firmware uses the first
+ * Remaining MSIs, if any, are used by Copy Engines
+ * This mapping is known to both Target firmware and Host software.
+ * It may be changed as long as Host and Target are kept in sync.
+ */
+/* MSI for firmware (errors, etc.) */
+#define MSI_ASSIGN_FW 0
+
+/* MSIs for Copy Engines */
+#define MSI_ASSIGN_CE_INITIAL 1
+#define MSI_ASSIGN_CE_MAX 7
+
+/* as of IP3.7.1 */
+#define RTC_STATE_V_ON 3
+
+#define RTC_STATE_COLD_RESET_MASK 0x00000400
+#define RTC_STATE_V_LSB 0
+#define RTC_STATE_V_MASK 0x00000007
+#define RTC_STATE_ADDRESS 0x0000
+#define PCIE_SOC_WAKE_V_MASK 0x00000001
+#define PCIE_SOC_WAKE_ADDRESS 0x0004
+#define PCIE_SOC_WAKE_RESET 0x00000000
+#define SOC_GLOBAL_RESET_ADDRESS 0x0008
+
+#define RTC_SOC_BASE_ADDRESS 0x00004000
+#define RTC_WMAC_BASE_ADDRESS 0x00005000
+#define MAC_COEX_BASE_ADDRESS 0x00006000
+#define BT_COEX_BASE_ADDRESS 0x00007000
+#define SOC_PCIE_BASE_ADDRESS 0x00008000
+#define SOC_CORE_BASE_ADDRESS 0x00009000
+#define WLAN_UART_BASE_ADDRESS 0x0000c000
+#define WLAN_SI_BASE_ADDRESS 0x00010000
+#define WLAN_GPIO_BASE_ADDRESS 0x00014000
+#define WLAN_ANALOG_INTF_BASE_ADDRESS 0x0001c000
+#define WLAN_MAC_BASE_ADDRESS 0x00020000
+#define EFUSE_BASE_ADDRESS 0x00030000
+#define FPGA_REG_BASE_ADDRESS 0x00039000
+#define WLAN_UART2_BASE_ADDRESS 0x00054c00
+#define CE_WRAPPER_BASE_ADDRESS 0x00057000
+#define CE0_BASE_ADDRESS 0x00057400
+#define CE1_BASE_ADDRESS 0x00057800
+#define CE2_BASE_ADDRESS 0x00057c00
+#define CE3_BASE_ADDRESS 0x00058000
+#define CE4_BASE_ADDRESS 0x00058400
+#define CE5_BASE_ADDRESS 0x00058800
+#define CE6_BASE_ADDRESS 0x00058c00
+#define CE7_BASE_ADDRESS 0x00059000
+#define DBI_BASE_ADDRESS 0x00060000
+#define WLAN_ANALOG_INTF_PCIE_BASE_ADDRESS 0x0006c000
+#define PCIE_LOCAL_BASE_ADDRESS 0x00080000
+
+#define SOC_RESET_CONTROL_OFFSET 0x00000000
+#define SOC_RESET_CONTROL_SI0_RST_MASK 0x00000001
+#define SOC_CPU_CLOCK_OFFSET 0x00000020
+#define SOC_CPU_CLOCK_STANDARD_LSB 0
+#define SOC_CPU_CLOCK_STANDARD_MASK 0x00000003
+#define SOC_CLOCK_CONTROL_OFFSET 0x00000028
+#define SOC_CLOCK_CONTROL_SI0_CLK_MASK 0x00000001
+#define SOC_SYSTEM_SLEEP_OFFSET 0x000000c4
+#define SOC_LPO_CAL_OFFSET 0x000000e0
+#define SOC_LPO_CAL_ENABLE_LSB 20
+#define SOC_LPO_CAL_ENABLE_MASK 0x00100000
+
+#define WLAN_RESET_CONTROL_COLD_RST_MASK 0x00000008
+#define WLAN_RESET_CONTROL_WARM_RST_MASK 0x00000004
+#define WLAN_SYSTEM_SLEEP_DISABLE_LSB 0
+#define WLAN_SYSTEM_SLEEP_DISABLE_MASK 0x00000001
+
+#define WLAN_GPIO_PIN0_ADDRESS 0x00000028
+#define WLAN_GPIO_PIN0_CONFIG_MASK 0x00007800
+#define WLAN_GPIO_PIN1_ADDRESS 0x0000002c
+#define WLAN_GPIO_PIN1_CONFIG_MASK 0x00007800
+#define WLAN_GPIO_PIN10_ADDRESS 0x00000050
+#define WLAN_GPIO_PIN11_ADDRESS 0x00000054
+#define WLAN_GPIO_PIN12_ADDRESS 0x00000058
+#define WLAN_GPIO_PIN13_ADDRESS 0x0000005c
+
+#define CLOCK_GPIO_OFFSET 0xffffffff
+#define CLOCK_GPIO_BT_CLK_OUT_EN_LSB 0
+#define CLOCK_GPIO_BT_CLK_OUT_EN_MASK 0
+
+#define SI_CONFIG_OFFSET 0x00000000
+#define SI_CONFIG_BIDIR_OD_DATA_LSB 18
+#define SI_CONFIG_BIDIR_OD_DATA_MASK 0x00040000
+#define SI_CONFIG_I2C_LSB 16
+#define SI_CONFIG_I2C_MASK 0x00010000
+#define SI_CONFIG_POS_SAMPLE_LSB 7
+#define SI_CONFIG_POS_SAMPLE_MASK 0x00000080
+#define SI_CONFIG_INACTIVE_DATA_LSB 5
+#define SI_CONFIG_INACTIVE_DATA_MASK 0x00000020
+#define SI_CONFIG_INACTIVE_CLK_LSB 4
+#define SI_CONFIG_INACTIVE_CLK_MASK 0x00000010
+#define SI_CONFIG_DIVIDER_LSB 0
+#define SI_CONFIG_DIVIDER_MASK 0x0000000f
+#define SI_CS_OFFSET 0x00000004
+#define SI_CS_DONE_ERR_MASK 0x00000400
+#define SI_CS_DONE_INT_MASK 0x00000200
+#define SI_CS_START_LSB 8
+#define SI_CS_START_MASK 0x00000100
+#define SI_CS_RX_CNT_LSB 4
+#define SI_CS_RX_CNT_MASK 0x000000f0
+#define SI_CS_TX_CNT_LSB 0
+#define SI_CS_TX_CNT_MASK 0x0000000f
+
+#define SI_TX_DATA0_OFFSET 0x00000008
+#define SI_TX_DATA1_OFFSET 0x0000000c
+#define SI_RX_DATA0_OFFSET 0x00000010
+#define SI_RX_DATA1_OFFSET 0x00000014
+
+#define CORE_CTRL_CPU_INTR_MASK 0x00002000
+#define CORE_CTRL_ADDRESS 0x0000
+#define PCIE_INTR_ENABLE_ADDRESS 0x0008
+#define PCIE_INTR_CLR_ADDRESS 0x0014
+#define SCRATCH_3_ADDRESS 0x0030
+
+/* Firmware indications to the Host via SCRATCH_3 register. */
+#define FW_INDICATOR_ADDRESS (SOC_CORE_BASE_ADDRESS + SCRATCH_3_ADDRESS)
+#define FW_IND_EVENT_PENDING 1
+#define FW_IND_INITIALIZED 2
+
+/* HOST_REG interrupt from firmware */
+#define PCIE_INTR_FIRMWARE_MASK 0x00000400
+#define PCIE_INTR_CE_MASK_ALL 0x0007f800
+
+#define DRAM_BASE_ADDRESS 0x00400000
+
+#define MISSING 0
+
+#define SYSTEM_SLEEP_OFFSET SOC_SYSTEM_SLEEP_OFFSET
+#define WLAN_SYSTEM_SLEEP_OFFSET SOC_SYSTEM_SLEEP_OFFSET
+#define WLAN_RESET_CONTROL_OFFSET SOC_RESET_CONTROL_OFFSET
+#define CLOCK_CONTROL_OFFSET SOC_CLOCK_CONTROL_OFFSET
+#define CLOCK_CONTROL_SI0_CLK_MASK SOC_CLOCK_CONTROL_SI0_CLK_MASK
+#define RESET_CONTROL_MBOX_RST_MASK MISSING
+#define RESET_CONTROL_SI0_RST_MASK SOC_RESET_CONTROL_SI0_RST_MASK
+#define GPIO_BASE_ADDRESS WLAN_GPIO_BASE_ADDRESS
+#define GPIO_PIN0_OFFSET WLAN_GPIO_PIN0_ADDRESS
+#define GPIO_PIN1_OFFSET WLAN_GPIO_PIN1_ADDRESS
+#define GPIO_PIN0_CONFIG_MASK WLAN_GPIO_PIN0_CONFIG_MASK
+#define GPIO_PIN1_CONFIG_MASK WLAN_GPIO_PIN1_CONFIG_MASK
+#define SI_BASE_ADDRESS WLAN_SI_BASE_ADDRESS
+#define SCRATCH_BASE_ADDRESS SOC_CORE_BASE_ADDRESS
+#define LOCAL_SCRATCH_OFFSET 0x18
+#define CPU_CLOCK_OFFSET SOC_CPU_CLOCK_OFFSET
+#define LPO_CAL_OFFSET SOC_LPO_CAL_OFFSET
+#define GPIO_PIN10_OFFSET WLAN_GPIO_PIN10_ADDRESS
+#define GPIO_PIN11_OFFSET WLAN_GPIO_PIN11_ADDRESS
+#define GPIO_PIN12_OFFSET WLAN_GPIO_PIN12_ADDRESS
+#define GPIO_PIN13_OFFSET WLAN_GPIO_PIN13_ADDRESS
+#define CPU_CLOCK_STANDARD_LSB SOC_CPU_CLOCK_STANDARD_LSB
+#define CPU_CLOCK_STANDARD_MASK SOC_CPU_CLOCK_STANDARD_MASK
+#define LPO_CAL_ENABLE_LSB SOC_LPO_CAL_ENABLE_LSB
+#define LPO_CAL_ENABLE_MASK SOC_LPO_CAL_ENABLE_MASK
+#define ANALOG_INTF_BASE_ADDRESS WLAN_ANALOG_INTF_BASE_ADDRESS
+#define MBOX_BASE_ADDRESS MISSING
+#define INT_STATUS_ENABLE_ERROR_LSB MISSING
+#define INT_STATUS_ENABLE_ERROR_MASK MISSING
+#define INT_STATUS_ENABLE_CPU_LSB MISSING
+#define INT_STATUS_ENABLE_CPU_MASK MISSING
+#define INT_STATUS_ENABLE_COUNTER_LSB MISSING
+#define INT_STATUS_ENABLE_COUNTER_MASK MISSING
+#define INT_STATUS_ENABLE_MBOX_DATA_LSB MISSING
+#define INT_STATUS_ENABLE_MBOX_DATA_MASK MISSING
+#define ERROR_STATUS_ENABLE_RX_UNDERFLOW_LSB MISSING
+#define ERROR_STATUS_ENABLE_RX_UNDERFLOW_MASK MISSING
+#define ERROR_STATUS_ENABLE_TX_OVERFLOW_LSB MISSING
+#define ERROR_STATUS_ENABLE_TX_OVERFLOW_MASK MISSING
+#define COUNTER_INT_STATUS_ENABLE_BIT_LSB MISSING
+#define COUNTER_INT_STATUS_ENABLE_BIT_MASK MISSING
+#define INT_STATUS_ENABLE_ADDRESS MISSING
+#define CPU_INT_STATUS_ENABLE_BIT_LSB MISSING
+#define CPU_INT_STATUS_ENABLE_BIT_MASK MISSING
+#define HOST_INT_STATUS_ADDRESS MISSING
+#define CPU_INT_STATUS_ADDRESS MISSING
+#define ERROR_INT_STATUS_ADDRESS MISSING
+#define ERROR_INT_STATUS_WAKEUP_MASK MISSING
+#define ERROR_INT_STATUS_WAKEUP_LSB MISSING
+#define ERROR_INT_STATUS_RX_UNDERFLOW_MASK MISSING
+#define ERROR_INT_STATUS_RX_UNDERFLOW_LSB MISSING
+#define ERROR_INT_STATUS_TX_OVERFLOW_MASK MISSING
+#define ERROR_INT_STATUS_TX_OVERFLOW_LSB MISSING
+#define COUNT_DEC_ADDRESS MISSING
+#define HOST_INT_STATUS_CPU_MASK MISSING
+#define HOST_INT_STATUS_CPU_LSB MISSING
+#define HOST_INT_STATUS_ERROR_MASK MISSING
+#define HOST_INT_STATUS_ERROR_LSB MISSING
+#define HOST_INT_STATUS_COUNTER_MASK MISSING
+#define HOST_INT_STATUS_COUNTER_LSB MISSING
+#define RX_LOOKAHEAD_VALID_ADDRESS MISSING
+#define WINDOW_DATA_ADDRESS MISSING
+#define WINDOW_READ_ADDR_ADDRESS MISSING
+#define WINDOW_WRITE_ADDR_ADDRESS MISSING
+
+#define RTC_STATE_V_GET(x) (((x) & RTC_STATE_V_MASK) >> RTC_STATE_V_LSB)
+
+#endif /* _HW_H_ */
^ permalink raw reply related
* [PATCH 10/29] ath10k: add htc.c
From: Kalle Valo @ 2013-05-15 14:44 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/htc.c | 1062 +++++++++++++++++++++++++++++++++
1 file changed, 1062 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/htc.c
diff --git a/drivers/net/wireless/ath/ath10k/htc.c b/drivers/net/wireless/ath/ath10k/htc.c
new file mode 100644
index 0000000..af95e87
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/htc.c
@@ -0,0 +1,1062 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "core.h"
+#include "hif.h"
+#include "debug.h"
+
+/********/
+/* Send */
+/********/
+
+static inline void ath10k_htc_stop_queue(struct ath10k_htc_ep *ep)
+{
+ if (ep->tx_queue_stopped)
+ return;
+
+ if (ep->ep_ops.stop_queue)
+ ep->ep_ops.stop_queue(ep->htc->ar);
+
+ ath10k_dbg(ATH10K_DBG_HTC, "ep %d stop\n", ep->eid);
+ ep->tx_queue_stopped = true;
+}
+
+static inline void ath10k_htc_wake_queue(struct ath10k_htc_ep *ep)
+{
+ if (!ep->tx_queue_stopped)
+ return;
+
+ if (ep->ep_ops.wake_queue)
+ ep->ep_ops.wake_queue(ep->htc->ar);
+
+ ath10k_dbg(ATH10K_DBG_HTC, "ep %d wake\n", ep->eid);
+ ep->tx_queue_stopped = false;
+}
+
+static inline void ath10k_htc_recalc_queue(struct ath10k_htc_ep *ep, int delta)
+{
+ ath10k_dbg(ATH10K_DBG_HTC, "ep %d queue len %d +%d max %d\n",
+ ep->eid, ep->tx_queue_len, delta, ep->max_tx_queue_depth);
+
+ if (ep->max_tx_queue_depth == 0)
+ return;
+
+ ep->tx_queue_len += delta;
+
+ if (ep->tx_queue_stopped) {
+ if (ep->tx_queue_len <= ep->max_tx_queue_depth/2)
+ ath10k_htc_wake_queue(ep);
+ } else if (ep->tx_queue_len >= ep->max_tx_queue_depth) {
+ ath10k_htc_stop_queue(ep);
+ }
+}
+
+static inline void ath10k_htc_send_complete_check(struct ath10k_htc_ep *ep,
+ int force)
+{
+ /*
+ * Check whether HIF has any prior sends that have finished,
+ * have not had the post-processing done.
+ */
+ ath10k_hif_send_complete_check(ep->htc->ar, ep->ul_pipe_id, force);
+}
+
+static void ath10k_htc_control_tx_complete(struct ath10k *ar,
+ struct sk_buff *skb)
+{
+ kfree_skb(skb);
+}
+
+static struct sk_buff *ath10k_htc_build_tx_ctrl_skb(void *ar)
+{
+ struct sk_buff *skb;
+ struct ath10k_skb_cb *skb_cb;
+
+ skb = dev_alloc_skb(ATH10K_HTC_CONTROL_BUFFER_SIZE);
+ if (!skb) {
+ ath10k_warn("Unable to allocate ctrl skb\n");
+ return NULL;
+ }
+
+ skb_reserve(skb, 20); /* FIXME: why 20 bytes? */
+ WARN_ONCE((unsigned long)skb->data & 3, "unaligned skb");
+
+ skb_cb = ATH10K_SKB_CB(skb);
+ memset(skb_cb, 0, sizeof(*skb_cb));
+
+ ath10k_dbg(ATH10K_DBG_HTC, "%s: skb %p\n", __func__, skb);
+ return skb;
+}
+
+static inline void ath10k_htc_restore_tx_skb(struct ath10k_htc *htc,
+ struct sk_buff *skb)
+{
+ ath10k_skb_unmap(htc->ar->dev, skb);
+ skb_pull(skb, sizeof(struct ath10k_htc_hdr));
+}
+
+static void ath10k_htc_notify_tx_completion(struct ath10k_htc_ep *ep,
+ struct sk_buff *skb)
+{
+ ath10k_dbg(ATH10K_DBG_HTC, "%s: ep %d skb %p\n", __func__,
+ ep->eid, skb);
+
+ ath10k_htc_restore_tx_skb(ep->htc, skb);
+
+ if (!ep->ep_ops.ep_tx_complete) {
+ ath10k_warn("no tx handler for eid %d\n", ep->eid);
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ ep->ep_ops.ep_tx_complete(ep->htc->ar, skb);
+}
+
+/* assumes tx_lock is held */
+static bool ath10k_htc_ep_need_credit_update(struct ath10k_htc_ep *ep)
+{
+ if (!ep->tx_credit_flow_enabled)
+ return false;
+ if (ep->tx_credits >= ep->tx_credits_per_max_message)
+ return false;
+
+ ath10k_dbg(ATH10K_DBG_HTC, "HTC: endpoint %d needs credit update\n",
+ ep->eid);
+ return true;
+}
+
+static int ath10k_htc_prepare_tx_skb(struct ath10k_htc_ep *ep,
+ struct sk_buff *skb)
+{
+ struct ath10k_htc_hdr *hdr;
+
+ hdr = (struct ath10k_htc_hdr *)skb->data;
+ memset(hdr, 0, sizeof(*hdr));
+
+ hdr->eid = ep->eid;
+ hdr->len = __cpu_to_le16(skb->len - sizeof(*hdr));
+
+ spin_lock_bh(&ep->htc->tx_lock);
+ hdr->seq_no = ep->seq_no++;
+
+ if (ath10k_htc_ep_need_credit_update(ep))
+ hdr->flags |= ATH10K_HTC_FLAG_NEED_CREDIT_UPDATE;
+
+ spin_unlock_bh(&ep->htc->tx_lock);
+
+ return 0;
+}
+
+static int ath10k_htc_issue_skb(struct ath10k_htc *htc,
+ struct ath10k_htc_ep *ep,
+ struct sk_buff *skb,
+ u8 credits)
+{
+ struct ath10k_skb_cb *skb_cb = ATH10K_SKB_CB(skb);
+ int ret;
+
+ ath10k_dbg(ATH10K_DBG_HTC, "%s: ep %d skb %p\n", __func__,
+ ep->eid, skb);
+
+ ret = ath10k_htc_prepare_tx_skb(ep, skb);
+ if (ret)
+ goto err;
+
+ ret = ath10k_skb_map(htc->ar->dev, skb);
+ if (ret)
+ goto err;
+
+ ret = ath10k_hif_send_head(htc->ar,
+ ep->ul_pipe_id,
+ ep->eid,
+ skb->len,
+ skb);
+ if (unlikely(ret))
+ goto err;
+
+ return 0;
+err:
+ ath10k_warn("HTC issue failed: %d\n", ret);
+
+ spin_lock_bh(&htc->tx_lock);
+ ep->tx_credits += credits;
+ spin_unlock_bh(&htc->tx_lock);
+
+ skb_cb->is_aborted = true;
+ ath10k_htc_notify_tx_completion(ep, skb);
+
+ return ret;
+}
+
+/* assumes tx_lock is held */
+static struct sk_buff *ath10k_htc_get_skb_credit_based(struct ath10k_htc *htc,
+ struct ath10k_htc_ep *ep,
+ u8 *credits)
+{
+ struct sk_buff *skb;
+ struct ath10k_skb_cb *skb_cb;
+ int credits_required;
+ int remainder;
+ unsigned int transfer_len;
+
+ skb = __skb_dequeue(&ep->tx_queue);
+ if (!skb)
+ return NULL;
+
+ skb_cb = ATH10K_SKB_CB(skb);
+ transfer_len = skb->len;
+
+ if (likely(transfer_len <= htc->target_credit_size)) {
+ credits_required = 1;
+ } else {
+ /* figure out how many credits this message requires */
+ credits_required = transfer_len / htc->target_credit_size;
+ remainder = transfer_len % htc->target_credit_size;
+
+ if (remainder)
+ credits_required++;
+ }
+
+ ath10k_dbg(ATH10K_DBG_HTC, "Credits required %d got %d\n",
+ credits_required, ep->tx_credits);
+
+ if (ep->tx_credits < credits_required) {
+ __skb_queue_head(&ep->tx_queue, skb);
+ ath10k_htc_recalc_queue(ep, 1);
+ return NULL;
+ }
+
+ ep->tx_credits -= credits_required;
+ *credits = credits_required;
+ return skb;
+}
+
+/* assumes tx_lock is held */
+static struct sk_buff *ath10k_htc_get_skb(struct ath10k_htc *htc,
+ struct ath10k_htc_ep *ep,
+ int resources)
+{
+ struct sk_buff *skb;
+ struct ath10k_skb_cb *skb_cb;
+
+ if (!resources)
+ return NULL;
+
+ skb = __skb_dequeue(&ep->tx_queue);
+ if (!skb)
+ return NULL;
+
+ skb_cb = ATH10K_SKB_CB(skb);
+ ath10k_htc_recalc_queue(ep, -1);
+
+ return skb;
+}
+
+static void ath10k_htc_send_work(struct work_struct *work)
+{
+ struct ath10k_htc_ep *ep = container_of(work,
+ struct ath10k_htc_ep, send_work);
+ struct ath10k_htc *htc = ep->htc;
+ struct sk_buff *skb;
+ int tx_resources = 0;
+ u8 credits = 0;
+
+ while (true) {
+ if (!ep->tx_credit_flow_enabled)
+ tx_resources = ath10k_hif_get_free_queue_number
+ (htc->ar, ep->ul_pipe_id);
+
+ if (ep->ul_is_polled)
+ ath10k_htc_send_complete_check(ep, 0);
+
+ spin_lock_bh(&htc->tx_lock);
+
+ if (ep->tx_credit_flow_enabled)
+ skb = ath10k_htc_get_skb_credit_based(htc, ep,
+ &credits);
+ else
+ skb = ath10k_htc_get_skb(htc, ep, tx_resources);
+
+ spin_unlock_bh(&htc->tx_lock);
+
+ if (!skb)
+ break; /* tx_queue empty or out of resources */
+
+ ath10k_htc_issue_skb(htc, ep, skb, credits);
+ }
+}
+
+int ath10k_htc_send(struct ath10k_htc *htc,
+ enum ath10k_htc_ep_id eid,
+ struct sk_buff *skb)
+{
+ struct ath10k_htc_ep *ep = &htc->endpoint[eid];
+
+ if (eid >= ATH10K_HTC_EP_COUNT) {
+ ath10k_warn("Invalid endpoint id: %d\n", eid);
+ return -ENOENT;
+ }
+
+ skb_push(skb, sizeof(struct ath10k_htc_hdr));
+
+ spin_lock_bh(&htc->tx_lock);
+ __skb_queue_tail(&ep->tx_queue, skb);
+ ath10k_htc_recalc_queue(ep, 1);
+ spin_unlock_bh(&htc->tx_lock);
+
+ queue_work(htc->ar->workqueue, &ep->send_work);
+ return 0;
+}
+
+static int ath10k_htc_tx_completion_handler(struct ath10k *ar,
+ struct sk_buff *skb,
+ unsigned int eid)
+{
+ struct ath10k_htc *htc = ar->htc;
+ struct ath10k_htc_ep *ep = &htc->endpoint[eid];
+ bool stopping;
+
+ ath10k_htc_notify_tx_completion(ep, skb);
+ /* the skb now belongs to the completion handler */
+
+ spin_lock_bh(&htc->tx_lock);
+ stopping = htc->stopping;
+ spin_unlock_bh(&htc->tx_lock);
+
+ if (!ep->tx_credit_flow_enabled && !stopping)
+ /*
+ * note: when using TX credit flow, the re-checking of
+ * queues happens when credits flow back from the target.
+ * in the non-TX credit case, we recheck after the packet
+ * completes
+ */
+ queue_work(ar->workqueue, &ep->send_work);
+
+ return 0;
+}
+
+/* flush endpoint TX queue */
+static void ath10k_htc_flush_endpoint_tx(struct ath10k_htc *htc,
+ struct ath10k_htc_ep *ep)
+{
+ struct sk_buff *skb;
+ struct ath10k_skb_cb *skb_cb;
+
+ spin_lock_bh(&htc->tx_lock);
+ for (;;) {
+ skb = __skb_dequeue(&ep->tx_queue);
+ if (!skb)
+ break;
+
+ skb_cb = ATH10K_SKB_CB(skb);
+ skb_cb->is_aborted = true;
+ ath10k_htc_notify_tx_completion(ep, skb);
+ }
+ spin_unlock_bh(&htc->tx_lock);
+
+ cancel_work_sync(&ep->send_work);
+}
+
+/***********/
+/* Receive */
+/***********/
+
+static void
+ath10k_htc_process_credit_report(struct ath10k_htc *htc,
+ const struct ath10k_htc_credit_report *report,
+ int len,
+ enum ath10k_htc_ep_id eid)
+{
+ struct ath10k_htc_ep *ep;
+ int i, n_reports;
+
+ if (len % sizeof(*report))
+ ath10k_warn("Uneven credit report len %d", len);
+
+ n_reports = len / sizeof(*report);
+
+ spin_lock_bh(&htc->tx_lock);
+ for (i = 0; i < n_reports; i++, report++) {
+ if (report->eid >= ATH10K_HTC_EP_COUNT)
+ break;
+
+ ath10k_dbg(ATH10K_DBG_HTC, "ep %d got %d credits\n",
+ report->eid, report->credits);
+
+ ep = &htc->endpoint[report->eid];
+ ep->tx_credits += report->credits;
+
+ if (ep->tx_credits && !skb_queue_empty(&ep->tx_queue))
+ queue_work(htc->ar->workqueue, &ep->send_work);
+ }
+ spin_unlock_bh(&htc->tx_lock);
+}
+
+static int ath10k_htc_process_trailer(struct ath10k_htc *htc,
+ u8 *buffer,
+ int length,
+ enum ath10k_htc_ep_id src_eid)
+{
+ int status = 0;
+ struct ath10k_htc_record *record;
+ u8 *orig_buffer;
+ int orig_length;
+ size_t len;
+
+ orig_buffer = buffer;
+ orig_length = length;
+
+ while (length > 0) {
+ record = (struct ath10k_htc_record *)buffer;
+
+ if (length < sizeof(record->hdr)) {
+ status = -EINVAL;
+ break;
+ }
+
+ if (record->hdr.len > length) {
+ /* no room left in buffer for record */
+ ath10k_warn("Invalid record length: %d\n",
+ record->hdr.len);
+ status = -EINVAL;
+ break;
+ }
+
+ switch (record->hdr.id) {
+ case ATH10K_HTC_RECORD_CREDITS:
+ len = sizeof(struct ath10k_htc_credit_report);
+ if (record->hdr.len < len) {
+ ath10k_warn("Credit report too long\n");
+ status = -EINVAL;
+ break;
+ }
+ ath10k_htc_process_credit_report(htc,
+ record->credit_report,
+ record->hdr.len,
+ src_eid);
+ break;
+ default:
+ ath10k_warn("Unhandled record: id:%d length:%d\n",
+ record->hdr.id, record->hdr.len);
+ break;
+ }
+
+ if (status)
+ break;
+
+ /* multiple records may be present in a trailer */
+ buffer += sizeof(record->hdr) + record->hdr.len;
+ length -= sizeof(record->hdr) + record->hdr.len;
+ }
+
+ if (status)
+ ath10k_dbg_dump(ATH10K_DBG_HTC, "htc rx bad trailer", "",
+ orig_buffer, orig_length);
+
+ return status;
+}
+
+static int ath10k_htc_rx_completion_handler(struct ath10k *ar,
+ struct sk_buff *skb,
+ u8 pipe_id)
+{
+ int status = 0;
+ struct ath10k_htc *htc = ar->htc;
+ struct ath10k_htc_hdr *hdr;
+ struct ath10k_htc_ep *ep;
+ u16 payload_len;
+ u32 trailer_len = 0;
+ size_t min_len;
+ u8 eid;
+ bool trailer_present;
+
+ hdr = (struct ath10k_htc_hdr *)skb->data;
+ skb_pull(skb, sizeof(*hdr));
+
+ eid = hdr->eid;
+
+ if (eid >= ATH10K_HTC_EP_COUNT) {
+ ath10k_warn("HTC Rx: invalid eid %d\n", eid);
+ ath10k_dbg_dump(ATH10K_DBG_HTC, "htc bad header", "",
+ hdr, sizeof(*hdr));
+ status = -EINVAL;
+ goto out;
+ }
+
+ ep = &htc->endpoint[eid];
+
+ /*
+ * If this endpoint that received a message from the target has
+ * a to-target HIF pipe whose send completions are polled rather
+ * than interrupt-driven, this is a good point to ask HIF to check
+ * whether it has any completed sends to handle.
+ */
+ if (ep->ul_is_polled)
+ ath10k_htc_send_complete_check(ep, 1);
+
+ payload_len = __le16_to_cpu(hdr->len);
+
+ if (payload_len + sizeof(*hdr) > ATH10K_HTC_MAX_LEN) {
+ ath10k_warn("HTC rx frame too long, len: %zu\n",
+ payload_len + sizeof(*hdr));
+ ath10k_dbg_dump(ATH10K_DBG_HTC, "htc bad rx pkt len", "",
+ hdr, sizeof(*hdr));
+ status = -EINVAL;
+ goto out;
+ }
+
+ if (skb->len < payload_len) {
+ ath10k_dbg(ATH10K_DBG_HTC,
+ "HTC Rx: insufficient length, got %d, expected %d\n",
+ skb->len, payload_len);
+ ath10k_dbg_dump(ATH10K_DBG_HTC, "htc bad rx pkt len",
+ "", hdr, sizeof(*hdr));
+ status = -EINVAL;
+ goto out;
+ }
+
+ /* get flags to check for trailer */
+ trailer_present = hdr->flags & ATH10K_HTC_FLAG_TRAILER_PRESENT;
+ if (trailer_present) {
+ u8 *trailer;
+
+ trailer_len = hdr->trailer_len;
+ min_len = sizeof(struct ath10k_ath10k_htc_record_hdr);
+
+ if ((trailer_len < min_len) ||
+ (trailer_len > payload_len)) {
+ ath10k_warn("Invalid trailer length: %d\n",
+ trailer_len);
+ status = -EPROTO;
+ goto out;
+ }
+
+ trailer = (u8 *)hdr;
+ trailer += sizeof(*hdr);
+ trailer += payload_len;
+ trailer -= trailer_len;
+ status = ath10k_htc_process_trailer(htc, trailer,
+ trailer_len, hdr->eid);
+ if (status)
+ goto out;
+
+ skb_trim(skb, skb->len - trailer_len);
+ }
+
+ if (((int)payload_len - (int)trailer_len) <= 0)
+ /* zero length packet with trailer data, just drop these */
+ goto out;
+
+ if (eid == ATH10K_HTC_EP_0) {
+ struct ath10k_htc_msg *msg = (struct ath10k_htc_msg *)skb->data;
+
+ switch (__le16_to_cpu(msg->hdr.message_id)) {
+ default:
+ /* handle HTC control message */
+ if (completion_done(&htc->ctl_resp)) {
+ /*
+ * this is a fatal error, target should not be
+ * sending unsolicited messages on the ep 0
+ */
+ ath10k_warn("HTC rx ctrl still processing\n");
+ status = -EINVAL;
+ complete(&htc->ctl_resp);
+ goto out;
+ }
+
+ htc->control_resp_len =
+ min_t(int, skb->len,
+ ATH10K_HTC_MAX_CTRL_MSG_LEN);
+
+ memcpy(htc->control_resp_buffer, skb->data,
+ htc->control_resp_len);
+
+ complete(&htc->ctl_resp);
+ break;
+ case ATH10K_HTC_MSG_SEND_SUSPEND_COMPLETE:
+ htc->htc_ops.target_send_suspend_complete(ar);
+ }
+ goto out;
+ }
+
+ ath10k_dbg(ATH10K_DBG_HTC, "htc rx completion ep %d skb %p\n",
+ eid, skb);
+ ep->ep_ops.ep_rx_complete(ar, skb);
+
+ /* skb is now owned by the rx completion handler */
+ skb = NULL;
+out:
+ kfree_skb(skb);
+
+ return status;
+}
+
+static void ath10k_htc_control_rx_complete(struct ath10k *ar,
+ struct sk_buff *skb)
+{
+ /* This is unexpected. FW is not supposed to send regular rx on this
+ * endpoint. */
+ ath10k_warn("unexpected htc rx\n");
+ kfree_skb(skb);
+}
+
+/***************/
+/* Init/Deinit */
+/***************/
+
+static const char *htc_service_name(enum ath10k_htc_svc_id id)
+{
+ switch (id) {
+ case ATH10K_HTC_SVC_ID_RESERVED:
+ return "Reserved";
+ case ATH10K_HTC_SVC_ID_RSVD_CTRL:
+ return "Control";
+ case ATH10K_HTC_SVC_ID_WMI_CONTROL:
+ return "WMI";
+ case ATH10K_HTC_SVC_ID_WMI_DATA_BE:
+ return "DATA BE";
+ case ATH10K_HTC_SVC_ID_WMI_DATA_BK:
+ return "DATA BK";
+ case ATH10K_HTC_SVC_ID_WMI_DATA_VI:
+ return "DATA VI";
+ case ATH10K_HTC_SVC_ID_WMI_DATA_VO:
+ return "DATA VO";
+ case ATH10K_HTC_SVC_ID_NMI_CONTROL:
+ return "NMI Control";
+ case ATH10K_HTC_SVC_ID_NMI_DATA:
+ return "NMI Data";
+ case ATH10K_HTC_SVC_ID_HTT_DATA_MSG:
+ return "HTT Data";
+ case ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS:
+ return "RAW";
+ }
+
+ return "Unknown";
+}
+
+static void ath10k_htc_reset_endpoint_states(struct ath10k_htc *htc)
+{
+ struct ath10k_htc_ep *ep;
+ int i;
+
+ for (i = ATH10K_HTC_EP_0; i < ATH10K_HTC_EP_COUNT; i++) {
+ ep = &htc->endpoint[i];
+ ep->service_id = ATH10K_HTC_SVC_ID_UNUSED;
+ ep->max_ep_message_len = 0;
+ ep->max_tx_queue_depth = 0;
+ ep->eid = i;
+ skb_queue_head_init(&ep->tx_queue);
+ ep->tx_queue_len = 0;
+ ep->htc = htc;
+ ep->tx_credit_flow_enabled = true;
+ INIT_WORK(&ep->send_work, ath10k_htc_send_work);
+ }
+}
+
+static void ath10k_htc_setup_target_buffer_assignments(struct ath10k_htc *htc)
+{
+ struct ath10k_htc_svc_tx_credits *entry;
+
+ entry = &htc->service_tx_alloc[0];
+
+ /*
+ * for PCIE allocate all credists/HTC buffers to WMI.
+ * no buffers are used/required for data. data always
+ * remains on host.
+ */
+ entry++;
+ entry->service_id = ATH10K_HTC_SVC_ID_WMI_CONTROL;
+ entry->credit_allocation = htc->total_transmit_credits;
+}
+
+static u8 ath10k_htc_get_credit_allocation(struct ath10k_htc *htc,
+ u16 service_id)
+{
+ u8 allocation = 0;
+ int i;
+
+ for (i = 0; i < ATH10K_HTC_EP_COUNT; i++) {
+ if (htc->service_tx_alloc[i].service_id == service_id)
+ allocation =
+ htc->service_tx_alloc[i].credit_allocation;
+ }
+
+ return allocation;
+}
+
+int ath10k_htc_wait_target(struct ath10k_htc *htc)
+{
+ int status = 0;
+ struct ath10k_htc_svc_conn_req conn_req;
+ struct ath10k_htc_svc_conn_resp conn_resp;
+ struct ath10k_htc_msg *msg;
+ u16 message_id;
+ u16 credit_count;
+ u16 credit_size;
+
+ INIT_COMPLETION(htc->ctl_resp);
+
+ status = ath10k_hif_start(htc->ar);
+ if (status) {
+ ath10k_err("could not start HIF (%d)\n", status);
+ goto err_start;
+ }
+
+ status = wait_for_completion_timeout(&htc->ctl_resp,
+ ATH10K_HTC_WAIT_TIMEOUT_HZ);
+ if (status <= 0) {
+ if (status == 0)
+ status = -ETIMEDOUT;
+
+ ath10k_err("ctl_resp never came in (%d)\n", status);
+ goto err_target;
+ }
+
+ if (htc->control_resp_len < sizeof(msg->hdr) + sizeof(msg->ready)) {
+ ath10k_err("Invalid HTC ready msg len:%d\n",
+ htc->control_resp_len);
+
+ status = -ECOMM;
+ goto err_target;
+ }
+
+ msg = (struct ath10k_htc_msg *)htc->control_resp_buffer;
+ message_id = __le16_to_cpu(msg->hdr.message_id);
+ credit_count = __le16_to_cpu(msg->ready.credit_count);
+ credit_size = __le16_to_cpu(msg->ready.credit_size);
+
+ if (message_id != ATH10K_HTC_MSG_READY_ID) {
+ ath10k_err("Invalid HTC ready msg: 0x%x\n", message_id);
+ status = -ECOMM;
+ goto err_target;
+ }
+
+ htc->total_transmit_credits = credit_count;
+ htc->target_credit_size = credit_size;
+
+ ath10k_dbg(ATH10K_DBG_HTC,
+ "Target ready! transmit resources: %d size:%d\n",
+ htc->total_transmit_credits,
+ htc->target_credit_size);
+
+ if ((htc->total_transmit_credits == 0) ||
+ (htc->target_credit_size == 0)) {
+ status = -ECOMM;
+ ath10k_err("Invalid credit size received\n");
+ goto err_target;
+ }
+
+ ath10k_htc_setup_target_buffer_assignments(htc);
+
+ /* setup our pseudo HTC control endpoint connection */
+ memset(&conn_req, 0, sizeof(conn_req));
+ memset(&conn_resp, 0, sizeof(conn_resp));
+ conn_req.ep_ops.ep_tx_complete = ath10k_htc_control_tx_complete;
+ conn_req.ep_ops.ep_rx_complete = ath10k_htc_control_rx_complete;
+ conn_req.max_send_queue_depth = ATH10K_NUM_CONTROL_TX_BUFFERS;
+ conn_req.service_id = ATH10K_HTC_SVC_ID_RSVD_CTRL;
+
+ /* connect fake service */
+ status = ath10k_htc_connect_service(htc, &conn_req, &conn_resp);
+ if (status) {
+ ath10k_err("could not connect to htc service (%d)\n", status);
+ goto err_target;
+ }
+
+ return 0;
+err_target:
+ ath10k_hif_stop(htc->ar);
+err_start:
+ return status;
+}
+
+int ath10k_htc_connect_service(struct ath10k_htc *htc,
+ struct ath10k_htc_svc_conn_req *conn_req,
+ struct ath10k_htc_svc_conn_resp *conn_resp)
+{
+ struct ath10k_htc_msg *msg;
+ struct ath10k_htc_conn_svc *req_msg;
+ struct ath10k_htc_conn_svc_response resp_msg_dummy;
+ struct ath10k_htc_conn_svc_response *resp_msg = &resp_msg_dummy;
+ enum ath10k_htc_ep_id assigned_eid = ATH10K_HTC_EP_COUNT;
+ struct ath10k_htc_ep *ep;
+ struct sk_buff *skb;
+ unsigned int max_msg_size = 0;
+ int length, status;
+ bool disable_credit_flow_ctrl = false;
+ u16 message_id, service_id, flags = 0;
+ u8 tx_alloc = 0;
+
+ /* special case for HTC pseudo control service */
+ if (conn_req->service_id == ATH10K_HTC_SVC_ID_RSVD_CTRL) {
+ disable_credit_flow_ctrl = true;
+ assigned_eid = ATH10K_HTC_EP_0;
+ max_msg_size = ATH10K_HTC_MAX_CTRL_MSG_LEN;
+ memset(&resp_msg_dummy, 0, sizeof(resp_msg_dummy));
+ goto setup;
+ }
+
+ tx_alloc = ath10k_htc_get_credit_allocation(htc,
+ conn_req->service_id);
+ if (!tx_alloc)
+ ath10k_warn("HTC Service %s does not allocate target credits\n",
+ htc_service_name(conn_req->service_id));
+
+ skb = ath10k_htc_build_tx_ctrl_skb(htc->ar);
+ if (!skb) {
+ ath10k_err("Failed to allocate HTC packet\n");
+ return -ENOMEM;
+ }
+
+ length = sizeof(msg->hdr) + sizeof(msg->connect_service);
+ skb_put(skb, length);
+ memset(skb->data, 0, length);
+
+ msg = (struct ath10k_htc_msg *)skb->data;
+ msg->hdr.message_id =
+ __cpu_to_le16(ATH10K_HTC_MSG_CONNECT_SERVICE_ID);
+
+ flags |= SM(tx_alloc, ATH10K_HTC_CONN_FLAGS_RECV_ALLOC);
+
+ req_msg = &msg->connect_service;
+ req_msg->flags = __cpu_to_le16(flags);
+ req_msg->service_id = __cpu_to_le16(conn_req->service_id);
+
+ /* Only enable credit flow control for WMI ctrl service */
+ if (conn_req->service_id != ATH10K_HTC_SVC_ID_WMI_CONTROL) {
+ flags |= ATH10K_HTC_CONN_FLAGS_DISABLE_CREDIT_FLOW_CTRL;
+ disable_credit_flow_ctrl = true;
+ }
+
+ INIT_COMPLETION(htc->ctl_resp);
+
+ status = ath10k_htc_send(htc, ATH10K_HTC_EP_0, skb);
+ if (status) {
+ kfree_skb(skb);
+ return status;
+ }
+
+ /* wait for response */
+ status = wait_for_completion_timeout(&htc->ctl_resp,
+ ATH10K_HTC_CONN_SVC_TIMEOUT_HZ);
+ if (status <= 0) {
+ if (status == 0)
+ status = -ETIMEDOUT;
+ ath10k_err("Service connect timeout: %d\n", status);
+ return status;
+ }
+
+ /* we controlled the buffer creation, it's aligned */
+ msg = (struct ath10k_htc_msg *)htc->control_resp_buffer;
+ resp_msg = &msg->connect_service_response;
+ message_id = __le16_to_cpu(msg->hdr.message_id);
+ service_id = __le16_to_cpu(resp_msg->service_id);
+
+ if ((message_id != ATH10K_HTC_MSG_CONNECT_SERVICE_RESP_ID) ||
+ (htc->control_resp_len < sizeof(msg->hdr) +
+ sizeof(msg->connect_service_response))) {
+ ath10k_err("Invalid resp message ID 0x%x", message_id);
+ return -EPROTO;
+ }
+
+ ath10k_dbg(ATH10K_DBG_HTC,
+ "HTC Service %s connect response: status: 0x%x, assigned ep: 0x%x\n",
+ htc_service_name(service_id),
+ resp_msg->status, resp_msg->eid);
+
+ conn_resp->connect_resp_code = resp_msg->status;
+
+ /* check response status */
+ if (resp_msg->status != ATH10K_HTC_CONN_SVC_STATUS_SUCCESS) {
+ ath10k_err("HTC Service %s connect request failed: 0x%x)\n",
+ htc_service_name(service_id),
+ resp_msg->status);
+ return -EPROTO;
+ }
+
+ assigned_eid = (enum ath10k_htc_ep_id)resp_msg->eid;
+ max_msg_size = __le16_to_cpu(resp_msg->max_msg_size);
+
+setup:
+
+ if (assigned_eid >= ATH10K_HTC_EP_COUNT)
+ return -EPROTO;
+
+ if (max_msg_size == 0)
+ return -EPROTO;
+
+ ep = &htc->endpoint[assigned_eid];
+ ep->eid = assigned_eid;
+
+ if (ep->service_id != ATH10K_HTC_SVC_ID_UNUSED)
+ return -EPROTO;
+
+ /* return assigned endpoint to caller */
+ conn_resp->eid = assigned_eid;
+ conn_resp->max_msg_len = __le16_to_cpu(resp_msg->max_msg_size);
+
+ /* setup the endpoint */
+ ep->service_id = conn_req->service_id;
+ ep->max_tx_queue_depth = conn_req->max_send_queue_depth;
+ ep->max_ep_message_len = __le16_to_cpu(resp_msg->max_msg_size);
+ ep->tx_credits = tx_alloc;
+ ep->tx_credit_size = htc->target_credit_size;
+ ep->tx_credits_per_max_message = ep->max_ep_message_len /
+ htc->target_credit_size;
+
+ if (ep->max_ep_message_len % htc->target_credit_size)
+ ep->tx_credits_per_max_message++;
+
+ /* copy all the callbacks */
+ ep->ep_ops = conn_req->ep_ops;
+
+ status = ath10k_hif_map_service_to_pipe(htc->ar,
+ ep->service_id,
+ &ep->ul_pipe_id,
+ &ep->dl_pipe_id,
+ &ep->ul_is_polled,
+ &ep->dl_is_polled);
+ if (status)
+ return status;
+
+ ath10k_dbg(ATH10K_DBG_HTC,
+ "HTC service: %s UL pipe: %d DL pipe: %d eid: %d ready\n",
+ htc_service_name(ep->service_id), ep->ul_pipe_id,
+ ep->dl_pipe_id, ep->eid);
+
+ ath10k_dbg(ATH10K_DBG_HTC,
+ "EP %d UL polled: %d, DL polled: %d\n",
+ ep->eid, ep->ul_is_polled, ep->dl_is_polled);
+
+ if (disable_credit_flow_ctrl && ep->tx_credit_flow_enabled) {
+ ep->tx_credit_flow_enabled = false;
+ ath10k_dbg(ATH10K_DBG_HTC,
+ "HTC service: %s eid: %d TX flow control disabled\n",
+ htc_service_name(ep->service_id), assigned_eid);
+ }
+
+ return status;
+}
+
+struct sk_buff *ath10k_htc_alloc_skb(int size)
+{
+ struct sk_buff *skb;
+
+ skb = dev_alloc_skb(size + sizeof(struct ath10k_htc_hdr));
+ if (!skb) {
+ ath10k_warn("could not allocate HTC tx skb\n");
+ return NULL;
+ }
+
+ skb_reserve(skb, sizeof(struct ath10k_htc_hdr));
+
+ /* FW/HTC requires 4-byte aligned streams */
+ if (!IS_ALIGNED((unsigned long)skb->data, 4))
+ ath10k_warn("Unaligned HTC tx skb\n");
+
+ return skb;
+}
+
+int ath10k_htc_start(struct ath10k_htc *htc)
+{
+ struct sk_buff *skb;
+ int status = 0;
+ struct ath10k_htc_msg *msg;
+
+ skb = ath10k_htc_build_tx_ctrl_skb(htc->ar);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_put(skb, sizeof(msg->hdr) + sizeof(msg->setup_complete_ext));
+ memset(skb->data, 0, skb->len);
+
+ msg = (struct ath10k_htc_msg *)skb->data;
+ msg->hdr.message_id =
+ __cpu_to_le16(ATH10K_HTC_MSG_SETUP_COMPLETE_EX_ID);
+
+ ath10k_dbg(ATH10K_DBG_HTC, "HTC is using TX credit flow control\n");
+
+ status = ath10k_htc_send(htc, ATH10K_HTC_EP_0, skb);
+ if (status) {
+ kfree_skb(skb);
+ return status;
+ }
+
+ return 0;
+}
+
+/*
+ * stop HTC communications, i.e. stop interrupt reception, and flush all
+ * queued buffers
+ */
+void ath10k_htc_stop(struct ath10k_htc *htc)
+{
+ int i;
+ struct ath10k_htc_ep *ep;
+
+ spin_lock_bh(&htc->tx_lock);
+ htc->stopping = true;
+ spin_unlock_bh(&htc->tx_lock);
+
+ for (i = ATH10K_HTC_EP_0; i < ATH10K_HTC_EP_COUNT; i++) {
+ ep = &htc->endpoint[i];
+ ath10k_htc_flush_endpoint_tx(htc, ep);
+ }
+
+ ath10k_hif_stop(htc->ar);
+ ath10k_htc_reset_endpoint_states(htc);
+}
+
+/* registered target arrival callback from the HIF layer */
+struct ath10k_htc *ath10k_htc_create(struct ath10k *ar,
+ struct ath10k_htc_ops *htc_ops)
+{
+ struct ath10k_hif_cb htc_callbacks;
+ struct ath10k_htc_ep *ep = NULL;
+ struct ath10k_htc *htc = NULL;
+
+ /* FIXME: use struct ath10k instead */
+ htc = kzalloc(sizeof(struct ath10k_htc), GFP_KERNEL);
+ if (!htc)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_init(&htc->tx_lock);
+
+ memcpy(&htc->htc_ops, htc_ops, sizeof(struct ath10k_htc_ops));
+
+ ath10k_htc_reset_endpoint_states(htc);
+
+ /* setup HIF layer callbacks */
+ htc_callbacks.rx_completion = ath10k_htc_rx_completion_handler;
+ htc_callbacks.tx_completion = ath10k_htc_tx_completion_handler;
+ htc->ar = ar;
+
+ /* Get HIF default pipe for HTC message exchange */
+ ep = &htc->endpoint[ATH10K_HTC_EP_0];
+
+ ath10k_hif_init(ar, &htc_callbacks);
+ ath10k_hif_get_default_pipe(ar, &ep->ul_pipe_id, &ep->dl_pipe_id);
+
+ init_completion(&htc->ctl_resp);
+
+ return htc;
+}
+
+void ath10k_htc_destroy(struct ath10k_htc *htc)
+{
+ kfree(htc);
+}
^ permalink raw reply related
* [PATCH 17/29] ath10k: add mac.c
From: Kalle Valo @ 2013-05-15 14:45 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/mac.c | 2979 +++++++++++++++++++++++++++++++++
1 file changed, 2979 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/mac.c
diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
new file mode 100644
index 0000000..974f992
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -0,0 +1,2979 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "mac.h"
+
+#include <net/mac80211.h>
+#include <linux/etherdevice.h>
+
+#include "core.h"
+#include "debug.h"
+#include "wmi.h"
+#include "htt.h"
+#include "txrx.h"
+
+/**********/
+/* Crypto */
+/**********/
+
+static int ath10k_send_key(struct ath10k_vif *arvif,
+ struct ieee80211_key_conf *key,
+ enum set_key_cmd cmd,
+ const u8 *macaddr)
+{
+ struct wmi_vdev_install_key_arg arg = {
+ .vdev_id = arvif->vdev_id,
+ .key_idx = key->keyidx,
+ .key_len = key->keylen,
+ .key_data = key->key,
+ .macaddr = macaddr,
+ };
+
+ if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE)
+ arg.key_flags = WMI_KEY_PAIRWISE;
+ else
+ arg.key_flags = WMI_KEY_GROUP;
+
+ switch (key->cipher) {
+ case WLAN_CIPHER_SUITE_CCMP:
+ arg.key_cipher = WMI_CIPHER_AES_CCM;
+ key->flags |= IEEE80211_KEY_FLAG_SW_MGMT_TX;
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
+ arg.key_cipher = WMI_CIPHER_TKIP;
+ arg.key_txmic_len = 8;
+ arg.key_rxmic_len = 8;
+ break;
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ arg.key_cipher = WMI_CIPHER_WEP;
+ /* AP/IBSS mode requires self-key to be groupwise
+ * Otherwise pairwise key must be set */
+ if (memcmp(macaddr, arvif->vif->addr, ETH_ALEN))
+ arg.key_flags = WMI_KEY_PAIRWISE;
+ break;
+ default:
+ ath10k_warn("cipher %d is not supported\n", key->cipher);
+ return -EOPNOTSUPP;
+ }
+
+ if (cmd == DISABLE_KEY) {
+ arg.key_cipher = WMI_CIPHER_NONE;
+ arg.key_data = NULL;
+ }
+
+ return ath10k_wmi_vdev_install_key(arvif->ar, &arg);
+}
+
+static int ath10k_install_key(struct ath10k_vif *arvif,
+ struct ieee80211_key_conf *key,
+ enum set_key_cmd cmd,
+ const u8 *macaddr)
+{
+ struct ath10k *ar = arvif->ar;
+ int ret;
+
+ INIT_COMPLETION(ar->install_key_done);
+
+ ret = ath10k_send_key(arvif, key, cmd, macaddr);
+ if (ret)
+ return ret;
+
+ ret = wait_for_completion_timeout(&ar->install_key_done, 3*HZ);
+ if (ret == 0)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int ath10k_install_peer_wep_keys(struct ath10k_vif *arvif,
+ const u8 *addr)
+{
+ struct ath10k *ar = arvif->ar;
+ struct ath10k_peer *peer;
+ int ret;
+ int i;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ spin_lock_bh(&ar->data_lock);
+ peer = ath10k_peer_find(ar, arvif->vdev_id, addr);
+ spin_unlock_bh(&ar->data_lock);
+
+ if (!peer)
+ return -ENOENT;
+
+ for (i = 0; i < ARRAY_SIZE(arvif->wep_keys); i++) {
+ if (arvif->wep_keys[i] == NULL)
+ continue;
+
+ ret = ath10k_install_key(arvif, arvif->wep_keys[i], SET_KEY,
+ addr);
+ if (ret)
+ return ret;
+
+ peer->keys[i] = arvif->wep_keys[i];
+ }
+
+ return 0;
+}
+
+static int ath10k_clear_peer_keys(struct ath10k_vif *arvif,
+ const u8 *addr)
+{
+ struct ath10k *ar = arvif->ar;
+ struct ath10k_peer *peer;
+ int first_errno = 0;
+ int ret;
+ int i;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ spin_lock_bh(&ar->data_lock);
+ peer = ath10k_peer_find(ar, arvif->vdev_id, addr);
+ spin_unlock_bh(&ar->data_lock);
+
+ if (!peer)
+ return -ENOENT;
+
+ for (i = 0; i < ARRAY_SIZE(peer->keys); i++) {
+ if (peer->keys[i] == NULL)
+ continue;
+
+ ret = ath10k_install_key(arvif, peer->keys[i],
+ DISABLE_KEY, addr);
+ if (ret && first_errno == 0)
+ first_errno = ret;
+
+ if (ret)
+ ath10k_warn("could not remove peer wep key %d (%d)\n",
+ i, ret);
+
+ peer->keys[i] = NULL;
+ }
+
+ return first_errno;
+}
+
+static int ath10k_clear_vdev_key(struct ath10k_vif *arvif,
+ struct ieee80211_key_conf *key)
+{
+ struct ath10k *ar = arvif->ar;
+ struct ath10k_peer *peer;
+ u8 addr[ETH_ALEN];
+ int first_errno = 0;
+ int ret;
+ int i;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ for (;;) {
+ /* since ath10k_install_key we can't hold data_lock all the
+ * time, so we try to remove the keys incrementally */
+ spin_lock_bh(&ar->data_lock);
+ i = 0;
+ list_for_each_entry(peer, &ar->peers, list) {
+ for (i = 0; i < ARRAY_SIZE(peer->keys); i++) {
+ if (peer->keys[i] == key) {
+ memcpy(addr, peer->addr, ETH_ALEN);
+ peer->keys[i] = NULL;
+ break;
+ }
+ }
+
+ if (i < ARRAY_SIZE(peer->keys))
+ break;
+ }
+ spin_unlock_bh(&ar->data_lock);
+
+ if (i == ARRAY_SIZE(peer->keys))
+ break;
+
+ ret = ath10k_install_key(arvif, key, DISABLE_KEY, addr);
+ if (ret && first_errno == 0)
+ first_errno = ret;
+
+ if (ret)
+ ath10k_warn("could not remove key for %pM\n", addr);
+ }
+
+ return first_errno;
+}
+
+
+/*********************/
+/* General utilities */
+/*********************/
+
+static inline enum wmi_phy_mode chan_to_phymode(struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type)
+{
+ enum wmi_phy_mode phymode = MODE_UNKNOWN;
+
+ switch (chan->band) {
+ case IEEE80211_BAND_2GHZ:
+ switch (channel_type) {
+ case NL80211_CHAN_NO_HT:
+ phymode = MODE_11G;
+ break;
+ case NL80211_CHAN_HT20:
+ phymode = MODE_11NG_HT20;
+ break;
+ case NL80211_CHAN_HT40PLUS:
+ case NL80211_CHAN_HT40MINUS:
+ phymode = MODE_11NG_HT40;
+ break;
+ }
+ break;
+ case IEEE80211_BAND_5GHZ:
+ switch (channel_type) {
+ case NL80211_CHAN_NO_HT:
+ phymode = MODE_11A;
+ break;
+ case NL80211_CHAN_HT20:
+ phymode = MODE_11NA_HT20;
+ break;
+ case NL80211_CHAN_HT40PLUS:
+ case NL80211_CHAN_HT40MINUS:
+ phymode = MODE_11NA_HT40;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ WARN_ON(phymode == MODE_UNKNOWN);
+ return phymode;
+}
+
+static int band_center_freq(struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type)
+{
+ u32 center_freq = chan->center_freq;
+
+ switch (channel_type) {
+ case NL80211_CHAN_HT40PLUS:
+ center_freq = chan->center_freq + 10;
+ break;
+ case NL80211_CHAN_HT40MINUS:
+ center_freq = chan->center_freq - 10;
+ break;
+ default:
+ break;
+ }
+
+ return center_freq;
+}
+
+static u8 ath10k_parse_mpdudensity(u8 mpdudensity)
+{
+/*
+ * 802.11n D2.0 defined values for "Minimum MPDU Start Spacing":
+ * 0 for no restriction
+ * 1 for 1/4 us
+ * 2 for 1/2 us
+ * 3 for 1 us
+ * 4 for 2 us
+ * 5 for 4 us
+ * 6 for 8 us
+ * 7 for 16 us
+ */
+ switch (mpdudensity) {
+ case 0:
+ return 0;
+ case 1:
+ case 2:
+ case 3:
+ /* Our lower layer calculations limit our precision to
+ 1 microsecond */
+ return 1;
+ case 4:
+ return 2;
+ case 5:
+ return 4;
+ case 6:
+ return 8;
+ case 7:
+ return 16;
+ default:
+ return 0;
+ }
+}
+
+static int ath10k_peer_create(struct ath10k *ar, u32 vdev_id, const u8 *addr)
+{
+ int ret;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ ret = ath10k_wmi_peer_create(ar, vdev_id, addr);
+ if (ret)
+ return ret;
+
+ ret = ath10k_wait_for_peer_created(ar, vdev_id, addr);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ath10k_peer_delete(struct ath10k *ar, u32 vdev_id, const u8 *addr)
+{
+ int ret;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ ret = ath10k_wmi_peer_delete(ar, vdev_id, addr);
+ if (ret)
+ return ret;
+
+ ret = ath10k_wait_for_peer_deleted(ar, vdev_id, addr);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void ath10k_peer_cleanup(struct ath10k *ar, u32 vdev_id)
+{
+ struct ath10k_peer *peer, *tmp;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ spin_lock_bh(&ar->data_lock);
+ list_for_each_entry_safe(peer, tmp, &ar->peers, list) {
+ if (peer->vdev_id != vdev_id)
+ continue;
+
+ ath10k_warn("removing stale peer %pM from vdev_id %d\n",
+ peer->addr, vdev_id);
+
+ list_del(&peer->list);
+ kfree(peer);
+ }
+ spin_unlock_bh(&ar->data_lock);
+}
+
+/************************/
+/* Interface management */
+/************************/
+
+static inline int ath10k_vdev_setup_sync(struct ath10k *ar)
+{
+ int ret;
+
+ ret = wait_for_completion_timeout(&ar->vdev_setup_done,
+ ATH10K_VDEV_SETUP_TIMEOUT_HZ);
+ if (ret == 0)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int ath10k_vdev_start(struct ath10k_vif *arvif)
+{
+ struct ath10k *ar = arvif->ar;
+ struct ieee80211_conf *conf = &ar->hw->conf;
+ struct ieee80211_channel *channel = conf->chandef.chan;
+ struct wmi_vdev_start_request_arg arg = {};
+ int ret = 0;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ INIT_COMPLETION(ar->vdev_setup_done);
+
+ arg.vdev_id = arvif->vdev_id;
+ arg.dtim_period = arvif->dtim_period;
+ arg.bcn_intval = arvif->beacon_interval;
+
+ arg.channel.freq = channel->center_freq;
+
+ arg.channel.band_center_freq1 =
+ band_center_freq(channel,
+ cfg80211_get_chandef_type(&conf->chandef));
+
+ arg.channel.mode =
+ chan_to_phymode(channel,
+ cfg80211_get_chandef_type(&conf->chandef));
+
+ arg.channel.min_power = channel->max_power * 3;
+ arg.channel.max_power = channel->max_power * 4;
+ arg.channel.max_reg_power = channel->max_reg_power * 4;
+ arg.channel.max_antenna_gain = channel->max_antenna_gain;
+
+ if (arvif->vdev_type == WMI_VDEV_TYPE_AP) {
+ arg.ssid = arvif->u.ap.ssid;
+ arg.ssid_len = arvif->u.ap.ssid_len;
+ arg.hidden_ssid = arvif->u.ap.hidden_ssid;
+ } else if (arvif->vdev_type == WMI_VDEV_TYPE_IBSS) {
+ arg.ssid = arvif->vif->bss_conf.ssid;
+ arg.ssid_len = arvif->vif->bss_conf.ssid_len;
+ }
+
+ ret = ath10k_wmi_vdev_start(ar, &arg);
+ if (ret) {
+ ath10k_warn("WMI vdev start failed: ret %d\n", ret);
+ return ret;
+ }
+
+ ret = ath10k_vdev_setup_sync(ar);
+ if (ret) {
+ ath10k_warn("vdev setup failed %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int ath10k_vdev_stop(struct ath10k_vif *arvif)
+{
+ struct ath10k *ar = arvif->ar;
+ int ret;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ INIT_COMPLETION(ar->vdev_setup_done);
+
+ ret = ath10k_wmi_vdev_stop(ar, arvif->vdev_id);
+ if (ret) {
+ ath10k_warn("WMI vdev stop failed: ret %d\n", ret);
+ return ret;
+ }
+
+ ret = ath10k_vdev_setup_sync(ar);
+ if (ret) {
+ ath10k_warn("vdev setup failed %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int ath10k_monitor_start(struct ath10k *ar, int vdev_id)
+{
+ struct ieee80211_channel *channel = ar->hw->conf.chandef.chan;
+ struct wmi_vdev_start_request_arg arg = {};
+ enum nl80211_channel_type type;
+ int ret = 0;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ type = cfg80211_get_chandef_type(&ar->hw->conf.chandef);
+
+ arg.vdev_id = vdev_id;
+ arg.channel.freq = channel->center_freq;
+ arg.channel.band_center_freq1 = band_center_freq(channel, type);
+
+ /* TODO setup this dynamically, what in case we
+ don't have any vifs? */
+ arg.channel.mode = chan_to_phymode(channel, type);
+
+ arg.channel.min_power = channel->max_power * 3;
+ arg.channel.max_power = channel->max_power * 4;
+ arg.channel.max_reg_power = channel->max_reg_power * 4;
+ arg.channel.max_antenna_gain = channel->max_antenna_gain;
+
+ ret = ath10k_wmi_vdev_start(ar, &arg);
+ if (ret) {
+ ath10k_warn("Monitor vdev start failed: ret %d\n", ret);
+ return ret;
+ }
+
+ ret = ath10k_vdev_setup_sync(ar);
+ if (ret) {
+ ath10k_warn("Monitor vdev setup failed %d\n", ret);
+ return ret;
+ }
+
+ ret = ath10k_wmi_vdev_up(ar, vdev_id, 0, ar->mac_addr);
+ if (ret) {
+ ath10k_warn("Monitor vdev up failed: %d\n", ret);
+ goto vdev_stop;
+ }
+
+ ar->monitor_vdev_id = vdev_id;
+ ar->monitor_enabled = true;
+
+ return 0;
+
+vdev_stop:
+ ret = ath10k_wmi_vdev_stop(ar, ar->monitor_vdev_id);
+ if (ret)
+ ath10k_warn("Monitor vdev stop failed: %d\n", ret);
+
+ return ret;
+}
+
+static int ath10k_monitor_stop(struct ath10k *ar)
+{
+ int ret = 0;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ /* For some reasons, ath10k_wmi_vdev_down() here couse
+ * often ath10k_wmi_vdev_stop() to fail. Next we could
+ * not run monitor vdev and driver reload
+ * required. Don't see such problems we skip
+ * ath10k_wmi_vdev_down() here.
+ */
+
+ ret = ath10k_wmi_vdev_stop(ar, ar->monitor_vdev_id);
+ if (ret)
+ ath10k_warn("Monitor vdev stop failed: %d\n", ret);
+
+ ret = ath10k_vdev_setup_sync(ar);
+ if (ret)
+ ath10k_warn("Monitor_down sync failed: %d\n", ret);
+
+ ar->monitor_enabled = false;
+ return ret;
+}
+
+static int ath10k_monitor_create(struct ath10k *ar)
+{
+ int bit, ret = 0;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ if (ar->monitor_present) {
+ ath10k_warn("Monitor mode already enabled\n");
+ return 0;
+ }
+
+ bit = ffs(ar->free_vdev_map);
+ if (bit == 0) {
+ ath10k_warn("No free VDEV slots\n");
+ return -ENOMEM;
+ }
+
+ ar->monitor_vdev_id = bit - 1;
+ ar->free_vdev_map &= ~(1 << ar->monitor_vdev_id);
+
+ ret = ath10k_wmi_vdev_create(ar, ar->monitor_vdev_id,
+ WMI_VDEV_TYPE_MONITOR,
+ 0, ar->mac_addr);
+ if (ret) {
+ ath10k_warn("WMI vdev monitor create failed: ret %d\n", ret);
+ goto vdev_fail;
+ }
+
+ ath10k_dbg(ATH10K_DBG_MAC, "Monitor interface created, vdev id: %d\n",
+ ar->monitor_vdev_id);
+
+ ar->monitor_present = true;
+ return 0;
+
+vdev_fail:
+ /*
+ * Restore the ID to the global map.
+ */
+ ar->free_vdev_map |= 1 << (ar->monitor_vdev_id);
+ return ret;
+}
+
+static int ath10k_monitor_destroy(struct ath10k *ar)
+{
+ int ret = 0;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ if (!ar->monitor_present)
+ return 0;
+
+ ret = ath10k_wmi_vdev_delete(ar, ar->monitor_vdev_id);
+ if (ret) {
+ ath10k_warn("WMI vdev monitor delete failed: %d\n", ret);
+ return ret;
+ }
+
+ ar->free_vdev_map |= 1 << (ar->monitor_vdev_id);
+ ar->monitor_present = false;
+
+ ath10k_dbg(ATH10K_DBG_MAC, "Monitor interface destroyed, vdev id: %d\n",
+ ar->monitor_vdev_id);
+ return ret;
+}
+
+static void ath10k_control_beaconing(struct ath10k_vif *arvif,
+ struct ieee80211_bss_conf *info)
+{
+ int ret = 0;
+
+ if (!info->enable_beacon) {
+ ath10k_vdev_stop(arvif);
+ return;
+ }
+
+ arvif->tx_seq_no = 0x1000;
+
+ ret = ath10k_vdev_start(arvif);
+ if (ret)
+ return;
+
+ ret = ath10k_wmi_vdev_up(arvif->ar, arvif->vdev_id, 0, info->bssid);
+ if (ret) {
+ ath10k_warn("Failed to bring up VDEV: %d\n",
+ arvif->vdev_id);
+ return;
+ }
+ ath10k_dbg(ATH10K_DBG_MAC, "VDEV: %d up\n", arvif->vdev_id);
+}
+
+static void ath10k_control_ibss(struct ath10k_vif *arvif,
+ struct ieee80211_bss_conf *info,
+ const u8 self_peer[ETH_ALEN])
+{
+ int ret = 0;
+
+ if (!info->ibss_joined) {
+ ret = ath10k_peer_delete(arvif->ar, arvif->vdev_id, self_peer);
+ if (ret)
+ ath10k_warn("Failed to delete IBSS self peer:%pM for VDEV:%d ret:%d\n",
+ self_peer, arvif->vdev_id, ret);
+
+ if (is_zero_ether_addr(arvif->u.ibss.bssid))
+ return;
+
+ ret = ath10k_peer_delete(arvif->ar, arvif->vdev_id,
+ arvif->u.ibss.bssid);
+ if (ret) {
+ ath10k_warn("Failed to delete IBSS BSSID peer:%pM for VDEV:%d ret:%d\n",
+ arvif->u.ibss.bssid, arvif->vdev_id, ret);
+ return;
+ }
+
+ memset(arvif->u.ibss.bssid, 0, ETH_ALEN);
+
+ return;
+ }
+
+ ret = ath10k_peer_create(arvif->ar, arvif->vdev_id, self_peer);
+ if (ret) {
+ ath10k_warn("Failed to create IBSS self peer:%pM for VDEV:%d ret:%d\n",
+ self_peer, arvif->vdev_id, ret);
+ return;
+ }
+
+ ret = ath10k_wmi_vdev_set_param(arvif->ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_ATIM_WINDOW,
+ ATH10K_DEFAULT_ATIM);
+ if (ret)
+ ath10k_warn("Failed to set IBSS ATIM for VDEV:%d ret:%d\n",
+ arvif->vdev_id, ret);
+}
+
+/*
+ * Review this when mac80211 gains per-interface powersave support.
+ */
+static void ath10k_ps_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
+{
+ struct ath10k_generic_iter *ar_iter = data;
+ struct ieee80211_conf *conf = &ar_iter->ar->hw->conf;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ enum wmi_sta_powersave_param param;
+ enum wmi_sta_ps_mode psmode;
+ int ret;
+
+ if (vif->type != NL80211_IFTYPE_STATION)
+ return;
+
+ if (conf->flags & IEEE80211_CONF_PS) {
+ psmode = WMI_STA_PS_MODE_ENABLED;
+ param = WMI_STA_PS_PARAM_INACTIVITY_TIME;
+
+ ret = ath10k_wmi_set_sta_ps_param(ar_iter->ar,
+ arvif->vdev_id,
+ param,
+ conf->dynamic_ps_timeout);
+ if (ret) {
+ ath10k_warn("Failed to set inactivity time for VDEV: %d\n",
+ arvif->vdev_id);
+ return;
+ }
+
+ ar_iter->ret = ret;
+ } else {
+ psmode = WMI_STA_PS_MODE_DISABLED;
+ }
+
+ ar_iter->ret = ath10k_wmi_set_psmode(ar_iter->ar, arvif->vdev_id,
+ psmode);
+ if (ar_iter->ret)
+ ath10k_warn("Failed to set PS Mode: %d for VDEV: %d\n",
+ psmode, arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC, "Set PS Mode: %d for VDEV: %d\n",
+ psmode, arvif->vdev_id);
+}
+
+/**********************/
+/* Station management */
+/**********************/
+
+static void ath10k_peer_assoc_h_basic(struct ath10k *ar,
+ struct ath10k_vif *arvif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_bss_conf *bss_conf,
+ struct wmi_peer_assoc_complete_arg *arg)
+{
+ memcpy(arg->addr, sta->addr, ETH_ALEN);
+ arg->vdev_id = arvif->vdev_id;
+ arg->peer_aid = sta->aid;
+ arg->peer_flags |= WMI_PEER_AUTH;
+
+ if (arvif->vdev_type == WMI_VDEV_TYPE_STA)
+ /*
+ * Seems FW have problems with Power Save in STA
+ * mode when we setup this parameter to high (eg. 5).
+ * Often we see that FW don't send NULL (with clean P flags)
+ * frame even there is info about buffered frames in beacons.
+ * Sometimes we have to wait more than 10 seconds before FW
+ * will wakeup. Often sending one ping from AP to our device
+ * just fail (more than 50%).
+ *
+ * Seems setting this FW parameter to 1 couse FW
+ * will check every beacon and will wakup immediately
+ * after detection buffered data.
+ */
+ arg->peer_listen_intval = 1;
+ else
+ arg->peer_listen_intval = ar->hw->conf.listen_interval;
+
+ arg->peer_num_spatial_streams = 1;
+
+ /*
+ * The assoc capabilities are available only in managed mode.
+ */
+ if (arvif->vdev_type == WMI_VDEV_TYPE_STA && bss_conf)
+ arg->peer_caps = bss_conf->assoc_capability;
+}
+
+static void ath10k_peer_assoc_h_crypto(struct ath10k *ar,
+ struct ath10k_vif *arvif,
+ struct wmi_peer_assoc_complete_arg *arg)
+{
+ struct ieee80211_vif *vif = arvif->vif;
+ struct ieee80211_bss_conf *info = &vif->bss_conf;
+ struct cfg80211_bss *bss;
+ const u8 *rsnie = NULL;
+ const u8 *wpaie = NULL;
+
+ bss = cfg80211_get_bss(ar->hw->wiphy, ar->hw->conf.chandef.chan,
+ info->bssid, NULL, 0, 0, 0);
+ if (bss) {
+ const struct cfg80211_bss_ies *ies;
+
+ rcu_read_lock();
+ rsnie = ieee80211_bss_get_ie(bss, WLAN_EID_RSN);
+
+ ies = rcu_dereference(bss->ies);
+
+ wpaie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT,
+ WLAN_OUI_TYPE_MICROSOFT_WPA,
+ ies->data,
+ ies->len);
+ rcu_read_unlock();
+ cfg80211_put_bss(ar->hw->wiphy, bss);
+ }
+
+ /* FIXME: base on RSN IE/WPA IE is a correct idea? */
+ if (rsnie || wpaie) {
+ ath10k_dbg(ATH10K_DBG_WMI, "%s: rsn ie found\n", __func__);
+ arg->peer_flags |= WMI_PEER_NEED_PTK_4_WAY;
+ }
+
+ if (wpaie) {
+ ath10k_dbg(ATH10K_DBG_WMI, "%s: wpa ie found\n", __func__);
+ arg->peer_flags |= WMI_PEER_NEED_GTK_2_WAY;
+ }
+}
+
+static void ath10k_peer_assoc_h_rates(struct ath10k *ar,
+ struct ieee80211_sta *sta,
+ struct wmi_peer_assoc_complete_arg *arg)
+{
+ struct wmi_rate_set_arg *rateset = &arg->peer_legacy_rates;
+ const struct ieee80211_supported_band *sband;
+ const struct ieee80211_rate *rates;
+ u32 ratemask;
+ int i;
+
+ sband = ar->hw->wiphy->bands[ar->hw->conf.chandef.chan->band];
+ ratemask = sta->supp_rates[ar->hw->conf.chandef.chan->band];
+ rates = sband->bitrates;
+
+ rateset->num_rates = 0;
+
+ for (i = 0; i < 32; i++, ratemask >>= 1, rates++) {
+ if (!(ratemask & 1))
+ continue;
+
+ rateset->rates[rateset->num_rates] = rates->hw_value;
+ rateset->num_rates++;
+ }
+}
+
+static void ath10k_peer_assoc_h_ht(struct ath10k *ar,
+ struct ieee80211_sta *sta,
+ struct wmi_peer_assoc_complete_arg *arg)
+{
+ const struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap;
+ int smps;
+ int i, n;
+
+ if (!ht_cap->ht_supported)
+ return;
+
+ arg->peer_flags |= WMI_PEER_HT;
+ arg->peer_max_mpdu = (1 << (IEEE80211_HT_MAX_AMPDU_FACTOR +
+ ht_cap->ampdu_factor)) - 1;
+
+ arg->peer_mpdu_density =
+ ath10k_parse_mpdudensity(ht_cap->ampdu_density);
+
+ arg->peer_ht_caps = ht_cap->cap;
+ arg->peer_rate_caps |= WMI_RC_HT_FLAG;
+
+ if (ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING)
+ arg->peer_flags |= WMI_PEER_LDPC;
+
+ if (sta->bandwidth >= IEEE80211_STA_RX_BW_40) {
+ arg->peer_flags |= WMI_PEER_40MHZ;
+ arg->peer_rate_caps |= WMI_RC_CW40_FLAG;
+ }
+
+ if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20)
+ arg->peer_rate_caps |= WMI_RC_SGI_FLAG;
+
+ if (ht_cap->cap & IEEE80211_HT_CAP_SGI_40)
+ arg->peer_rate_caps |= WMI_RC_SGI_FLAG;
+
+ if (ht_cap->cap & IEEE80211_HT_CAP_TX_STBC) {
+ arg->peer_rate_caps |= WMI_RC_TX_STBC_FLAG;
+ arg->peer_flags |= WMI_PEER_STBC;
+ }
+
+ if (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC) {
+ u32 stbc;
+ stbc = ht_cap->cap & IEEE80211_HT_CAP_RX_STBC;
+ stbc = stbc >> IEEE80211_HT_CAP_RX_STBC_SHIFT;
+ stbc = stbc << WMI_RC_RX_STBC_FLAG_S;
+ arg->peer_rate_caps |= stbc;
+ arg->peer_flags |= WMI_PEER_STBC;
+ }
+
+ smps = ht_cap->cap & IEEE80211_HT_CAP_SM_PS;
+ smps >>= IEEE80211_HT_CAP_SM_PS_SHIFT;
+
+ if (smps == WLAN_HT_CAP_SM_PS_STATIC) {
+ arg->peer_flags |= WMI_PEER_SPATIAL_MUX;
+ arg->peer_flags |= WMI_PEER_STATIC_MIMOPS;
+ } else if (smps == WLAN_HT_CAP_SM_PS_DYNAMIC) {
+ arg->peer_flags |= WMI_PEER_SPATIAL_MUX;
+ arg->peer_flags |= WMI_PEER_DYN_MIMOPS;
+ }
+
+ if (ht_cap->mcs.rx_mask[1] && ht_cap->mcs.rx_mask[2])
+ arg->peer_rate_caps |= WMI_RC_TS_FLAG;
+ else if (ht_cap->mcs.rx_mask[1])
+ arg->peer_rate_caps |= WMI_RC_DS_FLAG;
+
+ for (i = 0, n = 0; i < IEEE80211_HT_MCS_MASK_LEN*8; i++)
+ if (ht_cap->mcs.rx_mask[i/8] & (1 << i%8))
+ arg->peer_ht_rates.rates[n++] = i;
+
+ arg->peer_ht_rates.num_rates = n;
+ arg->peer_num_spatial_streams = max((n+7) / 8, 1);
+
+ ath10k_dbg(ATH10K_DBG_MAC, "mcs cnt %d nss %d\n",
+ arg->peer_ht_rates.num_rates,
+ arg->peer_num_spatial_streams);
+}
+
+static void ath10k_peer_assoc_h_qos_ap(struct ath10k *ar,
+ struct ath10k_vif *arvif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_bss_conf *bss_conf,
+ struct wmi_peer_assoc_complete_arg *arg)
+{
+ u32 uapsd = 0;
+ u32 max_sp = 0;
+
+ if (sta->wme)
+ arg->peer_flags |= WMI_PEER_QOS;
+
+ if (sta->wme && sta->uapsd_queues) {
+ ath10k_dbg(ATH10K_DBG_MAC, "uapsd_queues: 0x%X, max_sp: %d\n",
+ sta->uapsd_queues, sta->max_sp);
+
+ arg->peer_flags |= WMI_PEER_APSD;
+ arg->peer_flags |= WMI_RC_UAPSD_FLAG;
+
+ if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO)
+ uapsd |= WMI_AP_PS_UAPSD_AC3_DELIVERY_EN |
+ WMI_AP_PS_UAPSD_AC3_TRIGGER_EN;
+ if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI)
+ uapsd |= WMI_AP_PS_UAPSD_AC2_DELIVERY_EN |
+ WMI_AP_PS_UAPSD_AC2_TRIGGER_EN;
+ if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK)
+ uapsd |= WMI_AP_PS_UAPSD_AC1_DELIVERY_EN |
+ WMI_AP_PS_UAPSD_AC1_TRIGGER_EN;
+ if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE)
+ uapsd |= WMI_AP_PS_UAPSD_AC0_DELIVERY_EN |
+ WMI_AP_PS_UAPSD_AC0_TRIGGER_EN;
+
+
+ if (sta->max_sp < MAX_WMI_AP_PS_PEER_PARAM_MAX_SP)
+ max_sp = sta->max_sp;
+
+ ath10k_wmi_set_ap_ps_param(ar, arvif->vdev_id,
+ sta->addr,
+ WMI_AP_PS_PEER_PARAM_UAPSD,
+ uapsd);
+
+ ath10k_wmi_set_ap_ps_param(ar, arvif->vdev_id,
+ sta->addr,
+ WMI_AP_PS_PEER_PARAM_MAX_SP,
+ max_sp);
+
+ /* TODO setup this based on STA listen interval and
+ beacon interval. Currently we don't know
+ sta->listen_interval - mac80211 patch required.
+ Currently use 10 seconds */
+ ath10k_wmi_set_ap_ps_param(ar, arvif->vdev_id,
+ sta->addr,
+ WMI_AP_PS_PEER_PARAM_AGEOUT_TIME,
+ 10);
+ }
+}
+
+static void ath10k_peer_assoc_h_qos_sta(struct ath10k *ar,
+ struct ath10k_vif *arvif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_bss_conf *bss_conf,
+ struct wmi_peer_assoc_complete_arg *arg)
+{
+ if (bss_conf->qos)
+ arg->peer_flags |= WMI_PEER_QOS;
+}
+
+static void ath10k_peer_assoc_h_qos(struct ath10k *ar,
+ struct ath10k_vif *arvif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_bss_conf *bss_conf,
+ struct wmi_peer_assoc_complete_arg *arg)
+{
+ switch (arvif->vdev_type) {
+ case WMI_VDEV_TYPE_AP:
+ ath10k_peer_assoc_h_qos_ap(ar, arvif, sta, bss_conf, arg);
+ break;
+ case WMI_VDEV_TYPE_STA:
+ ath10k_peer_assoc_h_qos_sta(ar, arvif, sta, bss_conf, arg);
+ break;
+ default:
+ break;
+ }
+}
+
+static void ath10k_peer_assoc_h_phymode(struct ath10k *ar,
+ struct ath10k_vif *arvif,
+ struct ieee80211_sta *sta,
+ struct wmi_peer_assoc_complete_arg *arg)
+{
+ enum wmi_phy_mode phymode = MODE_UNKNOWN;
+
+ switch (ar->hw->conf.chandef.chan->band) {
+ case IEEE80211_BAND_2GHZ:
+ if (sta->ht_cap.ht_supported) {
+ if (sta->bandwidth == IEEE80211_STA_RX_BW_40)
+ phymode = MODE_11NG_HT40;
+ else
+ phymode = MODE_11NG_HT20;
+ } else {
+ phymode = MODE_11G;
+ }
+
+ break;
+ case IEEE80211_BAND_5GHZ:
+ if (sta->ht_cap.ht_supported) {
+ if (sta->bandwidth == IEEE80211_STA_RX_BW_40)
+ phymode = MODE_11NA_HT40;
+ else
+ phymode = MODE_11NA_HT20;
+ } else {
+ phymode = MODE_11A;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ arg->peer_phymode = phymode;
+ WARN_ON(phymode == MODE_UNKNOWN);
+}
+
+static int ath10k_peer_assoc(struct ath10k *ar,
+ struct ath10k_vif *arvif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_bss_conf *bss_conf)
+{
+ struct wmi_peer_assoc_complete_arg arg;
+
+ memset(&arg, 0, sizeof(struct wmi_peer_assoc_complete_arg));
+
+ ath10k_peer_assoc_h_basic(ar, arvif, sta, bss_conf, &arg);
+ ath10k_peer_assoc_h_crypto(ar, arvif, &arg);
+ ath10k_peer_assoc_h_rates(ar, sta, &arg);
+ ath10k_peer_assoc_h_ht(ar, sta, &arg);
+ ath10k_peer_assoc_h_qos(ar, arvif, sta, bss_conf, &arg);
+ ath10k_peer_assoc_h_phymode(ar, arvif, sta, &arg);
+
+ return ath10k_wmi_peer_assoc(ar, &arg);
+}
+
+/* can be called only in mac80211 callbacks due to `key_count` usage */
+static void ath10k_bss_assoc(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *bss_conf)
+{
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ struct ieee80211_sta *ap_sta;
+ int ret;
+
+ rcu_read_lock();
+
+ ap_sta = ieee80211_find_sta(vif, bss_conf->bssid);
+ if (!ap_sta) {
+ ath10k_warn("Failed to find station entry for %pM\n",
+ bss_conf->bssid);
+ rcu_read_unlock();
+ return;
+ }
+
+ ret = ath10k_peer_assoc(ar, arvif, ap_sta, bss_conf);
+ if (ret) {
+ ath10k_warn("Peer assoc failed for %pM\n", bss_conf->bssid);
+ rcu_read_unlock();
+ return;
+ }
+
+ rcu_read_unlock();
+
+ ret = ath10k_wmi_vdev_up(ar, arvif->vdev_id, bss_conf->aid,
+ bss_conf->bssid);
+ if (ret)
+ ath10k_warn("VDEV: %d up failed: ret %d\n",
+ arvif->vdev_id, ret);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "VDEV: %d associated, BSSID: %pM, AID: %d\n",
+ arvif->vdev_id, bss_conf->bssid, bss_conf->aid);
+}
+
+/*
+ * FIXME: flush TIDs
+ */
+static void ath10k_bss_disassoc(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ int ret;
+
+ /*
+ * For some reason, calling VDEV-DOWN before VDEV-STOP
+ * makes the FW to send frames via HTT after disassociation.
+ * No idea why this happens, even though VDEV-DOWN is supposed
+ * to be analogous to link down, so just stop the VDEV.
+ */
+ ret = ath10k_vdev_stop(arvif);
+ if (!ret)
+ ath10k_dbg(ATH10K_DBG_MAC, "VDEV: %d stopped\n",
+ arvif->vdev_id);
+
+ /*
+ * If we don't call VDEV-DOWN after VDEV-STOP FW will remain active and
+ * report beacons from previously associated network through HTT.
+ * This in turn would spam mac80211 WARN_ON if we bring down all
+ * interfaces as it expects there is no rx when no interface is
+ * running.
+ */
+ ret = ath10k_wmi_vdev_down(ar, arvif->vdev_id);
+ if (ret)
+ ath10k_dbg(ATH10K_DBG_MAC, "VDEV: %d ath10k_wmi_vdev_down failed (%d)\n",
+ arvif->vdev_id, ret);
+
+ ath10k_wmi_flush_tx(ar);
+
+ arvif->def_wep_key_index = 0;
+}
+
+static int ath10k_station_assoc(struct ath10k *ar, struct ath10k_vif *arvif,
+ struct ieee80211_sta *sta)
+{
+ int ret = 0;
+
+ ret = ath10k_peer_assoc(ar, arvif, sta, NULL);
+ if (ret) {
+ ath10k_warn("WMI peer assoc failed for %pM\n", sta->addr);
+ return ret;
+ }
+
+ ret = ath10k_install_peer_wep_keys(arvif, sta->addr);
+ if (ret) {
+ ath10k_warn("could not install peer wep keys (%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int ath10k_station_disassoc(struct ath10k *ar, struct ath10k_vif *arvif,
+ struct ieee80211_sta *sta)
+{
+ int ret = 0;
+
+ ret = ath10k_clear_peer_keys(arvif, sta->addr);
+ if (ret) {
+ ath10k_warn("could not clear all peer wep keys (%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+/**************/
+/* Regulatory */
+/**************/
+
+static int ath10k_update_channel_list(struct ath10k *ar)
+{
+ struct ieee80211_hw *hw = ar->hw;
+ struct ieee80211_supported_band **bands;
+ enum ieee80211_band band;
+ struct ieee80211_channel *channel;
+ struct wmi_scan_chan_list_arg arg = {0};
+ struct wmi_channel_arg *ch;
+ bool passive;
+ int len;
+ int ret;
+ int i;
+
+ bands = hw->wiphy->bands;
+ for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+ if (!bands[band])
+ continue;
+
+ for (i = 0; i < bands[band]->n_channels; i++) {
+ if (bands[band]->channels[i].flags &
+ IEEE80211_CHAN_DISABLED)
+ continue;
+
+ arg.n_channels++;
+ }
+ }
+
+ len = sizeof(struct wmi_channel_arg) * arg.n_channels;
+ arg.channels = kzalloc(len, GFP_KERNEL);
+ if (!arg.channels)
+ return -ENOMEM;
+
+ ch = arg.channels;
+ for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+ if (!bands[band])
+ continue;
+
+ for (i = 0; i < bands[band]->n_channels; i++) {
+ channel = &bands[band]->channels[i];
+
+ if (channel->flags & IEEE80211_CHAN_DISABLED)
+ continue;
+
+ ch->allow_ht = true;
+
+ ch->allow_ibss =
+ !(channel->flags & IEEE80211_CHAN_NO_IBSS);
+
+ ch->ht40plus =
+ !(channel->flags & IEEE80211_CHAN_NO_HT40PLUS);
+
+ passive = channel->flags & IEEE80211_CHAN_PASSIVE_SCAN;
+ ch->passive = passive;
+
+ ch->freq = channel->center_freq;
+ ch->min_power = channel->max_power * 3;
+ ch->max_power = channel->max_power * 4;
+ ch->max_reg_power = channel->max_reg_power * 4;
+ ch->max_antenna_gain = channel->max_antenna_gain;
+ ch->reg_class_id = 0; /* FIXME */
+ ch->mode = chan_to_phymode(channel, NL80211_CHAN_NO_HT);
+
+ if (WARN_ON_ONCE(ch->mode == MODE_UNKNOWN))
+ continue;
+
+ ath10k_dbg(ATH10K_DBG_WMI,
+ "%s: [%zd/%d] freq %d maxpower %d regpower %d antenna %d mode %d\n",
+ __func__, ch - arg.channels, arg.n_channels,
+ ch->freq, ch->max_power, ch->max_reg_power,
+ ch->max_antenna_gain, ch->mode);
+
+ ch++;
+ }
+ }
+
+ ret = ath10k_wmi_scan_chan_list(ar, &arg);
+ kfree(arg.channels);
+
+ return ret;
+}
+
+static void ath10k_reg_notifier(struct wiphy *wiphy,
+ struct regulatory_request *request)
+{
+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+ struct ath10k *ar = hw->priv;
+ int ret;
+
+ ath_reg_notifier_apply(wiphy, request, &ar->ath_common.regulatory);
+
+ ret = ath10k_update_channel_list(ar);
+ if (ret)
+ ath10k_warn("could not update channel list (%d)\n", ret);
+}
+
+/***************/
+/* TX handlers */
+/***************/
+
+/*
+ * Frames sent to the FW have to be in "Native Wifi" format.
+ * Strip the QoS field from the 802.11 header.
+ */
+static void ath10k_tx_h_qos_workaround(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
+{
+ struct ieee80211_hdr *hdr = (void *)skb->data;
+ u8 *qos_ctl;
+
+ if (!ieee80211_is_data_qos(hdr->frame_control))
+ return;
+
+ qos_ctl = ieee80211_get_qos_ctl(hdr);
+ memmove(qos_ctl, qos_ctl + IEEE80211_QOS_CTL_LEN,
+ skb->len - ieee80211_hdrlen(hdr->frame_control));
+ skb_trim(skb, skb->len - IEEE80211_QOS_CTL_LEN);
+}
+
+static void ath10k_tx_h_update_wep_key(struct sk_buff *skb)
+{
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_vif *vif = info->control.vif;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ struct ath10k *ar = arvif->ar;
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ struct ieee80211_key_conf *key = info->control.hw_key;
+ int ret;
+
+ /* TODO AP mode should be implemented */
+ if (vif->type != NL80211_IFTYPE_STATION)
+ return;
+
+ if (!ieee80211_has_protected(hdr->frame_control))
+ return;
+
+ if (!key)
+ return;
+
+ if (key->cipher != WLAN_CIPHER_SUITE_WEP40 &&
+ key->cipher != WLAN_CIPHER_SUITE_WEP104)
+ return;
+
+ if (key->keyidx == arvif->def_wep_key_index)
+ return;
+
+ ath10k_dbg(ATH10K_DBG_MAC, "new wep keyidx will be %d\n", key->keyidx);
+
+ ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_DEF_KEYID,
+ key->keyidx);
+ if (ret) {
+ ath10k_warn("could not update wep keyidx (%d)\n", ret);
+ return;
+ }
+
+ arvif->def_wep_key_index = key->keyidx;
+}
+
+static void ath10k_tx_h_add_p2p_noa_ie(struct ath10k *ar, struct sk_buff *skb)
+{
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_vif *vif = info->control.vif;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+
+ /* This is case only for P2P_GO */
+ if (arvif->vdev_type != WMI_VDEV_TYPE_AP ||
+ arvif->vdev_subtype != WMI_VDEV_SUBTYPE_P2P_GO)
+ return;
+
+ if (unlikely(ieee80211_is_probe_resp(hdr->frame_control))) {
+ spin_lock_bh(&ar->data_lock);
+ if (arvif->u.ap.noa_data)
+ if (!pskb_expand_head(skb, 0, arvif->u.ap.noa_len,
+ GFP_ATOMIC))
+ memcpy(skb_put(skb, arvif->u.ap.noa_len),
+ arvif->u.ap.noa_data,
+ arvif->u.ap.noa_len);
+ spin_unlock_bh(&ar->data_lock);
+ }
+}
+
+static void ath10k_tx_htt(struct ath10k *ar, struct sk_buff *skb)
+{
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ int ret;
+
+ if (ieee80211_is_mgmt(hdr->frame_control))
+ ret = ath10k_htt_mgmt_tx(ar->htt, skb);
+ else if (ieee80211_is_nullfunc(hdr->frame_control))
+ /* FW does not report tx status properly for NullFunc frames
+ * unless they are sent through mgmt tx path. mac80211 sends
+ * those frames when it detects link/beacon loss and depends on
+ * the tx status to be correct. */
+ ret = ath10k_htt_mgmt_tx(ar->htt, skb);
+ else
+ ret = ath10k_htt_tx(ar->htt, skb);
+
+ if (ret) {
+ ath10k_warn("tx failed (%d). dropping packet.\n", ret);
+ ieee80211_free_txskb(ar->hw, skb);
+ }
+}
+
+void ath10k_offchan_tx_purge(struct ath10k *ar)
+{
+ struct sk_buff *skb;
+
+ for (;;) {
+ skb = skb_dequeue(&ar->offchan_tx_queue);
+ if (!skb)
+ break;
+
+ ieee80211_free_txskb(ar->hw, skb);
+ }
+}
+
+void ath10k_offchan_tx_work(struct work_struct *work)
+{
+ struct ath10k *ar = container_of(work, struct ath10k, offchan_tx_work);
+ struct ath10k_peer *peer;
+ struct ieee80211_hdr *hdr;
+ struct sk_buff *skb;
+ const u8 *peer_addr;
+ int vdev_id;
+ int ret;
+
+ /* FW requirement: We must create a peer before FW will send out
+ * an offchannel frame. Otherwise the frame will be stuck and
+ * never transmitted. We delete the peer upon tx completion.
+ * It is unlikely that a peer for offchannel tx will already be
+ * present. However it may be in some rare cases so account for that.
+ * Otherwise we might remove a legitimate peer and break stuff. */
+
+ for (;;) {
+ skb = skb_dequeue(&ar->offchan_tx_queue);
+ if (!skb)
+ break;
+
+ mutex_lock(&ar->conf_mutex);
+
+ ath10k_dbg(ATH10K_DBG_MAC, "processing offchannel skb %p\n",
+ skb);
+
+ hdr = (struct ieee80211_hdr *)skb->data;
+ peer_addr = ieee80211_get_DA(hdr);
+ vdev_id = ATH10K_SKB_CB(skb)->htt.vdev_id;
+
+ spin_lock_bh(&ar->data_lock);
+ peer = ath10k_peer_find(ar, vdev_id, peer_addr);
+ spin_unlock_bh(&ar->data_lock);
+
+ if (peer)
+ ath10k_dbg(ATH10K_DBG_MAC, "peer %pM on vdev %d already present\n",
+ peer_addr, vdev_id);
+
+ if (!peer) {
+ ret = ath10k_peer_create(ar, vdev_id, peer_addr);
+ if (ret)
+ ath10k_warn("peer %pM on vdev %d not created (%d)\n",
+ peer_addr, vdev_id, ret);
+ }
+
+ spin_lock_bh(&ar->data_lock);
+ INIT_COMPLETION(ar->offchan_tx_completed);
+ ar->offchan_tx_skb = skb;
+ spin_unlock_bh(&ar->data_lock);
+
+ ath10k_tx_htt(ar, skb);
+
+ ret = wait_for_completion_timeout(&ar->offchan_tx_completed,
+ 3 * HZ);
+ if (ret <= 0)
+ ath10k_warn("timed out waiting for offchannel skb %p\n",
+ skb);
+
+ if (!peer) {
+ ret = ath10k_peer_delete(ar, vdev_id, peer_addr);
+ if (ret)
+ ath10k_warn("peer %pM on vdev %d not deleted (%d)\n",
+ peer_addr, vdev_id, ret);
+ }
+
+ mutex_unlock(&ar->conf_mutex);
+ }
+}
+
+/************/
+/* Scanning */
+/************/
+
+/*
+ * This gets called if we dont get a heart-beat during scan.
+ * This may indicate the FW has hung and we need to abort the
+ * scan manually to prevent cancel_hw_scan() from deadlocking
+ */
+void ath10k_reset_scan(unsigned long ptr)
+{
+ struct ath10k *ar = (struct ath10k *)ptr;
+
+ spin_lock_bh(&ar->data_lock);
+ if (!ar->scan.in_progress) {
+ spin_unlock_bh(&ar->data_lock);
+ return;
+ }
+
+ ath10k_warn("scan timeout. resetting. fw issue?\n");
+
+ if (ar->scan.is_roc)
+ ieee80211_remain_on_channel_expired(ar->hw);
+ else
+ ieee80211_scan_completed(ar->hw, 1 /* aborted */);
+
+ ar->scan.in_progress = false;
+ complete_all(&ar->scan.completed);
+ spin_unlock_bh(&ar->data_lock);
+}
+
+static void ath10k_abort_scan(struct ath10k *ar)
+{
+ struct wmi_stop_scan_arg arg = {
+ .req_id = 1, /* FIXME */
+ .req_type = WMI_SCAN_STOP_ONE,
+ .u.scan_id = ATH10K_SCAN_ID,
+ };
+ int ret;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ del_timer_sync(&ar->scan.timeout);
+
+ spin_lock_bh(&ar->data_lock);
+ if (!ar->scan.in_progress) {
+ spin_unlock_bh(&ar->data_lock);
+ return;
+ }
+
+ ar->scan.aborting = true;
+ spin_unlock_bh(&ar->data_lock);
+
+ ret = ath10k_wmi_stop_scan(ar, &arg);
+ if (ret)
+ ath10k_warn("could not submit wmi stop scan (%d)\n", ret);
+
+ ath10k_wmi_flush_tx(ar);
+
+ ret = wait_for_completion_timeout(&ar->scan.completed, 3*HZ);
+ if (ret == 0)
+ ath10k_warn("timed out while waiting for scan to stop\n");
+
+ spin_lock_bh(&ar->data_lock);
+ if (ar->scan.in_progress) {
+ ath10k_warn("could not stop scan. its still in progress\n");
+ ar->scan.in_progress = false;
+ ath10k_offchan_tx_purge(ar);
+ }
+ spin_unlock_bh(&ar->data_lock);
+}
+
+static int ath10k_start_scan(struct ath10k *ar,
+ const struct wmi_start_scan_arg *arg)
+{
+ int ret;
+
+ lockdep_assert_held(&ar->conf_mutex);
+
+ ret = ath10k_wmi_start_scan(ar, arg);
+ if (ret)
+ return ret;
+
+ /* make sure we submit the command so the completion
+ * timeout makes sense */
+ ath10k_wmi_flush_tx(ar);
+
+ ret = wait_for_completion_timeout(&ar->scan.started, 1*HZ);
+ if (ret == 0) {
+ ath10k_abort_scan(ar);
+ return ret;
+ }
+
+ /* the scan can complete earlier, before we even
+ * start the timer. in that case the timer handler
+ * checks ar->scan.in_progress and bails out if its
+ * false. Add a 200ms margin to account event/command
+ * processing. */
+ mod_timer(&ar->scan.timeout, jiffies +
+ msecs_to_jiffies(arg->max_scan_time+200));
+ return 0;
+}
+
+/**********************/
+/* mac80211 callbacks */
+/**********************/
+
+static void ath10k_tx(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
+{
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = NULL;
+ u32 vdev_id = 0;
+ u8 tid;
+
+ if (info->control.vif) {
+ arvif = ath10k_vif_to_arvif(info->control.vif);
+ vdev_id = arvif->vdev_id;
+ } else if (ar->monitor_enabled) {
+ vdev_id = ar->monitor_vdev_id;
+ }
+
+ /* We should disable CCK RATE due to P2P */
+ if (info->flags & IEEE80211_TX_CTL_NO_CCK_RATE)
+ ath10k_dbg(ATH10K_DBG_MAC, "IEEE80211_TX_CTL_NO_CCK_RATE\n");
+
+ /* we must calculate tid before we apply qos workaround
+ * as we'd lose the qos control field */
+ tid = HTT_DATA_TX_EXT_TID_NON_QOS_MCAST_BCAST;
+ if (ieee80211_is_data_qos(hdr->frame_control) &&
+ is_unicast_ether_addr(ieee80211_get_DA(hdr))) {
+ u8 *qc = ieee80211_get_qos_ctl(hdr);
+ tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK;
+ }
+
+ ath10k_tx_h_qos_workaround(hw, control, skb);
+ ath10k_tx_h_update_wep_key(skb);
+ ath10k_tx_h_add_p2p_noa_ie(ar, skb);
+ ath10k_tx_h_seq_no(skb);
+
+ memset(ATH10K_SKB_CB(skb), 0, sizeof(*ATH10K_SKB_CB(skb)));
+ ATH10K_SKB_CB(skb)->htt.vdev_id = vdev_id;
+ ATH10K_SKB_CB(skb)->htt.tid = tid;
+
+ if (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) {
+ spin_lock_bh(&ar->data_lock);
+ ATH10K_SKB_CB(skb)->htt.is_offchan = true;
+ ATH10K_SKB_CB(skb)->htt.vdev_id = ar->scan.vdev_id;
+ spin_unlock_bh(&ar->data_lock);
+
+ ath10k_dbg(ATH10K_DBG_MAC, "queued offchannel skb %p\n", skb);
+
+ skb_queue_tail(&ar->offchan_tx_queue, skb);
+ ieee80211_queue_work(hw, &ar->offchan_tx_work);
+ return;
+ }
+
+ ath10k_tx_htt(ar, skb);
+}
+
+/*
+ * Initialize various parameters with default vaules.
+ */
+static int ath10k_start(struct ieee80211_hw *hw)
+{
+ struct ath10k *ar = hw->priv;
+ int ret;
+
+ ret = ath10k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_PMF_QOS, 1);
+ if (ret)
+ ath10k_warn("could not enable WMI_PDEV_PARAM_PMF_QOS (%d)\n",
+ ret);
+
+ ret = ath10k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_DYNAMIC_BW, 0);
+ if (ret)
+ ath10k_warn("could not init WMI_PDEV_PARAM_DYNAMIC_BW (%d)\n",
+ ret);
+
+ return 0;
+}
+
+static void ath10k_stop(struct ieee80211_hw *hw)
+{
+ struct ath10k *ar = hw->priv;
+
+ /* avoid leaks in case FW never confirms scan for offchannel */
+ cancel_work_sync(&ar->offchan_tx_work);
+ ath10k_offchan_tx_purge(ar);
+}
+
+static int ath10k_config(struct ieee80211_hw *hw, u32 changed)
+{
+ struct ath10k_generic_iter ar_iter;
+ struct ath10k *ar = hw->priv;
+ struct ieee80211_conf *conf = &hw->conf;
+ int ret = 0;
+ u32 flags;
+
+ mutex_lock(&ar->conf_mutex);
+
+ if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
+ ath10k_dbg(ATH10K_DBG_MAC, "Config channel %d mhz\n",
+ conf->chandef.chan->center_freq);
+ spin_lock_bh(&ar->data_lock);
+ ar->rx_channel = conf->chandef.chan;
+ spin_unlock_bh(&ar->data_lock);
+ }
+
+ if (changed & IEEE80211_CONF_CHANGE_PS) {
+ memset(&ar_iter, 0, sizeof(struct ath10k_generic_iter));
+ ar_iter.ar = ar;
+ flags = IEEE80211_IFACE_ITER_RESUME_ALL;
+
+ ieee80211_iterate_active_interfaces_atomic(hw,
+ flags,
+ ath10k_ps_iter,
+ &ar_iter);
+
+ ret = ar_iter.ret;
+ }
+
+ if (changed & IEEE80211_CONF_CHANGE_MONITOR) {
+ if (conf->flags & IEEE80211_CONF_MONITOR)
+ ret = ath10k_monitor_create(ar);
+ else
+ ret = ath10k_monitor_destroy(ar);
+ }
+
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+/*
+ * TODO:
+ * Figure out how to handle WMI_VDEV_SUBTYPE_P2P_DEVICE,
+ * because we will send mgmt frames without CCK. This requirement
+ * for P2P_FIND/GO_NEG should be handled by checking CCK flag
+ * in the TX packet.
+ */
+static int ath10k_add_interface(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ enum wmi_sta_powersave_param param;
+ int ret = 0;
+ u32 value;
+ int bit;
+
+ mutex_lock(&ar->conf_mutex);
+
+ arvif->ar = ar;
+ arvif->vif = vif;
+
+ if ((vif->type == NL80211_IFTYPE_MONITOR) && ar->monitor_present) {
+ ath10k_warn("Only one monitor interface allowed\n");
+ ret = -EBUSY;
+ goto exit;
+ }
+
+ bit = ffs(ar->free_vdev_map);
+ if (bit == 0) {
+ ret = -EBUSY;
+ goto exit;
+ }
+
+ arvif->vdev_id = bit - 1;
+ arvif->vdev_subtype = WMI_VDEV_SUBTYPE_NONE;
+ ar->free_vdev_map &= ~(1 << arvif->vdev_id);
+
+ if (ar->p2p)
+ arvif->vdev_subtype = WMI_VDEV_SUBTYPE_P2P_DEVICE;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_UNSPECIFIED:
+ case NL80211_IFTYPE_STATION:
+ arvif->vdev_type = WMI_VDEV_TYPE_STA;
+ if (vif->p2p)
+ arvif->vdev_subtype = WMI_VDEV_SUBTYPE_P2P_CLIENT;
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ arvif->vdev_type = WMI_VDEV_TYPE_IBSS;
+ break;
+ case NL80211_IFTYPE_AP:
+ arvif->vdev_type = WMI_VDEV_TYPE_AP;
+
+ if (vif->p2p)
+ arvif->vdev_subtype = WMI_VDEV_SUBTYPE_P2P_GO;
+ break;
+ case NL80211_IFTYPE_MONITOR:
+ arvif->vdev_type = WMI_VDEV_TYPE_MONITOR;
+ break;
+ default:
+ WARN_ON(1);
+ break;
+ }
+
+ ath10k_dbg(ATH10K_DBG_MAC, "Add interface: id %d type %d subtype %d\n",
+ arvif->vdev_id, arvif->vdev_type, arvif->vdev_subtype);
+
+ ret = ath10k_wmi_vdev_create(ar, arvif->vdev_id, arvif->vdev_type,
+ arvif->vdev_subtype, vif->addr);
+ if (ret) {
+ ath10k_warn("WMI vdev create failed: ret %d\n", ret);
+ goto exit;
+ }
+
+ ret = ath10k_wmi_vdev_set_param(ar, 0, WMI_VDEV_PARAM_DEF_KEYID,
+ arvif->def_wep_key_index);
+ if (ret)
+ ath10k_warn("Failed to set default keyid: %d\n", ret);
+
+ ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_TX_ENCAP_TYPE,
+ ATH10K_HW_TXRX_NATIVE_WIFI);
+ if (ret)
+ ath10k_warn("Failed to set TX encap: %d\n", ret);
+
+ if (arvif->vdev_type == WMI_VDEV_TYPE_AP) {
+ ret = ath10k_peer_create(ar, arvif->vdev_id, vif->addr);
+ if (ret) {
+ ath10k_warn("Failed to create peer for AP: %d\n", ret);
+ goto exit;
+ }
+ }
+
+ if (arvif->vdev_type == WMI_VDEV_TYPE_STA) {
+ param = WMI_STA_PS_PARAM_RX_WAKE_POLICY;
+ value = WMI_STA_PS_RX_WAKE_POLICY_WAKE;
+ ret = ath10k_wmi_set_sta_ps_param(ar, arvif->vdev_id,
+ param, value);
+ if (ret)
+ ath10k_warn("Failed to set RX wake policy: %d\n", ret);
+
+ param = WMI_STA_PS_PARAM_TX_WAKE_THRESHOLD;
+ value = WMI_STA_PS_TX_WAKE_THRESHOLD_ALWAYS;
+ ret = ath10k_wmi_set_sta_ps_param(ar, arvif->vdev_id,
+ param, value);
+ if (ret)
+ ath10k_warn("Failed to set TX wake thresh: %d\n", ret);
+
+ param = WMI_STA_PS_PARAM_PSPOLL_COUNT;
+ value = WMI_STA_PS_PSPOLL_COUNT_NO_MAX;
+ ret = ath10k_wmi_set_sta_ps_param(ar, arvif->vdev_id,
+ param, value);
+ if (ret)
+ ath10k_warn("Failed to set PSPOLL count: %d\n", ret);
+ }
+
+ if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR)
+ ar->monitor_present = true;
+
+exit:
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+static void ath10k_remove_interface(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ int ret;
+
+ mutex_lock(&ar->conf_mutex);
+
+ ath10k_dbg(ATH10K_DBG_MAC, "Remove interface: id %d\n", arvif->vdev_id);
+
+ ar->free_vdev_map |= 1 << (arvif->vdev_id);
+
+ if (arvif->vdev_type == WMI_VDEV_TYPE_AP) {
+ ret = ath10k_peer_delete(arvif->ar, arvif->vdev_id, vif->addr);
+ if (ret)
+ ath10k_warn("Failed to remove peer for AP: %d\n", ret);
+
+ kfree(arvif->u.ap.noa_data);
+ }
+
+ ret = ath10k_wmi_vdev_delete(ar, arvif->vdev_id);
+ if (ret)
+ ath10k_warn("WMI vdev delete failed: %d\n", ret);
+
+ if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR)
+ ar->monitor_present = false;
+
+ ath10k_peer_cleanup(ar, arvif->vdev_id);
+
+ mutex_unlock(&ar->conf_mutex);
+}
+
+/*
+ * FIXME: Has to be verified.
+ */
+#define SUPPORTED_FILTERS \
+ (FIF_PROMISC_IN_BSS | \
+ FIF_ALLMULTI | \
+ FIF_CONTROL | \
+ FIF_PSPOLL | \
+ FIF_OTHER_BSS | \
+ FIF_BCN_PRBRESP_PROMISC | \
+ FIF_PROBE_REQ | \
+ FIF_FCSFAIL)
+
+static void ath10k_configure_filter(struct ieee80211_hw *hw,
+ unsigned int changed_flags,
+ unsigned int *total_flags,
+ u64 multicast)
+{
+ struct ath10k *ar = hw->priv;
+ int ret;
+
+ mutex_lock(&ar->conf_mutex);
+
+ changed_flags &= SUPPORTED_FILTERS;
+ *total_flags &= SUPPORTED_FILTERS;
+ ar->filter_flags = *total_flags;
+
+ if ((ar->filter_flags & FIF_PROMISC_IN_BSS) &&
+ !ar->monitor_enabled) {
+ ret = ath10k_monitor_start(ar, ar->monitor_vdev_id);
+ if (ret)
+ ath10k_warn("Unable to start monitor mode\n");
+ else
+ ath10k_dbg(ATH10K_DBG_MAC, "Monitor mode started\n");
+ } else if (!(ar->filter_flags & FIF_PROMISC_IN_BSS) &&
+ ar->monitor_enabled) {
+ ret = ath10k_monitor_stop(ar);
+ if (ret)
+ ath10k_warn("Unable to stop monitor mode\n");
+ else
+ ath10k_dbg(ATH10K_DBG_MAC, "Monitor mode stopped\n");
+ }
+
+ mutex_unlock(&ar->conf_mutex);
+}
+
+static void ath10k_bss_info_changed(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ int ret = 0;
+
+ mutex_lock(&ar->conf_mutex);
+
+ if (changed & BSS_CHANGED_IBSS)
+ ath10k_control_ibss(arvif, info, vif->addr);
+
+ if (changed & BSS_CHANGED_BEACON_INT) {
+ arvif->beacon_interval = info->beacon_int;
+ ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_BEACON_INTERVAL,
+ arvif->beacon_interval);
+ if (ret)
+ ath10k_warn("Failed to set beacon interval for VDEV: %d\n",
+ arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Beacon interval: %d set for VDEV: %d\n",
+ arvif->beacon_interval, arvif->vdev_id);
+ }
+
+ if (changed & BSS_CHANGED_BEACON) {
+ ret = ath10k_wmi_pdev_set_param(ar,
+ WMI_PDEV_PARAM_BEACON_TX_MODE,
+ WMI_BEACON_STAGGERED_MODE);
+ if (ret)
+ ath10k_warn("Failed to set beacon mode for VDEV: %d\n",
+ arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Set staggered beacon mode for VDEV: %d\n",
+ arvif->vdev_id);
+ }
+
+ if (changed & BSS_CHANGED_DTIM_PERIOD) {
+ arvif->dtim_period = info->dtim_period;
+
+ ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_DTIM_PERIOD,
+ arvif->dtim_period);
+ if (ret)
+ ath10k_warn("Failed to set dtim period for VDEV: %d\n",
+ arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Set dtim period: %d for VDEV: %d\n",
+ arvif->dtim_period, arvif->vdev_id);
+ }
+
+ if (changed & BSS_CHANGED_SSID &&
+ vif->type == NL80211_IFTYPE_AP) {
+ arvif->u.ap.ssid_len = info->ssid_len;
+ if (info->ssid_len)
+ memcpy(arvif->u.ap.ssid, info->ssid, info->ssid_len);
+ arvif->u.ap.hidden_ssid = info->hidden_ssid;
+ }
+
+ if (changed & BSS_CHANGED_BSSID) {
+ if (!is_zero_ether_addr(info->bssid)) {
+ ret = ath10k_peer_create(ar, arvif->vdev_id,
+ info->bssid);
+ if (ret)
+ ath10k_warn("Failed to add peer: %pM for VDEV: %d\n",
+ info->bssid, arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Added peer: %pM for VDEV: %d\n",
+ info->bssid, arvif->vdev_id);
+
+
+ if (vif->type == NL80211_IFTYPE_STATION) {
+ /*
+ * this is never erased as we it for crypto key
+ * clearing; this is FW requirement
+ */
+ memcpy(arvif->u.sta.bssid, info->bssid,
+ ETH_ALEN);
+
+ ret = ath10k_vdev_start(arvif);
+ if (!ret)
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "VDEV: %d started with BSSID: %pM\n",
+ arvif->vdev_id, info->bssid);
+ }
+
+ /*
+ * Mac80211 does not keep IBSS bssid when leaving IBSS,
+ * so driver need to store it. It is needed when leaving
+ * IBSS in order to remove BSSID peer.
+ */
+ if (vif->type == NL80211_IFTYPE_ADHOC)
+ memcpy(arvif->u.ibss.bssid, info->bssid,
+ ETH_ALEN);
+ }
+ }
+
+ if (changed & BSS_CHANGED_BEACON_ENABLED)
+ ath10k_control_beaconing(arvif, info);
+
+ if (changed & BSS_CHANGED_ERP_CTS_PROT) {
+ u32 cts_prot;
+ if (info->use_cts_prot)
+ cts_prot = 1;
+ else
+ cts_prot = 0;
+
+ ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_ENABLE_RTSCTS,
+ cts_prot);
+ if (ret)
+ ath10k_warn("Failed to set CTS prot for VDEV: %d\n",
+ arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Set CTS prot: %d for VDEV: %d\n",
+ cts_prot, arvif->vdev_id);
+ }
+
+ if (changed & BSS_CHANGED_ERP_SLOT) {
+ u32 slottime;
+ if (info->use_short_slot)
+ slottime = WMI_VDEV_SLOT_TIME_SHORT; /* 9us */
+
+ else
+ slottime = WMI_VDEV_SLOT_TIME_LONG; /* 20us */
+
+ ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_SLOT_TIME,
+ slottime);
+ if (ret)
+ ath10k_warn("Failed to set erp slot for VDEV: %d\n",
+ arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Set slottime: %d for VDEV: %d\n",
+ slottime, arvif->vdev_id);
+ }
+
+ if (changed & BSS_CHANGED_ERP_PREAMBLE) {
+ u32 preamble;
+ if (info->use_short_preamble)
+ preamble = WMI_VDEV_PREAMBLE_SHORT;
+ else
+ preamble = WMI_VDEV_PREAMBLE_LONG;
+
+ ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_PREAMBLE,
+ preamble);
+ if (ret)
+ ath10k_warn("Failed to set preamble for VDEV: %d\n",
+ arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Set preamble: %d for VDEV: %d\n",
+ preamble, arvif->vdev_id);
+ }
+
+ if (changed & BSS_CHANGED_ASSOC) {
+ if (info->assoc)
+ ath10k_bss_assoc(hw, vif, info);
+ }
+
+ mutex_unlock(&ar->conf_mutex);
+}
+
+static int ath10k_hw_scan(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct cfg80211_scan_request *req)
+{
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ struct wmi_start_scan_arg arg;
+ int ret = 0;
+ int i;
+
+ mutex_lock(&ar->conf_mutex);
+
+ spin_lock_bh(&ar->data_lock);
+ if (ar->scan.in_progress) {
+ spin_unlock_bh(&ar->data_lock);
+ ret = -EBUSY;
+ goto exit;
+ }
+
+ INIT_COMPLETION(ar->scan.started);
+ INIT_COMPLETION(ar->scan.completed);
+ ar->scan.in_progress = true;
+ ar->scan.aborting = false;
+ ar->scan.is_roc = false;
+ ar->scan.vdev_id = arvif->vdev_id;
+ spin_unlock_bh(&ar->data_lock);
+
+ memset(&arg, 0, sizeof(arg));
+ ath10k_wmi_start_scan_init(ar, &arg);
+ arg.vdev_id = arvif->vdev_id;
+ arg.scan_id = ATH10K_SCAN_ID;
+
+ if (!req->no_cck)
+ arg.scan_ctrl_flags |= WMI_SCAN_ADD_CCK_RATES;
+
+ if (req->ie_len) {
+ arg.ie_len = req->ie_len;
+ memcpy(arg.ie, req->ie, arg.ie_len);
+ }
+
+ if (req->n_ssids) {
+ arg.n_ssids = req->n_ssids;
+ for (i = 0; i < arg.n_ssids; i++) {
+ arg.ssids[i].len = req->ssids[i].ssid_len;
+ arg.ssids[i].ssid = req->ssids[i].ssid;
+ }
+ }
+
+ if (req->n_channels) {
+ arg.n_channels = req->n_channels;
+ for (i = 0; i < arg.n_channels; i++)
+ arg.channels[i] = req->channels[i]->center_freq;
+ }
+
+ ret = ath10k_start_scan(ar, &arg);
+ if (ret) {
+ ath10k_warn("could not start hw scan (%d)\n", ret);
+ spin_lock_bh(&ar->data_lock);
+ ar->scan.in_progress = false;
+ spin_unlock_bh(&ar->data_lock);
+ }
+
+exit:
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+static void ath10k_cancel_hw_scan(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct ath10k *ar = hw->priv;
+
+ mutex_lock(&ar->conf_mutex);
+ ath10k_abort_scan(ar);
+ mutex_unlock(&ar->conf_mutex);
+}
+
+static int ath10k_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+ struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key)
+{
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ struct ath10k_peer *peer;
+ const u8 *peer_addr;
+ bool is_wep = key->cipher == WLAN_CIPHER_SUITE_WEP40 ||
+ key->cipher == WLAN_CIPHER_SUITE_WEP104;
+ int ret = 0;
+
+ if (key->keyidx > WMI_MAX_KEY_INDEX)
+ return -ENOSPC;
+
+ mutex_lock(&ar->conf_mutex);
+
+ if (sta)
+ peer_addr = sta->addr;
+ else if (arvif->vdev_type == WMI_VDEV_TYPE_STA)
+ peer_addr = vif->bss_conf.bssid;
+ else
+ peer_addr = vif->addr;
+
+ key->hw_key_idx = key->keyidx;
+
+ /* the peer should not disappear in mid-way (unless FW goes awry) since
+ * we already hold conf_mutex. we just make sure its there now. */
+ spin_lock_bh(&ar->data_lock);
+ peer = ath10k_peer_find(ar, arvif->vdev_id, peer_addr);
+ spin_unlock_bh(&ar->data_lock);
+
+ if (!peer) {
+ if (cmd == SET_KEY) {
+ ath10k_warn("cannot install key for non-existent peer %pM\n",
+ peer_addr);
+ ret = -EOPNOTSUPP;
+ goto exit;
+ } else {
+ /* if the peer doesn't exist there is no key to disable
+ * anymore */
+ goto exit;
+ }
+ }
+
+ if (is_wep) {
+ if (cmd == SET_KEY)
+ arvif->wep_keys[key->keyidx] = key;
+ else
+ arvif->wep_keys[key->keyidx] = NULL;
+
+ if (cmd == DISABLE_KEY)
+ ath10k_clear_vdev_key(arvif, key);
+ }
+
+ ret = ath10k_install_key(arvif, key, cmd, peer_addr);
+ if (ret) {
+ ath10k_warn("ath10k_install_key failed (%d)\n", ret);
+ goto exit;
+ }
+
+ spin_lock_bh(&ar->data_lock);
+ peer = ath10k_peer_find(ar, arvif->vdev_id, peer_addr);
+ if (peer && cmd == SET_KEY)
+ peer->keys[key->keyidx] = key;
+ else if (peer && cmd == DISABLE_KEY)
+ peer->keys[key->keyidx] = NULL;
+ else if (peer == NULL)
+ /* impossible unless FW goes crazy */
+ ath10k_warn("peer %pM disappeared!\n", peer_addr);
+ spin_unlock_bh(&ar->data_lock);
+
+exit:
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+static int ath10k_sta_state(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ enum ieee80211_sta_state old_state,
+ enum ieee80211_sta_state new_state)
+{
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ int ret = 0;
+
+ mutex_lock(&ar->conf_mutex);
+
+ if (old_state == IEEE80211_STA_NOTEXIST &&
+ new_state == IEEE80211_STA_NONE &&
+ vif->type != NL80211_IFTYPE_STATION) {
+ /*
+ * New station addition.
+ */
+ ret = ath10k_peer_create(ar, arvif->vdev_id, sta->addr);
+ if (ret)
+ ath10k_warn("Failed to add peer: %pM for VDEV: %d\n",
+ sta->addr, arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Added peer: %pM for VDEV: %d\n",
+ sta->addr, arvif->vdev_id);
+ } else if ((old_state == IEEE80211_STA_NONE &&
+ new_state == IEEE80211_STA_NOTEXIST)) {
+ /*
+ * Existing station deletion.
+ */
+ ret = ath10k_peer_delete(ar, arvif->vdev_id, sta->addr);
+ if (ret)
+ ath10k_warn("Failed to delete peer: %pM for VDEV: %d\n",
+ sta->addr, arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Removed peer: %pM for VDEV: %d\n",
+ sta->addr, arvif->vdev_id);
+
+ if (vif->type == NL80211_IFTYPE_STATION)
+ ath10k_bss_disassoc(hw, vif);
+ } else if (old_state == IEEE80211_STA_AUTH &&
+ new_state == IEEE80211_STA_ASSOC &&
+ (vif->type == NL80211_IFTYPE_AP ||
+ vif->type == NL80211_IFTYPE_ADHOC)) {
+ /*
+ * New association.
+ */
+ ret = ath10k_station_assoc(ar, arvif, sta);
+ if (ret)
+ ath10k_warn("Failed to associate station: %pM\n",
+ sta->addr);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Station %pM moved to assoc state\n",
+ sta->addr);
+ } else if (old_state == IEEE80211_STA_ASSOC &&
+ new_state == IEEE80211_STA_AUTH &&
+ (vif->type == NL80211_IFTYPE_AP ||
+ vif->type == NL80211_IFTYPE_ADHOC)) {
+ /*
+ * Disassociation.
+ */
+ ret = ath10k_station_disassoc(ar, arvif, sta);
+ if (ret)
+ ath10k_warn("Failed to disassociate station: %pM\n",
+ sta->addr);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Station %pM moved to disassociated state\n",
+ sta->addr);
+ }
+
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+static int ath10k_conf_tx_uapsd(struct ath10k *ar, struct ieee80211_vif *vif,
+ u16 ac, bool enable)
+{
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ u32 value = 0;
+ int ret = 0;
+
+ if (arvif->vdev_type != WMI_VDEV_TYPE_STA)
+ return 0;
+
+ switch (ac) {
+ case IEEE80211_AC_VO:
+ value = WMI_STA_PS_UAPSD_AC3_DELIVERY_EN |
+ WMI_STA_PS_UAPSD_AC3_TRIGGER_EN;
+ break;
+ case IEEE80211_AC_VI:
+ value = WMI_STA_PS_UAPSD_AC2_DELIVERY_EN |
+ WMI_STA_PS_UAPSD_AC2_TRIGGER_EN;
+ break;
+ case IEEE80211_AC_BE:
+ value = WMI_STA_PS_UAPSD_AC1_DELIVERY_EN |
+ WMI_STA_PS_UAPSD_AC1_TRIGGER_EN;
+ break;
+ case IEEE80211_AC_BK:
+ value = WMI_STA_PS_UAPSD_AC0_DELIVERY_EN |
+ WMI_STA_PS_UAPSD_AC0_TRIGGER_EN;
+ break;
+ }
+
+ if (enable)
+ arvif->u.sta.uapsd |= value;
+ else
+ arvif->u.sta.uapsd &= ~value;
+
+ ret = ath10k_wmi_set_sta_ps_param(ar, arvif->vdev_id,
+ WMI_STA_PS_PARAM_UAPSD,
+ arvif->u.sta.uapsd);
+ if (ret) {
+ ath10k_warn("could not set uapsd params %d\n", ret);
+ goto exit;
+ }
+
+ if (arvif->u.sta.uapsd)
+ value = WMI_STA_PS_RX_WAKE_POLICY_POLL_UAPSD;
+ else
+ value = WMI_STA_PS_RX_WAKE_POLICY_WAKE;
+
+ ret = ath10k_wmi_set_sta_ps_param(ar, arvif->vdev_id,
+ WMI_STA_PS_PARAM_RX_WAKE_POLICY,
+ value);
+ if (ret)
+ ath10k_warn("could not set rx wake param %d\n", ret);
+
+exit:
+ return ret;
+}
+
+static int ath10k_conf_tx(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, u16 ac,
+ const struct ieee80211_tx_queue_params *params)
+{
+ struct ath10k *ar = hw->priv;
+ struct wmi_wmm_params_arg *p = NULL;
+ int ret;
+
+ mutex_lock(&ar->conf_mutex);
+
+ switch (ac) {
+ case IEEE80211_AC_VO:
+ p = &ar->wmm_params.ac_vo;
+ break;
+ case IEEE80211_AC_VI:
+ p = &ar->wmm_params.ac_vi;
+ break;
+ case IEEE80211_AC_BE:
+ p = &ar->wmm_params.ac_be;
+ break;
+ case IEEE80211_AC_BK:
+ p = &ar->wmm_params.ac_bk;
+ break;
+ }
+
+ if (WARN_ON(!p)) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ p->cwmin = params->cw_min;
+ p->cwmax = params->cw_max;
+ p->aifs = params->aifs;
+
+ /*
+ * The channel time duration programmed in the HW is in absolute
+ * microseconds, while mac80211 gives the txop in units of
+ * 32 microseconds.
+ */
+ p->txop = params->txop * 32;
+
+ /* FIXME: FW accepts wmm params per hw, not per vif */
+ ret = ath10k_wmi_pdev_set_wmm_params(ar, &ar->wmm_params);
+ if (ret) {
+ ath10k_warn("could not set wmm params %d\n", ret);
+ goto exit;
+ }
+
+ ret = ath10k_conf_tx_uapsd(ar, vif, ac, params->uapsd);
+ if (ret)
+ ath10k_warn("could not set sta uapsd %d\n", ret);
+
+exit:
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+#define ATH10K_ROC_TIMEOUT_HZ (2*HZ)
+
+static int ath10k_remain_on_channel(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_channel *chan,
+ int duration,
+ enum ieee80211_roc_type type)
+{
+ struct ath10k *ar = hw->priv;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ struct wmi_start_scan_arg arg;
+ int ret;
+
+ mutex_lock(&ar->conf_mutex);
+
+ spin_lock_bh(&ar->data_lock);
+ if (ar->scan.in_progress) {
+ spin_unlock_bh(&ar->data_lock);
+ ret = -EBUSY;
+ goto exit;
+ }
+
+ INIT_COMPLETION(ar->scan.started);
+ INIT_COMPLETION(ar->scan.completed);
+ INIT_COMPLETION(ar->scan.on_channel);
+ ar->scan.in_progress = true;
+ ar->scan.aborting = false;
+ ar->scan.is_roc = true;
+ ar->scan.vdev_id = arvif->vdev_id;
+ ar->scan.roc_freq = chan->center_freq;
+ spin_unlock_bh(&ar->data_lock);
+
+ memset(&arg, 0, sizeof(arg));
+ ath10k_wmi_start_scan_init(ar, &arg);
+ arg.vdev_id = arvif->vdev_id;
+ arg.scan_id = ATH10K_SCAN_ID;
+ arg.n_channels = 1;
+ arg.channels[0] = chan->center_freq;
+ arg.dwell_time_active = duration;
+ arg.dwell_time_passive = duration;
+ arg.max_scan_time = 2 * duration;
+ arg.scan_ctrl_flags |= WMI_SCAN_FLAG_PASSIVE;
+ arg.scan_ctrl_flags |= WMI_SCAN_FILTER_PROBE_REQ;
+
+ ret = ath10k_start_scan(ar, &arg);
+ if (ret) {
+ ath10k_warn("could not start roc scan (%d)\n", ret);
+ spin_lock_bh(&ar->data_lock);
+ ar->scan.in_progress = false;
+ spin_unlock_bh(&ar->data_lock);
+ goto exit;
+ }
+
+ ret = wait_for_completion_timeout(&ar->scan.on_channel, 3*HZ);
+ if (ret == 0) {
+ ath10k_warn("could not switch to channel for roc scan\n");
+ ath10k_abort_scan(ar);
+ ret = -ETIMEDOUT;
+ goto exit;
+ }
+
+ ret = 0;
+exit:
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+static int ath10k_cancel_remain_on_channel(struct ieee80211_hw *hw)
+{
+ struct ath10k *ar = hw->priv;
+
+ mutex_lock(&ar->conf_mutex);
+ ath10k_abort_scan(ar);
+ mutex_unlock(&ar->conf_mutex);
+
+ return 0;
+}
+
+/*
+ * Both RTS and Fragmentation threshold are interface-specific
+ * in ath10k, but device-specific in mac80211.
+ */
+static void ath10k_set_rts_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
+{
+ struct ath10k_generic_iter *ar_iter = data;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ u32 rts = ar_iter->ar->hw->wiphy->rts_threshold;
+
+ rts = min_t(u32, rts, ATH10K_RTS_MAX);
+
+ ar_iter->ret = ath10k_wmi_vdev_set_param(ar_iter->ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_RTS_THRESHOLD,
+ rts);
+ if (ar_iter->ret)
+ ath10k_warn("Failed to set RTS threshold for VDEV: %d\n",
+ arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Set RTS threshold: %d for VDEV: %d\n",
+ rts, arvif->vdev_id);
+}
+
+static int ath10k_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
+{
+ struct ath10k_generic_iter ar_iter;
+ struct ath10k *ar = hw->priv;
+
+ memset(&ar_iter, 0, sizeof(struct ath10k_generic_iter));
+ ar_iter.ar = ar;
+
+ mutex_lock(&ar->conf_mutex);
+ ieee80211_iterate_active_interfaces(hw, IEEE80211_IFACE_ITER_RESUME_ALL,
+ ath10k_set_rts_iter, &ar_iter);
+ mutex_unlock(&ar->conf_mutex);
+
+ return ar_iter.ret;
+}
+
+static void ath10k_set_frag_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
+{
+ struct ath10k_generic_iter *ar_iter = data;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+ u32 frag = ar_iter->ar->hw->wiphy->frag_threshold;
+ int ret;
+
+ frag = clamp_t(u32, frag,
+ ATH10K_FRAGMT_THRESHOLD_MIN,
+ ATH10K_FRAGMT_THRESHOLD_MAX);
+
+ ret = ath10k_wmi_vdev_set_param(ar_iter->ar, arvif->vdev_id,
+ WMI_VDEV_PARAM_FRAGMENTATION_THRESHOLD,
+ frag);
+
+ ar_iter->ret = ret;
+ if (ar_iter->ret)
+ ath10k_warn("Failed to set frag threshold for VDEV: %d\n",
+ arvif->vdev_id);
+ else
+ ath10k_dbg(ATH10K_DBG_MAC,
+ "Set frag threshold: %d for VDEV: %d\n",
+ frag, arvif->vdev_id);
+}
+
+static int ath10k_set_frag_threshold(struct ieee80211_hw *hw, u32 value)
+{
+ struct ath10k_generic_iter ar_iter;
+ struct ath10k *ar = hw->priv;
+
+ memset(&ar_iter, 0, sizeof(struct ath10k_generic_iter));
+ ar_iter.ar = ar;
+
+ mutex_lock(&ar->conf_mutex);
+ ieee80211_iterate_active_interfaces(hw, IEEE80211_IFACE_ITER_RESUME_ALL,
+ ath10k_set_frag_iter, &ar_iter);
+ mutex_unlock(&ar->conf_mutex);
+
+ return ar_iter.ret;
+}
+
+static void ath10k_flush(struct ieee80211_hw *hw, u32 queues, bool drop)
+{
+ struct ath10k *ar = hw->priv;
+ int ret;
+
+ /* mac80211 doesn't care if we really xmit queued frames or not
+ * we'll collect those frames either way if we stop/delete vdevs */
+ if (drop)
+ return;
+
+ ret = wait_event_timeout(ar->htt->empty_tx_wq, ({
+ bool empty;
+ spin_lock_bh(&ar->htt->tx_lock);
+ empty = bitmap_empty(ar->htt->used_msdu_ids,
+ HTT_MAX_NUM_PENDING_TX);
+ spin_unlock_bh(&ar->htt->tx_lock);
+ (empty);
+ }), ATH10K_FLUSH_TIMEOUT_HZ);
+ if (ret <= 0)
+ ath10k_warn("tx not flushed\n");
+}
+
+/* TODO: Implement this function properly
+ * For now it is needed to reply to Probe Requests in IBSS mode.
+ * Propably we need this information from FW.
+ */
+static int ath10k_tx_last_beacon(struct ieee80211_hw *hw)
+{
+ return 1;
+}
+
+static const struct ieee80211_ops ath10k_ops = {
+ .tx = ath10k_tx,
+ .start = ath10k_start,
+ .stop = ath10k_stop,
+ .config = ath10k_config,
+ .add_interface = ath10k_add_interface,
+ .remove_interface = ath10k_remove_interface,
+ .configure_filter = ath10k_configure_filter,
+ .bss_info_changed = ath10k_bss_info_changed,
+ .hw_scan = ath10k_hw_scan,
+ .cancel_hw_scan = ath10k_cancel_hw_scan,
+ .set_key = ath10k_set_key,
+ .sta_state = ath10k_sta_state,
+ .conf_tx = ath10k_conf_tx,
+ .remain_on_channel = ath10k_remain_on_channel,
+ .cancel_remain_on_channel = ath10k_cancel_remain_on_channel,
+ .set_rts_threshold = ath10k_set_rts_threshold,
+ .set_frag_threshold = ath10k_set_frag_threshold,
+ .flush = ath10k_flush,
+ .tx_last_beacon = ath10k_tx_last_beacon,
+};
+
+#define RATETAB_ENT(_rate, _rateid, _flags) { \
+ .bitrate = (_rate), \
+ .flags = (_flags), \
+ .hw_value = (_rateid), \
+}
+
+#define CHAN2G(_channel, _freq, _flags) { \
+ .band = IEEE80211_BAND_2GHZ, \
+ .hw_value = (_channel), \
+ .center_freq = (_freq), \
+ .flags = (_flags), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+#define CHAN5G(_channel, _freq, _flags) { \
+ .band = IEEE80211_BAND_5GHZ, \
+ .hw_value = (_channel), \
+ .center_freq = (_freq), \
+ .flags = (_flags), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+static const struct ieee80211_channel ath10k_2ghz_channels[] = {
+ CHAN2G(1, 2412, 0),
+ CHAN2G(2, 2417, 0),
+ CHAN2G(3, 2422, 0),
+ CHAN2G(4, 2427, 0),
+ CHAN2G(5, 2432, 0),
+ CHAN2G(6, 2437, 0),
+ CHAN2G(7, 2442, 0),
+ CHAN2G(8, 2447, 0),
+ CHAN2G(9, 2452, 0),
+ CHAN2G(10, 2457, 0),
+ CHAN2G(11, 2462, 0),
+ CHAN2G(12, 2467, 0),
+ CHAN2G(13, 2472, 0),
+ CHAN2G(14, 2484, 0),
+};
+
+static const struct ieee80211_channel ath10k_5ghz_channels[] = {
+ CHAN5G(36, 5180, 14),
+ CHAN5G(40, 5200, 15),
+ CHAN5G(44, 5220, 16),
+ CHAN5G(48, 5240, 17),
+ CHAN5G(52, 5260, 18),
+ CHAN5G(56, 5280, 19),
+ CHAN5G(60, 5300, 20),
+ CHAN5G(64, 5320, 21),
+ CHAN5G(100, 5500, 22),
+ CHAN5G(104, 5520, 23),
+ CHAN5G(108, 5540, 24),
+ CHAN5G(112, 5560, 25),
+ CHAN5G(116, 5580, 26),
+ CHAN5G(120, 5600, 27),
+ CHAN5G(124, 5620, 28),
+ CHAN5G(128, 5640, 29),
+ CHAN5G(132, 5660, 30),
+ CHAN5G(136, 5680, 31),
+ CHAN5G(140, 5700, 32),
+ CHAN5G(149, 5745, 33),
+ CHAN5G(153, 5765, 34),
+ CHAN5G(157, 5785, 35),
+ CHAN5G(161, 5805, 36),
+ CHAN5G(165, 5825, 37),
+};
+
+static struct ieee80211_rate ath10k_rates[] = {
+ /* CCK */
+ RATETAB_ENT(10, 0x82, 0),
+ RATETAB_ENT(20, 0x84, 0),
+ RATETAB_ENT(55, 0x8b, 0),
+ RATETAB_ENT(110, 0x96, 0),
+ /* OFDM */
+ RATETAB_ENT(60, 0x0c, 0),
+ RATETAB_ENT(90, 0x12, 0),
+ RATETAB_ENT(120, 0x18, 0),
+ RATETAB_ENT(180, 0x24, 0),
+ RATETAB_ENT(240, 0x30, 0),
+ RATETAB_ENT(360, 0x48, 0),
+ RATETAB_ENT(480, 0x60, 0),
+ RATETAB_ENT(540, 0x6c, 0),
+};
+
+#define ath10k_a_rates (ath10k_rates + 4)
+#define ath10k_a_rates_size (ARRAY_SIZE(ath10k_rates) - 4)
+#define ath10k_g_rates (ath10k_rates + 0)
+#define ath10k_g_rates_size (ARRAY_SIZE(ath10k_rates))
+
+struct ath10k *ath10k_mac_create(void)
+{
+ struct ieee80211_hw *hw;
+ struct ath10k *ar;
+
+ hw = ieee80211_alloc_hw(sizeof(struct ath10k), &ath10k_ops);
+ if (!hw)
+ return NULL;
+
+ ar = hw->priv;
+ ar->hw = hw;
+
+ return ar;
+}
+
+void ath10k_mac_destroy(struct ath10k *ar)
+{
+ ieee80211_free_hw(ar->hw);
+}
+
+static const struct ieee80211_iface_limit ath10k_if_limits[] = {
+ {
+ .max = 8,
+ .types = BIT(NL80211_IFTYPE_STATION)
+ | BIT(NL80211_IFTYPE_P2P_CLIENT)
+ | BIT(NL80211_IFTYPE_P2P_GO)
+ | BIT(NL80211_IFTYPE_AP)
+ }
+};
+
+static const struct ieee80211_iface_combination ath10k_if_comb = {
+ .limits = ath10k_if_limits,
+ .n_limits = ARRAY_SIZE(ath10k_if_limits),
+ .max_interfaces = 8,
+ .num_different_channels = 1,
+ .beacon_int_infra_match = true,
+};
+
+static struct ieee80211_sta_ht_cap ath10k_get_ht_cap(struct ath10k *ar)
+{
+ int i;
+ struct ieee80211_sta_ht_cap ht_cap = {0};
+
+ if (!(ar->ht_cap_info & WMI_HT_CAP_ENABLED))
+ return ht_cap;
+
+ ht_cap.ht_supported = 1;
+ ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
+ ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_8;
+ ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+ ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40;
+ ht_cap.cap |= WLAN_HT_CAP_SM_PS_STATIC << IEEE80211_HT_CAP_SM_PS_SHIFT;
+
+ if (ar->ht_cap_info & WMI_HT_CAP_HT20_SGI)
+ ht_cap.cap |= IEEE80211_HT_CAP_SGI_20;
+
+ if (ar->ht_cap_info & WMI_HT_CAP_HT40_SGI)
+ ht_cap.cap |= IEEE80211_HT_CAP_SGI_40;
+
+ if (ar->ht_cap_info & WMI_HT_CAP_DYNAMIC_SMPS) {
+ u32 smps;
+
+ smps = WLAN_HT_CAP_SM_PS_DYNAMIC;
+ smps <<= IEEE80211_HT_CAP_SM_PS_SHIFT;
+
+ ht_cap.cap |= smps;
+ }
+
+ if (ar->ht_cap_info & WMI_HT_CAP_TX_STBC)
+ ht_cap.cap |= IEEE80211_HT_CAP_TX_STBC;
+
+ if (ar->ht_cap_info & WMI_HT_CAP_RX_STBC) {
+ u32 stbc;
+
+ stbc = ar->ht_cap_info;
+ stbc &= WMI_HT_CAP_RX_STBC;
+ stbc >>= WMI_HT_CAP_RX_STBC_MASK_SHIFT;
+ stbc <<= IEEE80211_HT_CAP_RX_STBC_SHIFT;
+ stbc &= IEEE80211_HT_CAP_RX_STBC;
+
+ ht_cap.cap |= stbc;
+ }
+
+ if (ar->ht_cap_info & WMI_HT_CAP_LDPC)
+ ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING;
+
+ if (ar->ht_cap_info & WMI_HT_CAP_L_SIG_TXOP_PROT)
+ ht_cap.cap |= IEEE80211_HT_CAP_LSIG_TXOP_PROT;
+
+ /* max AMSDU is implicitly taken from vht_cap_info */
+ if (ar->vht_cap_info & WMI_VHT_CAP_MAX_MPDU_LEN_MASK)
+ ht_cap.cap |= IEEE80211_HT_CAP_MAX_AMSDU;
+
+ for (i = 0; i < WMI_MAX_SPATIAL_STREAM; i++)
+ ht_cap.mcs.rx_mask[i] = 0xFF;
+
+ ht_cap.mcs.tx_params |= IEEE80211_HT_MCS_TX_DEFINED;
+
+ return ht_cap;
+}
+
+
+static void ath10k_get_arvif_iter(void *data, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct ath10k_vif_iter *arvif_iter = data;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+
+ if (arvif->vdev_id == arvif_iter->vdev_id)
+ arvif_iter->arvif = arvif;
+}
+
+struct ath10k_vif *ath10k_get_arvif(struct ath10k *ar, u32 vdev_id)
+{
+ struct ath10k_vif_iter arvif_iter;
+ u32 flags;
+
+ memset(&arvif_iter, 0, sizeof(struct ath10k_vif_iter));
+ arvif_iter.vdev_id = vdev_id;
+
+ flags = IEEE80211_IFACE_ITER_RESUME_ALL;
+ ieee80211_iterate_active_interfaces_atomic(ar->hw,
+ flags,
+ ath10k_get_arvif_iter,
+ &arvif_iter);
+ if (!arvif_iter.arvif) {
+ ath10k_warn("No VIF found for VDEV: %d\n", vdev_id);
+ return NULL;
+ }
+
+ return arvif_iter.arvif;
+}
+
+int ath10k_mac_register(struct ath10k *ar)
+{
+ struct ieee80211_supported_band *band;
+ struct ieee80211_sta_ht_cap ht_cap;
+ void *channels;
+ int ret;
+
+ SET_IEEE80211_PERM_ADDR(ar->hw, ar->mac_addr);
+
+ SET_IEEE80211_DEV(ar->hw, ar->dev);
+
+ ht_cap = ath10k_get_ht_cap(ar);
+
+ if (ar->phy_capability & WHAL_WLAN_11G_CAPABILITY) {
+ channels = kmemdup(ath10k_2ghz_channels,
+ sizeof(ath10k_2ghz_channels),
+ GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ band = &ar->mac.sbands[IEEE80211_BAND_2GHZ];
+ band->n_channels = ARRAY_SIZE(ath10k_2ghz_channels);
+ band->channels = channels;
+ band->n_bitrates = ath10k_g_rates_size;
+ band->bitrates = ath10k_g_rates;
+ band->ht_cap = ht_cap;
+ ar->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = band;
+ }
+
+ if (ar->phy_capability & WHAL_WLAN_11A_CAPABILITY) {
+ channels = kmemdup(ath10k_5ghz_channels,
+ sizeof(ath10k_5ghz_channels),
+ GFP_KERNEL);
+ if (!channels) {
+ if (ar->phy_capability & WHAL_WLAN_11G_CAPABILITY) {
+ band = &ar->mac.sbands[IEEE80211_BAND_2GHZ];
+ kfree(band->channels);
+ }
+ return -ENOMEM;
+ }
+
+ band = &ar->mac.sbands[IEEE80211_BAND_5GHZ];
+ band->n_channels = ARRAY_SIZE(ath10k_5ghz_channels);
+ band->channels = channels;
+ band->n_bitrates = ath10k_a_rates_size;
+ band->bitrates = ath10k_a_rates;
+ band->ht_cap = ht_cap;
+ ar->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = band;
+ }
+
+ ar->hw->wiphy->interface_modes =
+ BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_ADHOC) |
+ BIT(NL80211_IFTYPE_AP) |
+ BIT(NL80211_IFTYPE_P2P_CLIENT) |
+ BIT(NL80211_IFTYPE_P2P_GO);
+
+ ar->hw->flags = IEEE80211_HW_SIGNAL_DBM |
+ IEEE80211_HW_SUPPORTS_PS |
+ IEEE80211_HW_SUPPORTS_DYNAMIC_PS |
+ IEEE80211_HW_SUPPORTS_UAPSD |
+ IEEE80211_HW_MFP_CAPABLE |
+ IEEE80211_HW_REPORTS_TX_ACK_STATUS |
+ IEEE80211_HW_HAS_RATE_CONTROL |
+ IEEE80211_HW_SUPPORTS_STATIC_SMPS |
+ IEEE80211_HW_WANT_MONITOR_VIF |
+ IEEE80211_HW_AP_LINK_PS;
+
+ if (ar->ht_cap_info & WMI_HT_CAP_DYNAMIC_SMPS)
+ ar->hw->flags |= IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS;
+
+ if (ar->ht_cap_info & WMI_HT_CAP_ENABLED) {
+ ar->hw->flags |= IEEE80211_HW_AMPDU_AGGREGATION;
+ ar->hw->flags |= IEEE80211_HW_TX_AMPDU_SETUP_IN_HW;
+ }
+
+ ar->hw->wiphy->max_scan_ssids = WLAN_SCAN_PARAMS_MAX_SSID;
+ ar->hw->wiphy->max_scan_ie_len = WLAN_SCAN_PARAMS_MAX_IE_LEN;
+
+ ar->hw->vif_data_size = sizeof(struct ath10k_vif);
+
+ ar->hw->channel_change_time = 5000;
+ ar->hw->max_listen_interval = ATH10K_MAX_HW_LISTEN_INTERVAL;
+
+ ar->hw->wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
+ ar->hw->wiphy->max_remain_on_channel_duration = 5000;
+
+ ar->hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
+ /*
+ * on LL hardware queues are managed entirely by the FW
+ * so we only advertise to mac we can do the queues thing
+ */
+ ar->hw->queues = 4;
+
+ ar->hw->wiphy->iface_combinations = &ath10k_if_comb;
+ ar->hw->wiphy->n_iface_combinations = 1;
+
+ ar->ath_common.regulatory.country_code = CTRY_DEFAULT;
+ ret = ath_regd_init(&ar->ath_common.regulatory, ar->hw->wiphy,
+ ath10k_reg_notifier);
+ if (ret) {
+ ath10k_err("Regulatory initialization failed\n");
+ return ret;
+ }
+
+ ret = ieee80211_register_hw(ar->hw);
+ if (ret) {
+ ath10k_err("ieee80211 registration failed: %d\n", ret);
+ return ret;
+ }
+
+ if (!ath_is_world_regd(&ar->ath_common.regulatory)) {
+ ret = regulatory_hint(ar->hw->wiphy,
+ ar->ath_common.regulatory.alpha2);
+ if (ret)
+ goto exit;
+ }
+
+ return 0;
+exit:
+ ieee80211_unregister_hw(ar->hw);
+ return ret;
+}
+
+void ath10k_mac_unregister(struct ath10k *ar)
+{
+ ieee80211_unregister_hw(ar->hw);
+
+ kfree(ar->mac.sbands[IEEE80211_BAND_2GHZ].channels);
+ kfree(ar->mac.sbands[IEEE80211_BAND_5GHZ].channels);
+
+ SET_IEEE80211_DEV(ar->hw, NULL);
+}
^ permalink raw reply related
* [PATCH 26/29] ath10k: add txrx.h
From: Kalle Valo @ 2013-05-15 14:46 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/txrx.h | 39 ++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/txrx.h
diff --git a/drivers/net/wireless/ath/ath10k/txrx.h b/drivers/net/wireless/ath/ath10k/txrx.h
new file mode 100644
index 0000000..e78632a
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/txrx.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef _TXRX_H_
+#define _TXRX_H_
+
+#include "htt.h"
+
+void ath10k_txrx_tx_unref(struct ath10k_htt *htt, struct sk_buff *txdesc);
+void ath10k_txrx_tx_completed(struct ath10k_htt *htt,
+ const struct htt_tx_done *tx_done);
+void ath10k_process_rx(struct ath10k *ar, struct htt_rx_info *info);
+
+struct ath10k_peer *ath10k_peer_find(struct ath10k *ar, int vdev_id,
+ const u8 *addr);
+int ath10k_wait_for_peer_created(struct ath10k *ar, int vdev_id,
+ const u8 *addr);
+int ath10k_wait_for_peer_deleted(struct ath10k *ar, int vdev_id,
+ const u8 *addr);
+
+void ath10k_peer_map_event(struct ath10k_htt *htt,
+ struct htt_peer_map_event *ev);
+void ath10k_peer_unmap_event(struct ath10k_htt *htt,
+ struct htt_peer_unmap_event *ev);
+
+#endif
^ permalink raw reply related
* [PATCH 20/29] ath10k: add pci.h
From: Kalle Valo @ 2013-05-15 14:45 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/pci.h | 355 +++++++++++++++++++++++++++++++++
1 file changed, 355 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/pci.h
diff --git a/drivers/net/wireless/ath/ath10k/pci.h b/drivers/net/wireless/ath/ath10k/pci.h
new file mode 100644
index 0000000..d2a055a
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/pci.h
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _PCI_H_
+#define _PCI_H_
+
+#include <linux/interrupt.h>
+
+#include "hw.h"
+#include "ce.h"
+
+/* FW dump area */
+#define REG_DUMP_COUNT_QCA988X 60
+
+/*
+ * maximum number of bytes that can be handled atomically by DiagRead/DiagWrite
+ */
+#define DIAG_TRANSFER_LIMIT 2048
+
+/*
+ * maximum number of bytes that can be
+ * handled atomically by DiagRead/DiagWrite
+ */
+#define DIAG_TRANSFER_LIMIT 2048
+
+struct bmi_xfer {
+ struct completion done;
+ bool wait_for_resp;
+ u32 resp_len;
+};
+
+struct ath10k_pci_compl {
+ struct list_head list;
+ int send_or_recv;
+ struct ce_state *ce_state;
+ struct hif_ce_pipe_info *pipe_info;
+ void *transfer_context;
+ unsigned int nbytes;
+ unsigned int transfer_id;
+ unsigned int flags;
+};
+
+/* compl_state.send_or_recv */
+#define HIF_CE_COMPLETE_FREE 0
+#define HIF_CE_COMPLETE_SEND 1
+#define HIF_CE_COMPLETE_RECV 2
+
+/*
+ * PCI-specific Target state
+ *
+ * NOTE: Structure is shared between Host software and Target firmware!
+ *
+ * Much of this may be of interest to the Host so
+ * HOST_INTEREST->hi_interconnect_state points here
+ * (and all members are 32-bit quantities in order to
+ * facilitate Host access). In particular, Host software is
+ * required to initialize pipe_cfg_addr and svc_to_pipe_map.
+ */
+struct pcie_state {
+ /* Pipe configuration Target address */
+ /* NB: ce_pipe_config[CE_COUNT] */
+ u32 pipe_cfg_addr;
+
+ /* Service to pipe map Target address */
+ /* NB: service_to_pipe[PIPE_TO_CE_MAP_CN] */
+ u32 svc_to_pipe_map;
+
+ /* number of MSI interrupts requested */
+ u32 msi_requested;
+
+ /* number of MSI interrupts granted */
+ u32 msi_granted;
+
+ /* Message Signalled Interrupt address */
+ u32 msi_addr;
+
+ /* Base data */
+ u32 msi_data;
+
+ /*
+ * Data for firmware interrupt;
+ * MSI data for other interrupts are
+ * in various SoC registers
+ */
+ u32 msi_fw_intr_data;
+
+ /* PCIE_PWR_METHOD_* */
+ u32 power_mgmt_method;
+
+ /* PCIE_CONFIG_FLAG_* */
+ u32 config_flags;
+};
+
+/* PCIE_CONFIG_FLAG definitions */
+#define PCIE_CONFIG_FLAG_ENABLE_L1 0x0000001
+
+/* Host software's Copy Engine configuration. */
+#define CE_ATTR_FLAGS 0
+
+/*
+ * Configuration information for a Copy Engine pipe.
+ * Passed from Host to Target during startup (one per CE).
+ *
+ * NOTE: Structure is shared between Host software and Target firmware!
+ */
+struct ce_pipe_config {
+ u32 pipenum;
+ u32 pipedir;
+ u32 nentries;
+ u32 nbytes_max;
+ u32 flags;
+ u32 reserved;
+};
+
+/*
+ * Directions for interconnect pipe configuration.
+ * These definitions may be used during configuration and are shared
+ * between Host and Target.
+ *
+ * Pipe Directions are relative to the Host, so PIPEDIR_IN means
+ * "coming IN over air through Target to Host" as with a WiFi Rx operation.
+ * Conversely, PIPEDIR_OUT means "going OUT from Host through Target over air"
+ * as with a WiFi Tx operation. This is somewhat awkward for the "middle-man"
+ * Target since things that are "PIPEDIR_OUT" are coming IN to the Target
+ * over the interconnect.
+ */
+#define PIPEDIR_NONE 0
+#define PIPEDIR_IN 1 /* Target-->Host, WiFi Rx direction */
+#define PIPEDIR_OUT 2 /* Host->Target, WiFi Tx direction */
+#define PIPEDIR_INOUT 3 /* bidirectional */
+
+/* Establish a mapping between a service/direction and a pipe. */
+struct service_to_pipe {
+ u32 service_id;
+ u32 pipedir;
+ u32 pipenum;
+};
+
+enum ath10k_pci_features {
+ ATH10K_PCI_FEATURE_MSI_X = 0,
+ ATH10K_PCI_FEATURE_HW_1_0_WARKAROUND = 1,
+
+ /* keep last */
+ ATH10K_PCI_FEATURE_COUNT
+};
+
+/* Per-pipe state. */
+struct hif_ce_pipe_info {
+ /* Handle of underlying Copy Engine */
+ struct ce_state *ce_hdl;
+
+ /* Our pipe number; facilitiates use of pipe_info ptrs. */
+ u8 pipe_num;
+
+ /* Convenience back pointer to hif_ce_state. */
+ struct ath10k *hif_ce_state;
+
+ size_t buf_sz;
+
+ /* protects compl_free and num_send_allowed */
+ spinlock_t pipe_lock;
+
+ /* List of free CE completion slots */
+ struct list_head compl_free;
+
+ /* Limit the number of outstanding send requests. */
+ int num_sends_allowed;
+
+ struct ath10k_pci *ar_pci;
+ struct tasklet_struct intr;
+};
+
+struct ath10k_pci {
+ struct pci_dev *pdev;
+ struct device *dev;
+ struct ath10k *ar;
+ void __iomem *mem;
+ int cacheline_sz;
+
+ DECLARE_BITMAP(features, ATH10K_PCI_FEATURE_COUNT);
+
+ /*
+ * Number of MSI interrupts granted, 0 --> using legacy PCI line
+ * interrupts.
+ */
+ int num_msi_intrs;
+
+ struct tasklet_struct intr_tq;
+ struct tasklet_struct msi_fw_err;
+
+ /* Number of Copy Engines supported */
+ unsigned int ce_count;
+
+ int started;
+
+ atomic_t keep_awake_count;
+ bool verified_awake;
+
+ /* List of CE completions to be processed */
+ struct list_head compl_process;
+
+ /* protects compl_processing and compl_process */
+ spinlock_t compl_lock;
+
+ bool compl_processing;
+
+ struct hif_ce_pipe_info pipe_info[CE_COUNT_MAX];
+
+ struct ath10k_hif_cb msg_callbacks_current;
+
+ /* Target address used to signal a pending firmware event */
+ u32 fw_indicator_address;
+
+ /* Copy Engine used for Diagnostic Accesses */
+ struct ce_state *ce_diag;
+
+ /* FIXME: document what this really protects */
+ spinlock_t ce_lock;
+
+ /* Map CE id to ce_state */
+ struct ce_state *ce_id_to_state[CE_COUNT_MAX];
+
+ /* makes sure that dummy reads are atomic */
+ spinlock_t hw_v1_workaround_lock;
+};
+
+static inline struct ath10k_pci *ath10k_pci_priv(struct ath10k *ar)
+{
+ return ar->hif.priv;
+}
+
+static inline u32 ath10k_pci_reg_read32(void __iomem *mem, u32 addr)
+{
+ return ioread32(mem + PCIE_LOCAL_BASE_ADDRESS + addr);
+}
+
+static inline void ath10k_pci_reg_write32(void __iomem *mem, u32 addr, u32 val)
+{
+ iowrite32(val, mem + PCIE_LOCAL_BASE_ADDRESS + addr);
+}
+
+#define ATH_PCI_RESET_WAIT_MAX 10 /* ms */
+#define PCIE_WAKE_TIMEOUT 5000 /* 5ms */
+
+#define BAR_NUM 0
+
+#define CDC_WAR_MAGIC_STR 0xceef0000
+#define CDC_WAR_DATA_CE 4
+
+/*
+ * TODO: Should be a function call specific to each Target-type.
+ * This convoluted macro converts from Target CPU Virtual Address Space to CE
+ * Address Space. As part of this process, we conservatively fetch the current
+ * PCIE_BAR. MOST of the time, this should match the upper bits of PCI space
+ * for this device; but that's not guaranteed.
+ */
+#define TARG_CPU_SPACE_TO_CE_SPACE(ar, pci_addr, addr) \
+ (((ioread32((pci_addr)+(SOC_CORE_BASE_ADDRESS| \
+ CORE_CTRL_ADDRESS)) & 0x7ff) << 21) | \
+ 0x100000 | ((addr) & 0xfffff))
+
+/* Wait up to this many Ms for a Diagnostic Access CE operation to complete */
+#define DIAG_ACCESS_CE_TIMEOUT_MS 10
+
+/*
+ * This API allows the Host to access Target registers directly
+ * and relatively efficiently over PCIe.
+ * This allows the Host to avoid extra overhead associated with
+ * sending a message to firmware and waiting for a response message
+ * from firmware, as is done on other interconnects.
+ *
+ * Yet there is some complexity with direct accesses because the
+ * Target's power state is not known a priori. The Host must issue
+ * special PCIe reads/writes in order to explicitly wake the Target
+ * and to verify that it is awake and will remain awake.
+ *
+ * Usage:
+ *
+ * Use ath10k_pci_read32 and ath10k_pci_write32 to access Target space.
+ * These calls must be bracketed by ath10k_pci_wake and
+ * ath10k_pci_sleep. A single BEGIN/END pair is adequate for
+ * multiple READ/WRITE operations.
+ *
+ * Use ath10k_pci_wake to put the Target in a state in
+ * which it is legal for the Host to directly access it. This
+ * may involve waking the Target from a low power state, which
+ * may take up to 2Ms!
+ *
+ * Use ath10k_pci_sleep to tell the Target that as far as
+ * this code path is concerned, it no longer needs to remain
+ * directly accessible. BEGIN/END is under a reference counter;
+ * multiple code paths may issue BEGIN/END on a single targid.
+ */
+static inline void ath10k_pci_write32(struct ath10k *ar, u32 offset,
+ u32 value)
+{
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ void __iomem *addr = ar_pci->mem;
+
+ if (test_bit(ATH10K_PCI_FEATURE_HW_1_0_WARKAROUND, ar_pci->features)) {
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&ar_pci->hw_v1_workaround_lock, irq_flags);
+
+ ioread32(addr+offset+4); /* 3rd read prior to write */
+ ioread32(addr+offset+4); /* 2nd read prior to write */
+ ioread32(addr+offset+4); /* 1st read prior to write */
+ iowrite32(value, addr+offset);
+
+ spin_unlock_irqrestore(&ar_pci->hw_v1_workaround_lock,
+ irq_flags);
+ } else {
+ iowrite32(value, addr+offset);
+ }
+}
+
+static inline u32 ath10k_pci_read32(struct ath10k *ar, u32 offset)
+{
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+ return ioread32(ar_pci->mem + offset);
+}
+
+extern unsigned int ath10k_target_ps;
+
+void ath10k_do_pci_wake(struct ath10k *ar);
+void ath10k_do_pci_sleep(struct ath10k *ar);
+
+static inline void ath10k_pci_wake(struct ath10k *ar)
+{
+ if (ath10k_target_ps)
+ ath10k_do_pci_wake(ar);
+}
+
+static inline void ath10k_pci_sleep(struct ath10k *ar)
+{
+ if (ath10k_target_ps)
+ ath10k_do_pci_sleep(ar);
+}
+
+#endif /* _PCI_H_ */
^ permalink raw reply related
* [PATCH 18/29] ath10k: add mac.h
From: Kalle Valo @ 2013-05-15 14:45 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/mac.h | 61 +++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/mac.h
diff --git a/drivers/net/wireless/ath/ath10k/mac.h b/drivers/net/wireless/ath/ath10k/mac.h
new file mode 100644
index 0000000..27fc92e
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/mac.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _MAC_H_
+#define _MAC_H_
+
+#include <net/mac80211.h>
+#include "core.h"
+
+struct ath10k_generic_iter {
+ struct ath10k *ar;
+ int ret;
+};
+
+struct ath10k *ath10k_mac_create(void);
+void ath10k_mac_destroy(struct ath10k *ar);
+int ath10k_mac_register(struct ath10k *ar);
+void ath10k_mac_unregister(struct ath10k *ar);
+struct ath10k_vif *ath10k_get_arvif(struct ath10k *ar, u32 vdev_id);
+void ath10k_reset_scan(unsigned long ptr);
+void ath10k_offchan_tx_purge(struct ath10k *ar);
+void ath10k_offchan_tx_work(struct work_struct *work);
+
+static inline struct ath10k_vif *ath10k_vif_to_arvif(struct ieee80211_vif *vif)
+{
+ return (struct ath10k_vif *)vif->drv_priv;
+}
+
+static inline void ath10k_tx_h_seq_no(struct sk_buff *skb)
+{
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ struct ieee80211_vif *vif = info->control.vif;
+ struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+
+ if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) {
+ if (arvif->tx_seq_no == 0)
+ arvif->tx_seq_no = 0x1000;
+
+ if (info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT)
+ arvif->tx_seq_no += 0x10;
+ hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG);
+ hdr->seq_ctrl |= cpu_to_le16(arvif->tx_seq_no);
+ }
+}
+
+#endif /* _MAC_H_ */
^ permalink raw reply related
* [PATCH 11/29] ath10k: add htc.h
From: Kalle Valo @ 2013-05-15 14:44 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/htc.h | 372 +++++++++++++++++++++++++++++++++
1 file changed, 372 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/htc.h
diff --git a/drivers/net/wireless/ath/ath10k/htc.h b/drivers/net/wireless/ath/ath10k/htc.h
new file mode 100644
index 0000000..6b4f75e
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/htc.h
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _HTC_H_
+#define _HTC_H_
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/bug.h>
+#include <linux/skbuff.h>
+#include <linux/semaphore.h>
+#include <linux/timer.h>
+
+struct ath10k;
+
+/****************/
+/* HTC protocol */
+/****************/
+
+/*
+ * HTC - host-target control protocol
+ *
+ * tx packets are generally <htc_hdr><payload>
+ * rx packets are more complex: <htc_hdr><payload><trailer>
+ *
+ * The payload + trailer length is stored in len.
+ * To get payload-only length one needs to payload - trailer_len.
+ *
+ * Trailer contains (possibly) multiple <htc_record>.
+ * Each record is a id-len-value.
+ *
+ * HTC header flags, control_byte0, control_byte1
+ * have different meaning depending whether its tx
+ * or rx.
+ *
+ * Alignment: htc_hdr, payload and trailer are
+ * 4-byte aligned.
+ */
+
+enum ath10k_htc_tx_flags {
+ ATH10K_HTC_FLAG_NEED_CREDIT_UPDATE = 0x01,
+ ATH10K_HTC_FLAG_SEND_BUNDLE = 0x02
+};
+
+enum ath10k_htc_rx_flags {
+ ATH10K_HTC_FLAG_TRAILER_PRESENT = 0x02,
+ ATH10K_HTC_FLAG_BUNDLE_MASK = 0xF0
+};
+
+struct ath10k_htc_hdr {
+ u8 eid; /* @enum ath10k_htc_ep_id */
+ u8 flags; /* @enum ath10k_htc_tx_flags, ath10k_htc_rx_flags */
+ __le16 len;
+ union {
+ u8 trailer_len; /* for rx */
+ u8 control_byte0;
+ } __packed;
+ union {
+ u8 seq_no; /* for tx */
+ u8 control_byte1;
+ } __packed;
+ u8 pad0;
+ u8 pad1;
+} __packed __aligned(4);
+
+enum ath10k_ath10k_htc_msg_id {
+ ATH10K_HTC_MSG_READY_ID = 1,
+ ATH10K_HTC_MSG_CONNECT_SERVICE_ID = 2,
+ ATH10K_HTC_MSG_CONNECT_SERVICE_RESP_ID = 3,
+ ATH10K_HTC_MSG_SETUP_COMPLETE_ID = 4,
+ ATH10K_HTC_MSG_SETUP_COMPLETE_EX_ID = 5,
+ ATH10K_HTC_MSG_SEND_SUSPEND_COMPLETE = 6
+};
+
+enum ath10k_htc_version {
+ ATH10K_HTC_VERSION_2P0 = 0x00, /* 2.0 */
+ ATH10K_HTC_VERSION_2P1 = 0x01, /* 2.1 */
+};
+
+enum ath10k_htc_conn_flags {
+ ATH10K_HTC_CONN_FLAGS_THRESHOLD_LEVEL_ONE_FOURTH = 0x0,
+ ATH10K_HTC_CONN_FLAGS_THRESHOLD_LEVEL_ONE_HALF = 0x1,
+ ATH10K_HTC_CONN_FLAGS_THRESHOLD_LEVEL_THREE_FOURTHS = 0x2,
+ ATH10K_HTC_CONN_FLAGS_THRESHOLD_LEVEL_UNITY = 0x3,
+#define ATH10K_HTC_CONN_FLAGS_THRESHOLD_LEVEL_MASK 0x3
+ ATH10K_HTC_CONN_FLAGS_REDUCE_CREDIT_DRIBBLE = 1 << 2,
+ ATH10K_HTC_CONN_FLAGS_DISABLE_CREDIT_FLOW_CTRL = 1 << 3
+#define ATH10K_HTC_CONN_FLAGS_RECV_ALLOC_MASK 0xFF00
+#define ATH10K_HTC_CONN_FLAGS_RECV_ALLOC_LSB 8
+};
+
+enum ath10k_htc_conn_svc_status {
+ ATH10K_HTC_CONN_SVC_STATUS_SUCCESS = 0,
+ ATH10K_HTC_CONN_SVC_STATUS_NOT_FOUND = 1,
+ ATH10K_HTC_CONN_SVC_STATUS_FAILED = 2,
+ ATH10K_HTC_CONN_SVC_STATUS_NO_RESOURCES = 3,
+ ATH10K_HTC_CONN_SVC_STATUS_NO_MORE_EP = 4
+};
+
+struct ath10k_ath10k_htc_msg_hdr {
+ __le16 message_id; /* @enum htc_message_id */
+} __packed;
+
+struct ath10k_htc_unknown {
+ u8 pad0;
+ u8 pad1;
+} __packed;
+
+struct ath10k_htc_ready {
+ __le16 credit_count;
+ __le16 credit_size;
+ u8 max_endpoints;
+ u8 pad0;
+} __packed;
+
+struct ath10k_htc_ready_extended {
+ struct ath10k_htc_ready base;
+ u8 htc_version; /* @enum ath10k_htc_version */
+ u8 max_msgs_per_htc_bundle;
+ u8 pad0;
+ u8 pad1;
+} __packed;
+
+struct ath10k_htc_conn_svc {
+ __le16 service_id;
+ __le16 flags; /* @enum ath10k_htc_conn_flags */
+ u8 pad0;
+ u8 pad1;
+} __packed;
+
+struct ath10k_htc_conn_svc_response {
+ __le16 service_id;
+ u8 status; /* @enum ath10k_htc_conn_svc_status */
+ u8 eid;
+ __le16 max_msg_size;
+} __packed;
+
+struct ath10k_htc_setup_complete_extended {
+ u8 pad0;
+ u8 pad1;
+ __le32 flags; /* @enum htc_setup_complete_flags */
+ u8 max_msgs_per_bundled_recv;
+ u8 pad2;
+ u8 pad3;
+ u8 pad4;
+} __packed;
+
+struct ath10k_htc_msg {
+ struct ath10k_ath10k_htc_msg_hdr hdr;
+ union {
+ /* host-to-target */
+ struct ath10k_htc_conn_svc connect_service;
+ struct ath10k_htc_ready ready;
+ struct ath10k_htc_ready_extended ready_ext;
+ struct ath10k_htc_unknown unknown;
+ struct ath10k_htc_setup_complete_extended setup_complete_ext;
+
+ /* target-to-host */
+ struct ath10k_htc_conn_svc_response connect_service_response;
+ };
+} __packed __aligned(4);
+
+enum ath10k_ath10k_htc_record_id {
+ ATH10K_HTC_RECORD_NULL = 0,
+ ATH10K_HTC_RECORD_CREDITS = 1
+};
+
+struct ath10k_ath10k_htc_record_hdr {
+ u8 id; /* @enum ath10k_ath10k_htc_record_id */
+ u8 len;
+ u8 pad0;
+ u8 pad1;
+} __packed;
+
+struct ath10k_htc_credit_report {
+ u8 eid; /* @enum ath10k_htc_ep_id */
+ u8 credits;
+ u8 pad0;
+ u8 pad1;
+} __packed;
+
+struct ath10k_htc_record {
+ struct ath10k_ath10k_htc_record_hdr hdr;
+ union {
+ struct ath10k_htc_credit_report credit_report[0];
+ u8 pauload[0];
+ };
+} __packed __aligned(4);
+
+/*
+ * note: the trailer offset is dynamic depending
+ * on payload length. this is only a struct layout draft
+ */
+struct ath10k_htc_frame {
+ struct ath10k_htc_hdr hdr;
+ union {
+ struct ath10k_htc_msg msg;
+ u8 payload[0];
+ };
+ struct ath10k_htc_record trailer[0];
+} __packed __aligned(4);
+
+
+/*******************/
+/* Host-side stuff */
+/*******************/
+
+enum ath10k_htc_svc_gid {
+ ATH10K_HTC_SVC_GRP_RSVD = 0,
+ ATH10K_HTC_SVC_GRP_WMI = 1,
+ ATH10K_HTC_SVC_GRP_NMI = 2,
+ ATH10K_HTC_SVC_GRP_HTT = 3,
+
+ ATH10K_HTC_SVC_GRP_TEST = 254,
+ ATH10K_HTC_SVC_GRP_LAST = 255,
+};
+
+#define SVC(group, idx) \
+ (int)(((int)(group) << 8) | (int)(idx))
+
+enum ath10k_htc_svc_id {
+ /* NOTE: service ID of 0x0000 is reserved and should never be used */
+ ATH10K_HTC_SVC_ID_RESERVED = 0x0000,
+ ATH10K_HTC_SVC_ID_UNUSED = ATH10K_HTC_SVC_ID_RESERVED,
+
+ ATH10K_HTC_SVC_ID_RSVD_CTRL = SVC(ATH10K_HTC_SVC_GRP_RSVD, 1),
+ ATH10K_HTC_SVC_ID_WMI_CONTROL = SVC(ATH10K_HTC_SVC_GRP_WMI, 0),
+ ATH10K_HTC_SVC_ID_WMI_DATA_BE = SVC(ATH10K_HTC_SVC_GRP_WMI, 1),
+ ATH10K_HTC_SVC_ID_WMI_DATA_BK = SVC(ATH10K_HTC_SVC_GRP_WMI, 2),
+ ATH10K_HTC_SVC_ID_WMI_DATA_VI = SVC(ATH10K_HTC_SVC_GRP_WMI, 3),
+ ATH10K_HTC_SVC_ID_WMI_DATA_VO = SVC(ATH10K_HTC_SVC_GRP_WMI, 4),
+
+ ATH10K_HTC_SVC_ID_NMI_CONTROL = SVC(ATH10K_HTC_SVC_GRP_NMI, 0),
+ ATH10K_HTC_SVC_ID_NMI_DATA = SVC(ATH10K_HTC_SVC_GRP_NMI, 1),
+
+ ATH10K_HTC_SVC_ID_HTT_DATA_MSG = SVC(ATH10K_HTC_SVC_GRP_HTT, 0),
+
+ /* raw stream service (i.e. flash, tcmd, calibration apps) */
+ ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS = SVC(ATH10K_HTC_SVC_GRP_TEST, 0),
+};
+
+#undef SVC
+
+enum ath10k_htc_ep_id {
+ ATH10K_HTC_EP_UNUSED = -1,
+ ATH10K_HTC_EP_0 = 0,
+ ATH10K_HTC_EP_1 = 1,
+ ATH10K_HTC_EP_2,
+ ATH10K_HTC_EP_3,
+ ATH10K_HTC_EP_4,
+ ATH10K_HTC_EP_5,
+ ATH10K_HTC_EP_6,
+ ATH10K_HTC_EP_7,
+ ATH10K_HTC_EP_8,
+ ATH10K_HTC_EP_COUNT,
+};
+
+struct ath10k_htc_ops {
+ void (*target_send_suspend_complete)(struct ath10k *ar);
+};
+
+struct ath10k_htc_ep_ops {
+ void (*ep_tx_complete)(struct ath10k *, struct sk_buff *);
+ void (*ep_rx_complete)(struct ath10k *, struct sk_buff *);
+ void (*stop_queue)(struct ath10k *);
+ void (*wake_queue)(struct ath10k *);
+};
+
+/* service connection information */
+struct ath10k_htc_svc_conn_req {
+ u16 service_id;
+ struct ath10k_htc_ep_ops ep_ops;
+ int max_send_queue_depth;
+};
+
+/* service connection response information */
+struct ath10k_htc_svc_conn_resp {
+ u8 buffer_len;
+ u8 actual_len;
+ enum ath10k_htc_ep_id eid;
+ unsigned int max_msg_len;
+ u8 connect_resp_code;
+};
+
+#define ATH10K_NUM_CONTROL_TX_BUFFERS 2
+#define ATH10K_HTC_MAX_LEN 4096
+#define ATH10K_HTC_MAX_CTRL_MSG_LEN 256
+#define ATH10K_HTC_WAIT_TIMEOUT_HZ (1*HZ)
+#define ATH10K_HTC_CONTROL_BUFFER_SIZE (ATH10K_HTC_MAX_CTRL_MSG_LEN + \
+ sizeof(struct ath10k_htc_hdr))
+#define ATH10K_HTC_CONN_SVC_TIMEOUT_HZ (1*HZ)
+
+struct ath10k_htc_ep {
+ struct ath10k_htc *htc;
+ enum ath10k_htc_ep_id eid;
+ enum ath10k_htc_svc_id service_id;
+ struct ath10k_htc_ep_ops ep_ops;
+
+ int max_tx_queue_depth;
+ int max_ep_message_len;
+ u8 ul_pipe_id;
+ u8 dl_pipe_id;
+ int ul_is_polled; /* call HIF to get tx completions */
+ int dl_is_polled; /* call HIF to fetch rx (not implemented) */
+
+ struct sk_buff_head tx_queue;
+ int tx_queue_len;
+ bool tx_queue_stopped;
+
+ u8 seq_no; /* for debugging */
+ int tx_credits;
+ int tx_credit_size;
+ int tx_credits_per_max_message;
+ bool tx_credit_flow_enabled;
+
+ struct work_struct send_work;
+};
+
+struct ath10k_htc_svc_tx_credits {
+ u16 service_id;
+ u8 credit_allocation;
+};
+
+struct ath10k_htc {
+ struct ath10k *ar;
+ struct ath10k_htc_ep endpoint[ATH10K_HTC_EP_COUNT];
+
+ /* protects endpoint and stopping fields */
+ spinlock_t tx_lock;
+
+ struct ath10k_htc_ops htc_ops;
+
+ u8 control_resp_buffer[ATH10K_HTC_MAX_CTRL_MSG_LEN];
+ int control_resp_len;
+
+ struct completion ctl_resp;
+
+ int total_transmit_credits;
+ struct ath10k_htc_svc_tx_credits service_tx_alloc[ATH10K_HTC_EP_COUNT];
+ int target_credit_size;
+
+ bool stopping;
+};
+
+struct ath10k_htc *ath10k_htc_create(struct ath10k *ar,
+ struct ath10k_htc_ops *htc_ops);
+int ath10k_htc_wait_target(struct ath10k_htc *htc);
+int ath10k_htc_start(struct ath10k_htc *htc);
+int ath10k_htc_connect_service(struct ath10k_htc *htc,
+ struct ath10k_htc_svc_conn_req *conn_req,
+ struct ath10k_htc_svc_conn_resp *conn_resp);
+int ath10k_htc_send(struct ath10k_htc *htc, enum ath10k_htc_ep_id eid,
+ struct sk_buff *packet);
+void ath10k_htc_stop(struct ath10k_htc *htc);
+void ath10k_htc_destroy(struct ath10k_htc *htc);
+struct sk_buff *ath10k_htc_alloc_skb(int size);
+
+#endif
^ permalink raw reply related
* [PATCH 05/29] ath10k: add core.c
From: Kalle Valo @ 2013-05-15 14:43 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/core.c | 665 ++++++++++++++++++++++++++++++++
1 file changed, 665 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/core.c
diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c
new file mode 100644
index 0000000..2b3426b
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/core.c
@@ -0,0 +1,665 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/module.h>
+#include <linux/firmware.h>
+
+#include "core.h"
+#include "mac.h"
+#include "htc.h"
+#include "hif.h"
+#include "wmi.h"
+#include "bmi.h"
+#include "debug.h"
+#include "htt.h"
+
+unsigned int ath10k_debug_mask;
+static bool uart_print;
+static unsigned int ath10k_p2p;
+module_param_named(debug_mask, ath10k_debug_mask, uint, 0644);
+module_param(uart_print, bool, 0644);
+module_param_named(p2p, ath10k_p2p, uint, 0644);
+MODULE_PARM_DESC(debug_mask, "Debugging mask");
+MODULE_PARM_DESC(uart_print, "Uart target debugging");
+MODULE_PARM_DESC(p2p, "Enable ath10k P2P support");
+
+static const struct ath10k_hw_params ath10k_hw_params_list[] = {
+ {
+ .id = QCA988X_HW_1_0_VERSION,
+ .name = "qca988x hw1.0",
+ .patch_load_addr = QCA988X_HW_1_0_PATCH_LOAD_ADDR,
+ .fw = {
+ .dir = QCA988X_HW_1_0_FW_DIR,
+ .fw = QCA988X_HW_1_0_FW_FILE,
+ .otp = QCA988X_HW_1_0_OTP_FILE,
+ .board = QCA988X_HW_1_0_BOARD_DATA_FILE,
+ },
+ },
+ {
+ .id = QCA988X_HW_2_0_VERSION,
+ .name = "qca988x hw2.0",
+ .patch_load_addr = QCA988X_HW_2_0_PATCH_LOAD_ADDR,
+ .fw = {
+ .dir = QCA988X_HW_2_0_FW_DIR,
+ .fw = QCA988X_HW_2_0_FW_FILE,
+ .otp = QCA988X_HW_2_0_OTP_FILE,
+ .board = QCA988X_HW_2_0_BOARD_DATA_FILE,
+ },
+ },
+};
+
+static void ath10k_send_suspend_complete(struct ath10k *ar)
+{
+ ath10k_dbg(ATH10K_DBG_CORE, "%s\n", __func__);
+
+ ar->is_target_paused = true;
+ wake_up(&ar->event_queue);
+}
+
+static int ath10k_check_fw_version(struct ath10k *ar)
+{
+ char version[32];
+
+ if (ar->fw_version_major >= SUPPORTED_FW_MAJOR &&
+ ar->fw_version_minor >= SUPPORTED_FW_MINOR &&
+ ar->fw_version_release >= SUPPORTED_FW_RELEASE &&
+ ar->fw_version_build >= SUPPORTED_FW_BUILD)
+ return 0;
+
+ snprintf(version, sizeof(version), "%u.%u.%u.%u",
+ SUPPORTED_FW_MAJOR, SUPPORTED_FW_MINOR,
+ SUPPORTED_FW_RELEASE, SUPPORTED_FW_BUILD);
+
+ ath10k_warn("WARNING: Firmware version %s is not officially supported.\n",
+ ar->hw->wiphy->fw_version);
+ ath10k_warn("Please upgrade to version %s (or newer)\n", version);
+
+ return 0;
+}
+
+static int ath10k_init_connect_htc(struct ath10k *ar)
+{
+ int status;
+
+ status = ath10k_wmi_connect_htc_service(ar);
+ if (status)
+ goto conn_fail;
+
+ /* Start HTC */
+ status = ath10k_htc_start(ar->htc);
+ if (status)
+ goto conn_fail;
+
+ /* Wait for WMI event to be ready */
+ status = ath10k_wmi_wait_for_service_ready(ar);
+ if (status <= 0) {
+ ath10k_warn("wmi service ready event not received");
+ status = -ETIMEDOUT;
+ goto timeout;
+ }
+
+ ath10k_dbg(ATH10K_DBG_CORE, "core wmi ready\n");
+ return 0;
+
+timeout:
+ ath10k_htc_stop(ar->htc);
+conn_fail:
+ return status;
+}
+
+static int ath10k_init_configure_target(struct ath10k *ar)
+{
+ u32 param_host;
+ int ret;
+
+ /* tell target which HTC version it is used*/
+ ret = ath10k_bmi_write32(ar, hi_app_host_interest,
+ HTC_PROTOCOL_VERSION);
+ if (ret) {
+ ath10k_err("settings HTC version failed\n");
+ return ret;
+ }
+
+ /* set the firmware mode to STA/IBSS/AP */
+ ret = ath10k_bmi_read32(ar, hi_option_flag, ¶m_host);
+ if (ret) {
+ ath10k_err("setting firmware mode (1/2) failed\n");
+ return ret;
+ }
+
+ /* TODO following parameters need to be re-visited. */
+ /* num_device */
+ param_host |= (1 << HI_OPTION_NUM_DEV_SHIFT);
+ /* Firmware mode */
+ /* FIXME: Why FW_MODE_AP ??.*/
+ param_host |= (HI_OPTION_FW_MODE_AP << HI_OPTION_FW_MODE_SHIFT);
+ /* mac_addr_method */
+ param_host |= (1 << HI_OPTION_MAC_ADDR_METHOD_SHIFT);
+ /* firmware_bridge */
+ param_host |= (0 << HI_OPTION_FW_BRIDGE_SHIFT);
+ /* fwsubmode */
+ param_host |= (0 << HI_OPTION_FW_SUBMODE_SHIFT);
+
+ ret = ath10k_bmi_write32(ar, hi_option_flag, param_host);
+ if (ret) {
+ ath10k_err("setting firmware mode (2/2) failed\n");
+ return ret;
+ }
+
+ /* We do all byte-swapping on the host */
+ ret = ath10k_bmi_write32(ar, hi_be, 0);
+ if (ret) {
+ ath10k_err("setting host CPU BE mode failed\n");
+ return ret;
+ }
+
+ /* FW descriptor/Data swap flags */
+ ret = ath10k_bmi_write32(ar, hi_fw_swap, 0);
+
+ if (ret) {
+ ath10k_err("setting FW data/desc swap flags failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar,
+ const char *dir,
+ const char *file)
+{
+ char filename[100];
+ const struct firmware *fw;
+ int ret;
+
+ if (file == NULL)
+ return ERR_PTR(-ENOENT);
+
+ if (dir == NULL)
+ dir = ".";
+
+ snprintf(filename, sizeof(filename), "%s/%s", dir, file);
+ ret = request_firmware(&fw, filename, ar->dev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return fw;
+}
+
+static int ath10k_push_board_ext_data(struct ath10k *ar,
+ const struct firmware *fw)
+{
+ u32 board_data_size = QCA988X_BOARD_DATA_SZ;
+ u32 board_ext_data_size = QCA988X_BOARD_EXT_DATA_SZ;
+ u32 board_ext_data_addr;
+ int ret;
+
+ ret = ath10k_bmi_read32(ar, hi_board_ext_data, &board_ext_data_addr);
+ if (ret) {
+ ath10k_err("could not read board ext data addr (%d)\n", ret);
+ return ret;
+ }
+
+ ath10k_dbg(ATH10K_DBG_CORE,
+ "ath10k: Board extended Data download addr: 0x%x\n",
+ board_ext_data_addr);
+
+ if (board_ext_data_addr == 0)
+ return 0;
+
+ if (fw->size != (board_data_size + board_ext_data_size)) {
+ ath10k_err("invalid board (ext) data sizes %zu != %d+%d\n",
+ fw->size, board_data_size, board_ext_data_size);
+ return -EINVAL;
+ }
+
+ ret = ath10k_bmi_write_memory(ar, board_ext_data_addr,
+ fw->data + board_data_size,
+ board_ext_data_size);
+ if (ret) {
+ ath10k_err("could not write board ext data (%d)\n", ret);
+ return ret;
+ }
+
+ ret = ath10k_bmi_write32(ar, hi_board_ext_data_config,
+ (board_ext_data_size << 16) | 1);
+ if (ret) {
+ ath10k_err("could not write board ext data bit (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ath10k_download_board_data(struct ath10k *ar)
+{
+ u32 board_data_size = QCA988X_BOARD_DATA_SZ;
+ u32 address;
+ const struct firmware *fw;
+ int ret;
+
+ fw = ath10k_fetch_fw_file(ar, ar->hw_params.fw.dir,
+ ar->hw_params.fw.board);
+ if (IS_ERR(fw)) {
+ ath10k_err("could not fetch board data fw file (%ld)\n",
+ PTR_ERR(fw));
+ return PTR_ERR(fw);
+ }
+
+ ret = ath10k_push_board_ext_data(ar, fw);
+ if (ret) {
+ ath10k_err("could not push board ext data (%d)\n", ret);
+ goto exit;
+ }
+
+ ret = ath10k_bmi_read32(ar, hi_board_data, &address);
+ if (ret) {
+ ath10k_err("could not read board data addr (%d)\n", ret);
+ goto exit;
+ }
+
+ ret = ath10k_bmi_write_memory(ar, address, fw->data,
+ min_t(u32, board_data_size, fw->size));
+ if (ret) {
+ ath10k_err("could not write board data (%d)\n", ret);
+ goto exit;
+ }
+
+ ret = ath10k_bmi_write32(ar, hi_board_data_initialized, 1);
+ if (ret) {
+ ath10k_err("could not write board data bit (%d)\n", ret);
+ goto exit;
+ }
+
+exit:
+ release_firmware(fw);
+ return ret;
+}
+
+static int ath10k_download_and_run_otp(struct ath10k *ar)
+{
+ const struct firmware *fw;
+ u32 address;
+ u32 exec_param;
+ int ret;
+
+ /* OTP is optional */
+
+ if (ar->hw_params.fw.otp == NULL) {
+ ath10k_info("otp file not defined\n");
+ return 0;
+ }
+
+ address = ar->hw_params.patch_load_addr;
+
+ fw = ath10k_fetch_fw_file(ar, ar->hw_params.fw.dir,
+ ar->hw_params.fw.otp);
+ if (IS_ERR(fw)) {
+ ath10k_warn("could not fetch otp (%ld)\n", PTR_ERR(fw));
+ return 0;
+ }
+
+ ret = ath10k_bmi_fast_download(ar, address, fw->data, fw->size);
+ if (ret) {
+ ath10k_err("could not write otp (%d)\n", ret);
+ goto exit;
+ }
+
+ exec_param = 0;
+ ret = ath10k_bmi_execute(ar, address, &exec_param);
+ if (ret) {
+ ath10k_err("could not execute otp (%d)\n", ret);
+ goto exit;
+ }
+
+exit:
+ release_firmware(fw);
+ return ret;
+}
+
+static int ath10k_download_fw(struct ath10k *ar)
+{
+ const struct firmware *fw;
+ u32 address;
+ int ret;
+
+ if (ar->hw_params.fw.fw == NULL)
+ return -EINVAL;
+
+ address = ar->hw_params.patch_load_addr;
+
+ fw = ath10k_fetch_fw_file(ar, ar->hw_params.fw.dir,
+ ar->hw_params.fw.fw);
+ if (IS_ERR(fw)) {
+ ath10k_err("could not fetch fw (%ld)\n", PTR_ERR(fw));
+ return PTR_ERR(fw);
+ }
+
+ ret = ath10k_bmi_fast_download(ar, address, fw->data, fw->size);
+ if (ret) {
+ ath10k_err("could not write fw (%d)\n", ret);
+ goto exit;
+ }
+
+exit:
+ release_firmware(fw);
+ return ret;
+}
+
+static int ath10k_init_download_firmware(struct ath10k *ar)
+{
+ int ret;
+
+ ret = ath10k_download_board_data(ar);
+ if (ret)
+ return ret;
+
+ ret = ath10k_download_and_run_otp(ar);
+ if (ret)
+ return ret;
+
+ ret = ath10k_download_fw(ar);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int ath10k_init_uart(struct ath10k *ar)
+{
+ int ret;
+
+ /*
+ * Explicitly setting UART prints to zero as target turns it on
+ * based on scratch registers.
+ */
+ ret = ath10k_bmi_write32(ar, hi_serial_enable, 0);
+ if (ret) {
+ ath10k_warn("could not disable UART prints (%d)\n", ret);
+ return ret;
+ }
+
+ if (!uart_print) {
+ ath10k_info("UART prints disabled\n");
+ return 0;
+ }
+
+ ret = ath10k_bmi_write32(ar, hi_dbg_uart_txpin, 7);
+ if (ret) {
+ ath10k_warn("could not enable UART prints (%d)\n", ret);
+ return ret;
+ }
+
+ ret = ath10k_bmi_write32(ar, hi_serial_enable, 1);
+ if (ret) {
+ ath10k_warn("could not enable UART prints (%d)\n", ret);
+ return ret;
+ }
+
+ ath10k_info("UART prints enabled\n");
+ return 0;
+}
+
+static int ath10k_init_hw_params(struct ath10k *ar)
+{
+ const struct ath10k_hw_params *uninitialized_var(hw_params);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ath10k_hw_params_list); i++) {
+ hw_params = &ath10k_hw_params_list[i];
+
+ if (hw_params->id == ar->target_version)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(ath10k_hw_params_list)) {
+ ath10k_err("Unsupported hardware version: 0x%x\n",
+ ar->target_version);
+ return -EINVAL;
+ }
+
+ ar->hw_params = *hw_params;
+
+ ath10k_info("Hardware name %s version 0x%x\n",
+ ar->hw_params.name, ar->target_version);
+
+ return 0;
+}
+
+struct ath10k *ath10k_core_create(void *hif_priv, struct device *dev,
+ enum ath10k_bus bus,
+ const struct ath10k_hif_ops *hif_ops)
+{
+ struct ath10k *ar;
+
+ ar = ath10k_mac_create();
+ if (!ar)
+ return NULL;
+
+ ar->ath_common.priv = ar;
+ ar->ath_common.hw = ar->hw;
+
+ ar->p2p = !!ath10k_p2p;
+ ar->dev = dev;
+
+ ar->hif.priv = hif_priv;
+ ar->hif.ops = hif_ops;
+ ar->hif.bus = bus;
+
+ ar->free_vdev_map = 0xFF; /* 8 vdevs */
+
+ init_completion(&ar->scan.started);
+ init_completion(&ar->scan.completed);
+ init_completion(&ar->scan.on_channel);
+
+ init_completion(&ar->install_key_done);
+ init_completion(&ar->vdev_setup_done);
+
+ setup_timer(&ar->scan.timeout, ath10k_reset_scan, (unsigned long)ar);
+
+ ar->workqueue = create_singlethread_workqueue("ath10k_wq");
+ if (!ar->workqueue)
+ goto err_wq;
+
+ mutex_init(&ar->conf_mutex);
+ spin_lock_init(&ar->data_lock);
+
+ INIT_LIST_HEAD(&ar->peers);
+ init_waitqueue_head(&ar->peer_mapping_wq);
+
+ init_completion(&ar->offchan_tx_completed);
+ INIT_WORK(&ar->offchan_tx_work, ath10k_offchan_tx_work);
+ skb_queue_head_init(&ar->offchan_tx_queue);
+
+ init_waitqueue_head(&ar->event_queue);
+
+ return ar;
+
+err_wq:
+ ath10k_mac_destroy(ar);
+ return NULL;
+}
+EXPORT_SYMBOL(ath10k_core_create);
+
+void ath10k_core_destroy(struct ath10k *ar)
+{
+ flush_workqueue(ar->workqueue);
+ destroy_workqueue(ar->workqueue);
+
+ ath10k_mac_destroy(ar);
+}
+EXPORT_SYMBOL(ath10k_core_destroy);
+
+
+int ath10k_core_register(struct ath10k *ar)
+{
+ struct ath10k_htc_ops htc_ops;
+ struct bmi_target_info target_info;
+ int status;
+
+ memset(&target_info, 0, sizeof(target_info));
+ status = ath10k_bmi_get_target_info(ar, &target_info);
+ if (status)
+ goto err;
+
+ ar->target_version = target_info.version;
+ ar->hw->wiphy->hw_version = target_info.version;
+
+ status = ath10k_init_hw_params(ar);
+ if (status)
+ goto err;
+
+ if (ath10k_init_configure_target(ar)) {
+ status = -EINVAL;
+ goto err;
+ }
+
+ status = ath10k_init_download_firmware(ar);
+ if (status)
+ goto err;
+
+ status = ath10k_init_uart(ar);
+ if (status)
+ goto err;
+
+ htc_ops.target_send_suspend_complete = ath10k_send_suspend_complete;
+
+ ar->htc = ath10k_htc_create(ar, &htc_ops);
+ if (IS_ERR(ar->htc)) {
+ status = PTR_ERR(ar->htc);
+ ath10k_err("could not create HTC (%d)\n", status);
+ goto err;
+ }
+
+ status = ath10k_bmi_done(ar);
+ if (status)
+ goto err_htc_destroy;
+
+ status = ath10k_wmi_attach(ar);
+ if (status) {
+ ath10k_err("WMI attach failed: %d\n", status);
+ goto err_htc_destroy;
+ }
+
+ status = ath10k_htc_wait_target(ar->htc);
+ if (status)
+ goto err_wmi_detach;
+
+ ar->htt = ath10k_htt_attach(ar);
+ if (!ar->htt) {
+ status = -ENOMEM;
+ goto err_wmi_detach;
+ }
+
+ status = ath10k_init_connect_htc(ar);
+ if (status)
+ goto err_htt_detach;
+
+ ath10k_info("firmware %s booted\n", ar->hw->wiphy->fw_version);
+
+ status = ath10k_check_fw_version(ar);
+ if (status)
+ goto err_disconnect_htc;
+
+ status = ath10k_wmi_cmd_init(ar);
+ if (status) {
+ ath10k_err("could not send WMI init command (%d)\n", status);
+ goto err_disconnect_htc;
+ }
+
+ status = ath10k_wmi_wait_for_unified_ready(ar);
+ if (status <= 0) {
+ ath10k_err("wmi unified ready event not received\n");
+ status = -ETIMEDOUT;
+ goto err_disconnect_htc;
+ }
+
+ status = ath10k_htt_attach_target(ar->htt);
+ if (status)
+ goto err_disconnect_htc;
+
+ status = ath10k_mac_register(ar);
+ if (status)
+ goto err_disconnect_htc;
+
+ status = ath10k_debug_create(ar);
+ if (status) {
+ ath10k_err("unable to initialize debugfs\n");
+ goto err_unregister_mac;
+ }
+
+ return 0;
+
+err_unregister_mac:
+ ath10k_mac_unregister(ar);
+err_disconnect_htc:
+ ath10k_htc_stop(ar->htc);
+err_htt_detach:
+ ath10k_htt_detach(ar->htt);
+err_wmi_detach:
+ ath10k_wmi_detach(ar);
+err_htc_destroy:
+ ath10k_htc_destroy(ar->htc);
+err:
+ return status;
+}
+EXPORT_SYMBOL(ath10k_core_register);
+
+void ath10k_core_unregister(struct ath10k *ar)
+{
+ /* We must unregister from mac80211 before we stop HTC and HIF.
+ * Otherwise we will fail to submit commands to FW and mac80211 will be
+ * unhappy about callback failures. */
+ ath10k_mac_unregister(ar);
+ ath10k_htc_stop(ar->htc);
+ ath10k_htt_detach(ar->htt);
+ ath10k_wmi_detach(ar);
+ ath10k_htc_destroy(ar->htc);
+}
+EXPORT_SYMBOL(ath10k_core_unregister);
+
+int ath10k_core_target_suspend(struct ath10k *ar)
+{
+ int ret;
+
+ ath10k_dbg(ATH10K_DBG_CORE, "%s: called", __func__);
+
+ ret = ath10k_wmi_pdev_suspend_target(ar);
+ if (ret)
+ ath10k_warn("could not suspend target (%d)\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(ath10k_core_target_suspend);
+
+int ath10k_core_target_resume(struct ath10k *ar)
+{
+ int ret;
+
+ ath10k_dbg(ATH10K_DBG_CORE, "%s: called", __func__);
+
+ ret = ath10k_wmi_pdev_resume_target(ar);
+ if (ret)
+ ath10k_warn("could not resume target (%d)\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(ath10k_core_target_resume);
+
+MODULE_AUTHOR("Qualcomm Atheros");
+MODULE_DESCRIPTION("Core module for QCA988X PCIe devices.");
+MODULE_LICENSE("Dual BSD/GPL");
^ permalink raw reply related
* [PATCH 25/29] ath10k: add txrx.c
From: Kalle Valo @ 2013-05-15 14:46 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/txrx.c | 415 ++++++++++++++++++++++++++++++++
1 file changed, 415 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/txrx.c
diff --git a/drivers/net/wireless/ath/ath10k/txrx.c b/drivers/net/wireless/ath/ath10k/txrx.c
new file mode 100644
index 0000000..a32448c
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/txrx.c
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "core.h"
+#include "txrx.h"
+#include "htt.h"
+#include "mac.h"
+#include "debug.h"
+
+static void ath10k_report_offchan_tx(struct ath10k *ar, struct sk_buff *skb)
+{
+ if (!ATH10K_SKB_CB(skb)->htt.is_offchan)
+ return;
+
+ /* If the original wait_for_completion() timed out before
+ * {data,mgmt}_tx_completed() was called then we could complete
+ * offchan_tx_completed for a different skb. Prevent this by using
+ * offchan_tx_skb. */
+ spin_lock_bh(&ar->data_lock);
+ if (ar->offchan_tx_skb != skb) {
+ ath10k_warn("completed old offchannel frame\n");
+ goto out;
+ }
+
+ complete(&ar->offchan_tx_completed);
+ ar->offchan_tx_skb = NULL; /* just for sanity */
+
+ ath10k_dbg(ATH10K_DBG_HTT, "completed offchannel skb %p\n", skb);
+out:
+ spin_unlock_bh(&ar->data_lock);
+}
+
+void ath10k_txrx_tx_unref(struct ath10k_htt *htt, struct sk_buff *txdesc)
+{
+ struct device *dev = htt->ar->dev;
+ struct ieee80211_tx_info *info;
+ struct sk_buff *txfrag = ATH10K_SKB_CB(txdesc)->htt.txfrag;
+ struct sk_buff *msdu = ATH10K_SKB_CB(txdesc)->htt.msdu;
+ int ret;
+
+ if (ATH10K_SKB_CB(txdesc)->htt.refcount == 0)
+ return;
+
+ ATH10K_SKB_CB(txdesc)->htt.refcount--;
+
+ if (ATH10K_SKB_CB(txdesc)->htt.refcount > 0)
+ return;
+
+ if (txfrag) {
+ ret = ath10k_skb_unmap(dev, txfrag);
+ if (ret)
+ ath10k_warn("txfrag unmap failed (%d)\n", ret);
+
+ dev_kfree_skb_any(txfrag);
+ }
+
+ ret = ath10k_skb_unmap(dev, msdu);
+ if (ret)
+ ath10k_warn("data skb unmap failed (%d)\n", ret);
+
+ ath10k_report_offchan_tx(htt->ar, msdu);
+
+ info = IEEE80211_SKB_CB(msdu);
+ memset(&info->status, 0, sizeof(info->status));
+
+ if (ATH10K_SKB_CB(txdesc)->htt.discard) {
+ ieee80211_free_txskb(htt->ar->hw, msdu);
+ goto exit;
+ }
+
+ if (!(info->flags & IEEE80211_TX_CTL_NO_ACK))
+ info->flags |= IEEE80211_TX_STAT_ACK;
+
+ if (ATH10K_SKB_CB(txdesc)->htt.no_ack)
+ info->flags &= ~IEEE80211_TX_STAT_ACK;
+
+ ieee80211_tx_status(htt->ar->hw, msdu);
+ /* we do not own the msdu anymore */
+
+exit:
+ spin_lock_bh(&htt->tx_lock);
+ htt->pending_tx[ATH10K_SKB_CB(txdesc)->htt.msdu_id] = NULL;
+ ath10k_htt_tx_free_msdu_id(htt, ATH10K_SKB_CB(txdesc)->htt.msdu_id);
+ if (bitmap_empty(htt->used_msdu_ids, HTT_MAX_NUM_PENDING_TX))
+ wake_up(&htt->empty_tx_wq);
+ spin_unlock_bh(&htt->tx_lock);
+
+ dev_kfree_skb_any(txdesc);
+}
+
+void ath10k_txrx_tx_completed(struct ath10k_htt *htt,
+ const struct htt_tx_done *tx_done)
+{
+ struct sk_buff *txdesc;
+
+ ath10k_dbg(ATH10K_DBG_HTT, "htt tx completion msdu_id %u discard %d no_ack %d\n",
+ tx_done->msdu_id, !!tx_done->discard, !!tx_done->no_ack);
+
+ if (tx_done->msdu_id >= ARRAY_SIZE(htt->pending_tx)) {
+ ath10k_warn("warning: msdu_id %d too big, ignoring\n",
+ tx_done->msdu_id);
+ return;
+ }
+
+ txdesc = htt->pending_tx[tx_done->msdu_id];
+
+ ATH10K_SKB_CB(txdesc)->htt.discard = tx_done->discard;
+ ATH10K_SKB_CB(txdesc)->htt.no_ack = tx_done->no_ack;
+
+ ath10k_txrx_tx_unref(htt, txdesc);
+}
+
+static const u8 rx_legacy_rate_idx[] = {
+ 3, /* 0x00 - 11Mbps */
+ 2, /* 0x01 - 5.5Mbps */
+ 1, /* 0x02 - 2Mbps */
+ 0, /* 0x03 - 1Mbps */
+ 3, /* 0x04 - 11Mbps */
+ 2, /* 0x05 - 5.5Mbps */
+ 1, /* 0x06 - 2Mbps */
+ 0, /* 0x07 - 1Mbps */
+ 10, /* 0x08 - 48Mbps */
+ 8, /* 0x09 - 24Mbps */
+ 6, /* 0x0A - 12Mbps */
+ 4, /* 0x0B - 6Mbps */
+ 11, /* 0x0C - 54Mbps */
+ 9, /* 0x0D - 36Mbps */
+ 7, /* 0x0E - 18Mbps */
+ 5, /* 0x0F - 9Mbps */
+};
+
+static void process_rx_rates(struct ath10k *ar, struct htt_rx_info *info,
+ enum ieee80211_band band,
+ struct ieee80211_rx_status *status)
+{
+ u8 cck, rate, rate_idx, bw, sgi, mcs, nss;
+ u8 info0 = info->rate.info0;
+ u32 info1 = info->rate.info1;
+ u32 info2 = info->rate.info2;
+ u8 preamble = 0;
+
+ /* Check if valid fields */
+ if (!(info0 & HTT_RX_INDICATION_INFO0_START_VALID))
+ return;
+
+ preamble = MS(info1, HTT_RX_INDICATION_INFO1_PREAMBLE_TYPE);
+
+ switch (preamble) {
+ case HTT_RX_LEGACY:
+ cck = info0 & HTT_RX_INDICATION_INFO0_LEGACY_RATE_CCK;
+ rate = MS(info0, HTT_RX_INDICATION_INFO0_LEGACY_RATE);
+ rate_idx = 0;
+
+ if (rate < 0x08 || rate > 0x0F)
+ break;
+
+ switch (band) {
+ case IEEE80211_BAND_2GHZ:
+ if (cck)
+ rate &= ~BIT(3);
+ rate_idx = rx_legacy_rate_idx[rate];
+ break;
+ case IEEE80211_BAND_5GHZ:
+ rate_idx = rx_legacy_rate_idx[rate];
+ /* We are using same rate table registering
+ HW - ath10k_rates[]. In case of 5GHz skip
+ CCK rates, so -4 here */
+ rate_idx -= 4;
+ break;
+ default:
+ break;
+ }
+
+ status->rate_idx = rate_idx;
+ break;
+ case HTT_RX_HT:
+ case HTT_RX_HT_WITH_TXBF:
+ /* HT-SIG - Table 20-11 in info1 and info2 */
+ mcs = info1 & 0x1F;
+ nss = mcs >> 3;
+ bw = (info1 >> 7) & 1;
+ sgi = (info2 >> 7) & 1;
+
+ status->rate_idx = mcs;
+ status->flag |= RX_FLAG_HT;
+ if (sgi)
+ status->flag |= RX_FLAG_SHORT_GI;
+ if (bw)
+ status->flag |= RX_FLAG_40MHZ;
+ break;
+ case HTT_RX_VHT:
+ case HTT_RX_VHT_WITH_TXBF:
+ /* VHT-SIG-A1 in info 1, VHT-SIG-A2 in info2
+ TODO check this */
+ mcs = (info2 >> 4) & 0x0F;
+ nss = (info1 >> 10) & 0x07;
+ bw = info1 & 3;
+ sgi = info2 & 1;
+
+ status->rate_idx = mcs + (nss * 10);
+
+ if (sgi)
+ status->flag |= RX_FLAG_SHORT_GI;
+
+ switch (bw) {
+ /* 20MHZ */
+ case 0:
+ break;
+ /* 40MHZ */
+ case 1:
+ status->flag |= RX_FLAG_40MHZ;
+ break;
+ /* 80MHZ */
+ case 2:
+ status->flag |= RX_FLAG_80MHZ;
+ }
+
+ status->flag |= RX_FLAG_VHT;
+ break;
+ default:
+ break;
+ }
+}
+
+void ath10k_process_rx(struct ath10k *ar, struct htt_rx_info *info)
+{
+ struct ieee80211_rx_status *status;
+ struct ieee80211_channel *ch;
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)info->skb->data;
+
+ status = IEEE80211_SKB_RXCB(info->skb);
+ memset(status, 0, sizeof(*status));
+
+ if (info->encrypt_type != HTT_RX_MPDU_ENCRYPT_NONE) {
+ status->flag |= RX_FLAG_DECRYPTED | RX_FLAG_IV_STRIPPED |
+ RX_FLAG_MMIC_STRIPPED;
+ hdr->frame_control = __cpu_to_le16(
+ __le16_to_cpu(hdr->frame_control) &
+ ~IEEE80211_FCTL_PROTECTED);
+ }
+
+ if (info->status == HTT_RX_IND_MPDU_STATUS_TKIP_MIC_ERR)
+ status->flag |= RX_FLAG_MMIC_ERROR;
+
+ if (info->fcs_err)
+ status->flag |= RX_FLAG_FAILED_FCS_CRC;
+
+ status->signal = info->signal;
+
+ spin_lock_bh(&ar->data_lock);
+ ch = ar->scan_channel;
+ if (!ch)
+ ch = ar->rx_channel;
+ spin_unlock_bh(&ar->data_lock);
+
+ if (!ch) {
+ ath10k_warn("no channel configured; ignoring frame!\n");
+ dev_kfree_skb_any(info->skb);
+ return;
+ }
+
+ process_rx_rates(ar, info, ch->band, status);
+ status->band = ch->band;
+ status->freq = ch->center_freq;
+
+ ath10k_dbg(ATH10K_DBG_HTT,
+ "rx skb %p len %u %s%s%s%s%s %srate_idx %u vht_nss %u freq %u band %u\n",
+ info->skb,
+ info->skb->len,
+ status->flag == 0 ? "legacy" : "",
+ status->flag & RX_FLAG_HT ? "ht" : "",
+ status->flag & RX_FLAG_VHT ? "vht" : "",
+ status->flag & RX_FLAG_40MHZ ? "40" : "",
+ status->flag & RX_FLAG_80MHZ ? "80" : "",
+ status->flag & RX_FLAG_SHORT_GI ? "sgi " : "",
+ status->rate_idx,
+ status->vht_nss,
+ status->freq,
+ status->band);
+
+ ieee80211_rx(ar->hw, info->skb);
+}
+
+struct ath10k_peer *ath10k_peer_find(struct ath10k *ar, int vdev_id,
+ const u8 *addr)
+{
+ struct ath10k_peer *peer;
+
+ lockdep_assert_held(&ar->data_lock);
+
+ list_for_each_entry(peer, &ar->peers, list) {
+ if (peer->vdev_id != vdev_id)
+ continue;
+ if (memcmp(peer->addr, addr, ETH_ALEN))
+ continue;
+
+ return peer;
+ }
+
+ return NULL;
+}
+
+static struct ath10k_peer *ath10k_peer_find_by_id(struct ath10k *ar,
+ int peer_id)
+{
+ struct ath10k_peer *peer;
+
+ lockdep_assert_held(&ar->data_lock);
+
+ list_for_each_entry(peer, &ar->peers, list)
+ if (test_bit(peer_id, peer->peer_ids))
+ return peer;
+
+ return NULL;
+}
+
+static int ath10k_wait_for_peer_common(struct ath10k *ar, int vdev_id,
+ const u8 *addr, bool expect_mapped)
+{
+ int ret;
+
+ ret = wait_event_timeout(ar->peer_mapping_wq, ({
+ bool mapped;
+
+ spin_lock_bh(&ar->data_lock);
+ mapped = !!ath10k_peer_find(ar, vdev_id, addr);
+ spin_unlock_bh(&ar->data_lock);
+
+ mapped == expect_mapped;
+ }), 3*HZ);
+
+ if (ret <= 0)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+int ath10k_wait_for_peer_created(struct ath10k *ar, int vdev_id, const u8 *addr)
+{
+ return ath10k_wait_for_peer_common(ar, vdev_id, addr, true);
+}
+
+int ath10k_wait_for_peer_deleted(struct ath10k *ar, int vdev_id, const u8 *addr)
+{
+ return ath10k_wait_for_peer_common(ar, vdev_id, addr, false);
+}
+
+void ath10k_peer_map_event(struct ath10k_htt *htt,
+ struct htt_peer_map_event *ev)
+{
+ struct ath10k *ar = htt->ar;
+ struct ath10k_peer *peer;
+
+ spin_lock_bh(&ar->data_lock);
+ peer = ath10k_peer_find(ar, ev->vdev_id, ev->addr);
+ if (!peer) {
+ peer = kzalloc(sizeof(*peer), GFP_ATOMIC);
+ if (!peer)
+ goto exit;
+
+ peer->vdev_id = ev->vdev_id;
+ memcpy(peer->addr, ev->addr, ETH_ALEN);
+ list_add(&peer->list, &ar->peers);
+ wake_up(&ar->peer_mapping_wq);
+ }
+
+ ath10k_dbg(ATH10K_DBG_HTT, "htt peer map vdev %d peer %pM id %d\n",
+ ev->vdev_id, ev->addr, ev->peer_id);
+
+ set_bit(ev->peer_id, peer->peer_ids);
+exit:
+ spin_unlock_bh(&ar->data_lock);
+}
+
+void ath10k_peer_unmap_event(struct ath10k_htt *htt,
+ struct htt_peer_unmap_event *ev)
+{
+ struct ath10k *ar = htt->ar;
+ struct ath10k_peer *peer;
+
+ spin_lock_bh(&ar->data_lock);
+ peer = ath10k_peer_find_by_id(ar, ev->peer_id);
+ if (!peer) {
+ ath10k_warn("unknown peer id %d\n", ev->peer_id);
+ goto exit;
+ }
+
+ ath10k_dbg(ATH10K_DBG_HTT, "htt peer unmap vdev %d peer %pM id %d\n",
+ peer->vdev_id, peer->addr, ev->peer_id);
+
+ clear_bit(ev->peer_id, peer->peer_ids);
+
+ if (bitmap_empty(peer->peer_ids, ATH10K_MAX_NUM_PEER_IDS)) {
+ list_del(&peer->list);
+ kfree(peer);
+ wake_up(&ar->peer_mapping_wq);
+ }
+
+exit:
+ spin_unlock_bh(&ar->data_lock);
+}
^ permalink raw reply related
* [PATCH 03/29] ath10k: add ce.c
From: Kalle Valo @ 2013-05-15 14:43 UTC (permalink / raw)
To: linux-wireless; +Cc: ath10k-devel
In-Reply-To: <20130515143356.4492.43033.stgit@localhost6.localdomain6>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/ce.c | 1189 ++++++++++++++++++++++++++++++++++
1 file changed, 1189 insertions(+)
create mode 100644 drivers/net/wireless/ath/ath10k/ce.c
diff --git a/drivers/net/wireless/ath/ath10k/ce.c b/drivers/net/wireless/ath/ath10k/ce.c
new file mode 100644
index 0000000..61a8ac7
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/ce.c
@@ -0,0 +1,1189 @@
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "hif.h"
+#include "pci.h"
+#include "ce.h"
+#include "debug.h"
+
+/*
+ * Support for Copy Engine hardware, which is mainly used for
+ * communication between Host and Target over a PCIe interconnect.
+ */
+
+/*
+ * A single CopyEngine (CE) comprises two "rings":
+ * a source ring
+ * a destination ring
+ *
+ * Each ring consists of a number of descriptors which specify
+ * an address, length, and meta-data.
+ *
+ * Typically, one side of the PCIe interconnect (Host or Target)
+ * controls one ring and the other side controls the other ring.
+ * The source side chooses when to initiate a transfer and it
+ * chooses what to send (buffer address, length). The destination
+ * side keeps a supply of "anonymous receive buffers" available and
+ * it handles incoming data as it arrives (when the destination
+ * recieves an interrupt).
+ *
+ * The sender may send a simple buffer (address/length) or it may
+ * send a small list of buffers. When a small list is sent, hardware
+ * "gathers" these and they end up in a single destination buffer
+ * with a single interrupt.
+ *
+ * There are several "contexts" managed by this layer -- more, it
+ * may seem -- than should be needed. These are provided mainly for
+ * maximum flexibility and especially to facilitate a simpler HIF
+ * implementation. There are per-CopyEngine recv, send, and watermark
+ * contexts. These are supplied by the caller when a recv, send,
+ * or watermark handler is established and they are echoed back to
+ * the caller when the respective callbacks are invoked. There is
+ * also a per-transfer context supplied by the caller when a buffer
+ * (or sendlist) is sent and when a buffer is enqueued for recv.
+ * These per-transfer contexts are echoed back to the caller when
+ * the buffer is sent/received.
+ */
+
+static inline void ath10k_ce_dest_ring_write_index_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ ath10k_pci_write32(ar, ce_ctrl_addr + DST_WR_INDEX_ADDRESS, n);
+}
+
+static inline u32 ath10k_ce_dest_ring_write_index_get(struct ath10k *ar,
+ u32 ce_ctrl_addr)
+{
+ return ath10k_pci_read32(ar, ce_ctrl_addr + DST_WR_INDEX_ADDRESS);
+}
+
+static inline void ath10k_ce_src_ring_write_index_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ void __iomem *indicator_addr;
+
+ if (!test_bit(ATH10K_PCI_FEATURE_HW_1_0_WARKAROUND, ar_pci->features)) {
+ ath10k_pci_write32(ar, ce_ctrl_addr + SR_WR_INDEX_ADDRESS, n);
+ return;
+ }
+
+ /* workaround for QCA988x_1.0 HW CE */
+ indicator_addr = ar_pci->mem + ce_ctrl_addr + DST_WATERMARK_ADDRESS;
+
+ if (ce_ctrl_addr == ath10k_ce_base_address(CDC_WAR_DATA_CE)) {
+ iowrite32((CDC_WAR_MAGIC_STR | n), indicator_addr);
+ } else {
+ unsigned long irq_flags;
+ local_irq_save(irq_flags);
+ iowrite32(1, indicator_addr);
+
+ /*
+ * PCIE write waits for ACK in IPQ8K, there is no
+ * need to read back value.
+ */
+ (void)ioread32(indicator_addr);
+ (void)ioread32(indicator_addr); /* conservative */
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + SR_WR_INDEX_ADDRESS, n);
+
+ iowrite32(0, indicator_addr);
+ local_irq_restore(irq_flags);
+ }
+}
+
+static inline u32 ath10k_ce_src_ring_write_index_get(struct ath10k *ar,
+ u32 ce_ctrl_addr)
+{
+ return ath10k_pci_read32(ar, ce_ctrl_addr + SR_WR_INDEX_ADDRESS);
+}
+
+static inline u32 ath10k_ce_src_ring_read_index_get(struct ath10k *ar,
+ u32 ce_ctrl_addr)
+{
+ return ath10k_pci_read32(ar, ce_ctrl_addr + CURRENT_SRRI_ADDRESS);
+}
+
+static inline void ath10k_ce_src_ring_base_addr_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int addr)
+{
+ ath10k_pci_write32(ar, ce_ctrl_addr + SR_BA_ADDRESS, addr);
+}
+
+static inline void ath10k_ce_src_ring_size_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ ath10k_pci_write32(ar, ce_ctrl_addr + SR_SIZE_ADDRESS, n);
+}
+
+static inline void ath10k_ce_src_ring_dmax_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ u32 ctrl1_addr = ath10k_pci_read32((ar),
+ (ce_ctrl_addr) + CE_CTRL1_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS,
+ (ctrl1_addr & ~CE_CTRL1_DMAX_LENGTH_MASK) |
+ CE_CTRL1_DMAX_LENGTH_SET(n));
+}
+
+static inline void ath10k_ce_src_ring_byte_swap_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ u32 ctrl1_addr = ath10k_pci_read32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS,
+ (ctrl1_addr & ~CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MASK) |
+ CE_CTRL1_SRC_RING_BYTE_SWAP_EN_SET(n));
+}
+
+static inline void ath10k_ce_dest_ring_byte_swap_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ u32 ctrl1_addr = ath10k_pci_read32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS,
+ (ctrl1_addr & ~CE_CTRL1_DST_RING_BYTE_SWAP_EN_MASK) |
+ CE_CTRL1_DST_RING_BYTE_SWAP_EN_SET(n));
+}
+
+static inline u32 ath10k_ce_dest_ring_read_index_get(struct ath10k *ar,
+ u32 ce_ctrl_addr)
+{
+ return ath10k_pci_read32(ar, ce_ctrl_addr + CURRENT_DRRI_ADDRESS);
+}
+
+static inline void ath10k_ce_dest_ring_base_addr_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ u32 addr)
+{
+ ath10k_pci_write32(ar, ce_ctrl_addr + DR_BA_ADDRESS, addr);
+}
+
+static inline void ath10k_ce_dest_ring_size_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ ath10k_pci_write32(ar, ce_ctrl_addr + DR_SIZE_ADDRESS, n);
+}
+
+static inline void ath10k_ce_src_ring_highmark_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ u32 addr = ath10k_pci_read32(ar, ce_ctrl_addr + SRC_WATERMARK_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + SRC_WATERMARK_ADDRESS,
+ (addr & ~SRC_WATERMARK_HIGH_MASK) |
+ SRC_WATERMARK_HIGH_SET(n));
+}
+
+static inline void ath10k_ce_src_ring_lowmark_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ u32 addr = ath10k_pci_read32(ar, ce_ctrl_addr + SRC_WATERMARK_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + SRC_WATERMARK_ADDRESS,
+ (addr & ~SRC_WATERMARK_LOW_MASK) |
+ SRC_WATERMARK_LOW_SET(n));
+}
+
+static inline void ath10k_ce_dest_ring_highmark_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ u32 addr = ath10k_pci_read32(ar, ce_ctrl_addr + DST_WATERMARK_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + DST_WATERMARK_ADDRESS,
+ (addr & ~DST_WATERMARK_HIGH_MASK) |
+ DST_WATERMARK_HIGH_SET(n));
+}
+
+static inline void ath10k_ce_dest_ring_lowmark_set(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int n)
+{
+ u32 addr = ath10k_pci_read32(ar, ce_ctrl_addr + DST_WATERMARK_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + DST_WATERMARK_ADDRESS,
+ (addr & ~DST_WATERMARK_LOW_MASK) |
+ DST_WATERMARK_LOW_SET(n));
+}
+
+static inline void ath10k_ce_copy_complete_inter_enable(struct ath10k *ar,
+ u32 ce_ctrl_addr)
+{
+ u32 host_ie_addr = ath10k_pci_read32(ar,
+ ce_ctrl_addr + HOST_IE_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + HOST_IE_ADDRESS,
+ host_ie_addr | HOST_IE_COPY_COMPLETE_MASK);
+}
+
+static inline void ath10k_ce_copy_complete_intr_disable(struct ath10k *ar,
+ u32 ce_ctrl_addr)
+{
+ u32 host_ie_addr = ath10k_pci_read32(ar,
+ ce_ctrl_addr + HOST_IE_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + HOST_IE_ADDRESS,
+ host_ie_addr & ~HOST_IE_COPY_COMPLETE_MASK);
+}
+
+static inline void ath10k_ce_watermark_intr_disable(struct ath10k *ar,
+ u32 ce_ctrl_addr)
+{
+ u32 host_ie_addr = ath10k_pci_read32(ar,
+ ce_ctrl_addr + HOST_IE_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + HOST_IE_ADDRESS,
+ host_ie_addr & ~CE_WATERMARK_MASK);
+}
+
+static inline void ath10k_ce_error_intr_enable(struct ath10k *ar,
+ u32 ce_ctrl_addr)
+{
+ u32 misc_ie_addr = ath10k_pci_read32(ar,
+ ce_ctrl_addr + MISC_IE_ADDRESS);
+
+ ath10k_pci_write32(ar, ce_ctrl_addr + MISC_IE_ADDRESS,
+ misc_ie_addr | CE_ERROR_MASK);
+}
+
+static inline void ath10k_ce_engine_int_status_clear(struct ath10k *ar,
+ u32 ce_ctrl_addr,
+ unsigned int mask)
+{
+ ath10k_pci_write32(ar, ce_ctrl_addr + HOST_IS_ADDRESS, mask);
+}
+
+
+/*
+ * Guts of ath10k_ce_send, used by both ath10k_ce_send and
+ * ath10k_ce_sendlist_send.
+ * The caller takes responsibility for any needed locking.
+ */
+static int ath10k_ce_send_nolock(struct ce_state *ce_state,
+ void *per_transfer_context,
+ u32 buffer,
+ unsigned int nbytes,
+ unsigned int transfer_id,
+ unsigned int flags)
+{
+ struct ath10k *ar = ce_state->ar;
+ struct ce_ring_state *src_ring = ce_state->src_ring;
+ struct ce_desc *desc, *sdesc;
+ unsigned int nentries_mask = src_ring->nentries_mask;
+ unsigned int sw_index = src_ring->sw_index;
+ unsigned int write_index = src_ring->write_index;
+ u32 ctrl_addr = ce_state->ctrl_addr;
+ u32 desc_flags = 0;
+ int ret = 0;
+
+ if (nbytes > ce_state->src_sz_max)
+ ath10k_warn("%s: send more we can (nbytes: %d, max: %d)\n",
+ __func__, nbytes, ce_state->src_sz_max);
+
+ ath10k_pci_wake(ar);
+
+ if (unlikely(CE_RING_DELTA(nentries_mask,
+ write_index, sw_index - 1) <= 0)) {
+ ret = -EIO;
+ goto exit;
+ }
+
+ desc = CE_SRC_RING_TO_DESC(src_ring->base_addr_owner_space,
+ write_index);
+ sdesc = CE_SRC_RING_TO_DESC(src_ring->shadow_base, write_index);
+
+ desc_flags |= SM(transfer_id, CE_DESC_FLAGS_META_DATA);
+
+ if (flags & CE_SEND_FLAG_GATHER)
+ desc_flags |= CE_DESC_FLAGS_GATHER;
+ if (flags & CE_SEND_FLAG_BYTE_SWAP)
+ desc_flags |= CE_DESC_FLAGS_BYTE_SWAP;
+
+ sdesc->addr = __cpu_to_le32(buffer);
+ sdesc->nbytes = __cpu_to_le16(nbytes);
+ sdesc->flags = __cpu_to_le16(desc_flags);
+
+ *desc = *sdesc;
+
+ src_ring->per_transfer_context[write_index] = per_transfer_context;
+
+ /* Update Source Ring Write Index */
+ write_index = CE_RING_IDX_INCR(nentries_mask, write_index);
+
+ /* WORKAROUND */
+ if (!(flags & CE_SEND_FLAG_GATHER))
+ ath10k_ce_src_ring_write_index_set(ar, ctrl_addr, write_index);
+
+ src_ring->write_index = write_index;
+exit:
+ ath10k_pci_sleep(ar);
+ return ret;
+}
+
+int ath10k_ce_send(struct ce_state *ce_state,
+ void *per_transfer_context,
+ u32 buffer,
+ unsigned int nbytes,
+ unsigned int transfer_id,
+ unsigned int flags)
+{
+ struct ath10k *ar = ce_state->ar;
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ int ret;
+
+ spin_lock_bh(&ar_pci->ce_lock);
+ ret = ath10k_ce_send_nolock(ce_state, per_transfer_context,
+ buffer, nbytes, transfer_id, flags);
+ spin_unlock_bh(&ar_pci->ce_lock);
+
+ return ret;
+}
+
+void ath10k_ce_sendlist_buf_add(struct ce_sendlist *sendlist, u32 buffer,
+ unsigned int nbytes, u32 flags)
+{
+ unsigned int num_items = sendlist->num_items;
+ struct ce_sendlist_item *item;
+
+ item = &sendlist->item[num_items];
+ item->data = buffer;
+ item->u.nbytes = nbytes;
+ item->flags = flags;
+ sendlist->num_items++;
+}
+
+int ath10k_ce_sendlist_send(struct ce_state *ce_state,
+ void *per_transfer_context,
+ struct ce_sendlist *sendlist,
+ unsigned int transfer_id)
+{
+ struct ce_ring_state *src_ring = ce_state->src_ring;
+ struct ce_sendlist_item *item;
+ struct ath10k *ar = ce_state->ar;
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ unsigned int nentries_mask = src_ring->nentries_mask;
+ unsigned int num_items = sendlist->num_items;
+ unsigned int sw_index;
+ unsigned int write_index;
+ int i, delta, ret = -ENOMEM;
+
+ spin_lock_bh(&ar_pci->ce_lock);
+
+ sw_index = src_ring->sw_index;
+ write_index = src_ring->write_index;
+
+ delta = CE_RING_DELTA(nentries_mask, write_index, sw_index - 1);
+
+ if (delta >= num_items) {
+ /*
+ * Handle all but the last item uniformly.
+ */
+ for (i = 0; i < num_items - 1; i++) {
+ item = &sendlist->item[i];
+ ret = ath10k_ce_send_nolock(ce_state,
+ CE_SENDLIST_ITEM_CTXT,
+ (u32) item->data,
+ item->u.nbytes, transfer_id,
+ item->flags |
+ CE_SEND_FLAG_GATHER);
+ if (ret)
+ ath10k_warn("CE send failed for item: %d\n", i);
+ }
+ /*
+ * Provide valid context pointer for final item.
+ */
+ item = &sendlist->item[i];
+ ret = ath10k_ce_send_nolock(ce_state, per_transfer_context,
+ (u32) item->data, item->u.nbytes,
+ transfer_id, item->flags);
+ if (ret)
+ ath10k_warn("CE send failed for last item: %d\n", i);
+ }
+
+ spin_unlock_bh(&ar_pci->ce_lock);
+
+ return ret;
+}
+
+int ath10k_ce_recv_buf_enqueue(struct ce_state *ce_state,
+ void *per_recv_context,
+ u32 buffer)
+{
+ struct ce_ring_state *dest_ring = ce_state->dest_ring;
+ u32 ctrl_addr = ce_state->ctrl_addr;
+ struct ath10k *ar = ce_state->ar;
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ unsigned int nentries_mask = dest_ring->nentries_mask;
+ unsigned int write_index;
+ unsigned int sw_index;
+ int ret;
+
+ spin_lock_bh(&ar_pci->ce_lock);
+ write_index = dest_ring->write_index;
+ sw_index = dest_ring->sw_index;
+
+ ath10k_pci_wake(ar);
+
+ if (CE_RING_DELTA(nentries_mask, write_index, sw_index - 1) > 0) {
+ struct ce_desc *base = dest_ring->base_addr_owner_space;
+ struct ce_desc *desc = CE_DEST_RING_TO_DESC(base, write_index);
+
+ /* Update destination descriptor */
+ desc->addr = __cpu_to_le32(buffer);
+ desc->nbytes = 0;
+
+ dest_ring->per_transfer_context[write_index] =
+ per_recv_context;
+
+ /* Update Destination Ring Write Index */
+ write_index = CE_RING_IDX_INCR(nentries_mask, write_index);
+ ath10k_ce_dest_ring_write_index_set(ar, ctrl_addr, write_index);
+ dest_ring->write_index = write_index;
+ ret = 0;
+ } else {
+ ret = -EIO;
+ }
+ ath10k_pci_sleep(ar);
+ spin_unlock_bh(&ar_pci->ce_lock);
+
+ return ret;
+}
+
+/*
+ * Guts of ath10k_ce_completed_recv_next.
+ * The caller takes responsibility for any necessary locking.
+ */
+static int ath10k_ce_completed_recv_next_nolock(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp,
+ unsigned int *nbytesp,
+ unsigned int *transfer_idp,
+ unsigned int *flagsp)
+{
+ struct ce_ring_state *dest_ring = ce_state->dest_ring;
+ unsigned int nentries_mask = dest_ring->nentries_mask;
+ unsigned int sw_index = dest_ring->sw_index;
+
+ struct ce_desc *base = dest_ring->base_addr_owner_space;
+ struct ce_desc *desc = CE_DEST_RING_TO_DESC(base, sw_index);
+ struct ce_desc sdesc;
+ u16 nbytes;
+
+ /* Copy in one go for performance reasons */
+ sdesc = *desc;
+
+ nbytes = __le16_to_cpu(sdesc.nbytes);
+ if (nbytes == 0) {
+ /*
+ * This closes a relatively unusual race where the Host
+ * sees the updated DRRI before the update to the
+ * corresponding descriptor has completed. We treat this
+ * as a descriptor that is not yet done.
+ */
+ return -EIO;
+ }
+
+ desc->nbytes = 0;
+
+ /* Return data from completed destination descriptor */
+ *bufferp = __le32_to_cpu(sdesc.addr);
+ *nbytesp = nbytes;
+ *transfer_idp = MS(__le16_to_cpu(sdesc.flags), CE_DESC_FLAGS_META_DATA);
+
+ if (__le16_to_cpu(sdesc.flags) & CE_DESC_FLAGS_BYTE_SWAP)
+ *flagsp = CE_RECV_FLAG_SWAPPED;
+ else
+ *flagsp = 0;
+
+ if (per_transfer_contextp)
+ *per_transfer_contextp =
+ dest_ring->per_transfer_context[sw_index];
+
+ /* sanity */
+ dest_ring->per_transfer_context[sw_index] = NULL;
+
+ /* Update sw_index */
+ sw_index = CE_RING_IDX_INCR(nentries_mask, sw_index);
+ dest_ring->sw_index = sw_index;
+
+ return 0;
+}
+
+int ath10k_ce_completed_recv_next(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp,
+ unsigned int *nbytesp,
+ unsigned int *transfer_idp,
+ unsigned int *flagsp)
+{
+ struct ath10k *ar = ce_state->ar;
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ int ret;
+
+ spin_lock_bh(&ar_pci->ce_lock);
+ ret = ath10k_ce_completed_recv_next_nolock(ce_state,
+ per_transfer_contextp,
+ bufferp, nbytesp,
+ transfer_idp, flagsp);
+ spin_unlock_bh(&ar_pci->ce_lock);
+
+ return ret;
+}
+
+int ath10k_ce_revoke_recv_next(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp)
+{
+ struct ce_ring_state *dest_ring;
+ unsigned int nentries_mask;
+ unsigned int sw_index;
+ unsigned int write_index;
+ int ret;
+ struct ath10k *ar;
+ struct ath10k_pci *ar_pci;
+
+ dest_ring = ce_state->dest_ring;
+
+ if (!dest_ring)
+ return -EIO;
+
+ ar = ce_state->ar;
+ ar_pci = ath10k_pci_priv(ar);
+
+ spin_lock_bh(&ar_pci->ce_lock);
+
+ nentries_mask = dest_ring->nentries_mask;
+ sw_index = dest_ring->sw_index;
+ write_index = dest_ring->write_index;
+ if (write_index != sw_index) {
+ struct ce_desc *base = dest_ring->base_addr_owner_space;
+ struct ce_desc *desc = CE_DEST_RING_TO_DESC(base, sw_index);
+
+ /* Return data from completed destination descriptor */
+ *bufferp = __le32_to_cpu(desc->addr);
+
+ if (per_transfer_contextp)
+ *per_transfer_contextp =
+ dest_ring->per_transfer_context[sw_index];
+
+ /* sanity */
+ dest_ring->per_transfer_context[sw_index] = NULL;
+
+ /* Update sw_index */
+ sw_index = CE_RING_IDX_INCR(nentries_mask, sw_index);
+ dest_ring->sw_index = sw_index;
+ ret = 0;
+ } else {
+ ret = -EIO;
+ }
+
+ spin_unlock_bh(&ar_pci->ce_lock);
+
+ return ret;
+}
+
+/*
+ * Guts of ath10k_ce_completed_send_next.
+ * The caller takes responsibility for any necessary locking.
+ */
+static int ath10k_ce_completed_send_next_nolock(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp,
+ unsigned int *nbytesp,
+ unsigned int *transfer_idp)
+{
+ struct ce_ring_state *src_ring = ce_state->src_ring;
+ u32 ctrl_addr = ce_state->ctrl_addr;
+ struct ath10k *ar = ce_state->ar;
+ unsigned int nentries_mask = src_ring->nentries_mask;
+ unsigned int sw_index = src_ring->sw_index;
+ unsigned int read_index;
+ int ret = -EIO;
+
+ if (src_ring->hw_index == sw_index) {
+ /*
+ * The SW completion index has caught up with the cached
+ * version of the HW completion index.
+ * Update the cached HW completion index to see whether
+ * the SW has really caught up to the HW, or if the cached
+ * value of the HW index has become stale.
+ */
+ ath10k_pci_wake(ar);
+ src_ring->hw_index =
+ ath10k_ce_src_ring_read_index_get(ar, ctrl_addr);
+ ath10k_pci_sleep(ar);
+ }
+ read_index = src_ring->hw_index;
+
+ if ((read_index != sw_index) && (read_index != 0xffffffff)) {
+ struct ce_desc *sbase = src_ring->shadow_base;
+ struct ce_desc *sdesc = CE_SRC_RING_TO_DESC(sbase, sw_index);
+
+ /* Return data from completed source descriptor */
+ *bufferp = __le32_to_cpu(sdesc->addr);
+ *nbytesp = __le16_to_cpu(sdesc->nbytes);
+ *transfer_idp = MS(__le16_to_cpu(sdesc->flags),
+ CE_DESC_FLAGS_META_DATA);
+
+ if (per_transfer_contextp)
+ *per_transfer_contextp =
+ src_ring->per_transfer_context[sw_index];
+
+ /* sanity */
+ src_ring->per_transfer_context[sw_index] = NULL;
+
+ /* Update sw_index */
+ sw_index = CE_RING_IDX_INCR(nentries_mask, sw_index);
+ src_ring->sw_index = sw_index;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/* NB: Modeled after ath10k_ce_completed_send_next */
+int ath10k_ce_cancel_send_next(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp,
+ unsigned int *nbytesp,
+ unsigned int *transfer_idp)
+{
+ struct ce_ring_state *src_ring;
+ unsigned int nentries_mask;
+ unsigned int sw_index;
+ unsigned int write_index;
+ int ret;
+ struct ath10k *ar;
+ struct ath10k_pci *ar_pci;
+
+ src_ring = ce_state->src_ring;
+
+ if (!src_ring)
+ return -EIO;
+
+ ar = ce_state->ar;
+ ar_pci = ath10k_pci_priv(ar);
+
+ spin_lock_bh(&ar_pci->ce_lock);
+
+ nentries_mask = src_ring->nentries_mask;
+ sw_index = src_ring->sw_index;
+ write_index = src_ring->write_index;
+
+ if (write_index != sw_index) {
+ struct ce_desc *base = src_ring->base_addr_owner_space;
+ struct ce_desc *desc = CE_SRC_RING_TO_DESC(base, sw_index);
+
+ /* Return data from completed source descriptor */
+ *bufferp = __le32_to_cpu(desc->addr);
+ *nbytesp = __le16_to_cpu(desc->nbytes);
+ *transfer_idp = MS(__le16_to_cpu(desc->flags),
+ CE_DESC_FLAGS_META_DATA);
+
+ if (per_transfer_contextp)
+ *per_transfer_contextp =
+ src_ring->per_transfer_context[sw_index];
+
+ /* sanity */
+ src_ring->per_transfer_context[sw_index] = NULL;
+
+ /* Update sw_index */
+ sw_index = CE_RING_IDX_INCR(nentries_mask, sw_index);
+ src_ring->sw_index = sw_index;
+ ret = 0;
+ } else {
+ ret = -EIO;
+ }
+
+ spin_unlock_bh(&ar_pci->ce_lock);
+
+ return ret;
+}
+
+int ath10k_ce_completed_send_next(struct ce_state *ce_state,
+ void **per_transfer_contextp,
+ u32 *bufferp,
+ unsigned int *nbytesp,
+ unsigned int *transfer_idp)
+{
+ struct ath10k *ar = ce_state->ar;
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ int ret;
+
+ spin_lock_bh(&ar_pci->ce_lock);
+ ret = ath10k_ce_completed_send_next_nolock(ce_state,
+ per_transfer_contextp,
+ bufferp, nbytesp,
+ transfer_idp);
+ spin_unlock_bh(&ar_pci->ce_lock);
+
+ return ret;
+}
+
+/*
+ * Guts of interrupt handler for per-engine interrupts on a particular CE.
+ *
+ * Invokes registered callbacks for recv_complete,
+ * send_complete, and watermarks.
+ */
+void ath10k_ce_per_engine_service(struct ath10k *ar, unsigned int ce_id)
+{
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ struct ce_state *ce_state = ar_pci->ce_id_to_state[ce_id];
+ u32 ctrl_addr = ce_state->ctrl_addr;
+ void *transfer_context;
+ u32 buf;
+ unsigned int nbytes;
+ unsigned int id;
+ unsigned int flags;
+
+ ath10k_pci_wake(ar);
+ spin_lock_bh(&ar_pci->ce_lock);
+
+ /* Clear the copy-complete interrupts that will be handled here. */
+ ath10k_ce_engine_int_status_clear(ar, ctrl_addr,
+ HOST_IS_COPY_COMPLETE_MASK);
+
+ if (ce_state->recv_cb) {
+ /*
+ * Pop completed recv buffers and call the registered
+ * recv callback for each
+ */
+ while (ath10k_ce_completed_recv_next_nolock(ce_state,
+ &transfer_context,
+ &buf, &nbytes,
+ &id, &flags) == 0) {
+ spin_unlock_bh(&ar_pci->ce_lock);
+ ce_state->recv_cb(ce_state, transfer_context, buf,
+ nbytes, id, flags);
+ spin_lock_bh(&ar_pci->ce_lock);
+ }
+ }
+
+ if (ce_state->send_cb) {
+ /*
+ * Pop completed send buffers and call the registered
+ * send callback for each
+ */
+ while (ath10k_ce_completed_send_next_nolock(ce_state,
+ &transfer_context,
+ &buf,
+ &nbytes,
+ &id) == 0) {
+ spin_unlock_bh(&ar_pci->ce_lock);
+ ce_state->send_cb(ce_state, transfer_context,
+ buf, nbytes, id);
+ spin_lock_bh(&ar_pci->ce_lock);
+ }
+ }
+
+ /*
+ * Misc CE interrupts are not being handled, but still need
+ * to be cleared.
+ */
+ ath10k_ce_engine_int_status_clear(ar, ctrl_addr, CE_WATERMARK_MASK);
+
+ spin_unlock_bh(&ar_pci->ce_lock);
+ ath10k_pci_sleep(ar);
+}
+
+/*
+ * Handler for per-engine interrupts on ALL active CEs.
+ * This is used in cases where the system is sharing a
+ * single interrput for all CEs
+ */
+
+void ath10k_ce_per_engine_service_any(struct ath10k *ar)
+{
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ int ce_id;
+ u32 intr_summary;
+
+ ath10k_pci_wake(ar);
+ intr_summary = CE_INTERRUPT_SUMMARY(ar);
+
+ for (ce_id = 0; intr_summary && (ce_id < ar_pci->ce_count); ce_id++) {
+ if (intr_summary & (1 << ce_id))
+ intr_summary &= ~(1 << ce_id);
+ else
+ /* no intr pending on this CE */
+ continue;
+
+ ath10k_ce_per_engine_service(ar, ce_id);
+ }
+
+ ath10k_pci_sleep(ar);
+}
+
+/*
+ * Adjust interrupts for the copy complete handler.
+ * If it's needed for either send or recv, then unmask
+ * this interrupt; otherwise, mask it.
+ *
+ * Called with ce_lock held.
+ */
+static void ath10k_ce_per_engine_handler_adjust(struct ce_state *ce_state,
+ int disable_copy_compl_intr)
+{
+ u32 ctrl_addr = ce_state->ctrl_addr;
+ struct ath10k *ar = ce_state->ar;
+
+ ath10k_pci_wake(ar);
+
+ if ((!disable_copy_compl_intr) &&
+ (ce_state->send_cb || ce_state->recv_cb))
+ ath10k_ce_copy_complete_inter_enable(ar, ctrl_addr);
+ else
+ ath10k_ce_copy_complete_intr_disable(ar, ctrl_addr);
+
+ ath10k_ce_watermark_intr_disable(ar, ctrl_addr);
+
+ ath10k_pci_sleep(ar);
+}
+
+void ath10k_ce_disable_interrupts(struct ath10k *ar)
+{
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ int ce_id;
+
+ ath10k_pci_wake(ar);
+ for (ce_id = 0; ce_id < ar_pci->ce_count; ce_id++) {
+ struct ce_state *ce_state = ar_pci->ce_id_to_state[ce_id];
+ u32 ctrl_addr = ce_state->ctrl_addr;
+
+ ath10k_ce_copy_complete_intr_disable(ar, ctrl_addr);
+ }
+ ath10k_pci_sleep(ar);
+}
+
+void ath10k_ce_send_cb_register(struct ce_state *ce_state,
+ void (*send_cb) (struct ce_state *ce_state,
+ void *transfer_context,
+ u32 buffer,
+ unsigned int nbytes,
+ unsigned int transfer_id),
+ int disable_interrupts)
+{
+ struct ath10k *ar = ce_state->ar;
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+ spin_lock_bh(&ar_pci->ce_lock);
+ ce_state->send_cb = send_cb;
+ ath10k_ce_per_engine_handler_adjust(ce_state, disable_interrupts);
+ spin_unlock_bh(&ar_pci->ce_lock);
+}
+
+void ath10k_ce_recv_cb_register(struct ce_state *ce_state,
+ void (*recv_cb) (struct ce_state *ce_state,
+ void *transfer_context,
+ u32 buffer,
+ unsigned int nbytes,
+ unsigned int transfer_id,
+ unsigned int flags))
+{
+ struct ath10k *ar = ce_state->ar;
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+ spin_lock_bh(&ar_pci->ce_lock);
+ ce_state->recv_cb = recv_cb;
+ ath10k_ce_per_engine_handler_adjust(ce_state, 0);
+ spin_unlock_bh(&ar_pci->ce_lock);
+}
+
+static int ath10k_ce_init_src_ring(struct ath10k *ar,
+ unsigned int ce_id,
+ struct ce_state *ce_state,
+ const struct ce_attr *attr)
+{
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ struct ce_ring_state *src_ring;
+ unsigned int nentries = attr->src_nentries;
+ unsigned int ce_nbytes;
+ u32 ctrl_addr = ath10k_ce_base_address(ce_id);
+ dma_addr_t base_addr;
+ char *ptr;
+
+ nentries = roundup_pow_of_two(nentries);
+
+ if (ce_state->src_ring) {
+ WARN_ON(ce_state->src_ring->nentries != nentries);
+ return 0;
+ }
+
+ ce_nbytes = sizeof(struct ce_ring_state) + (nentries * sizeof(void *));
+ ptr = kzalloc(ce_nbytes, GFP_KERNEL);
+ if (ptr == NULL)
+ return -ENOMEM;
+
+ ce_state->src_ring = (struct ce_ring_state *)ptr;
+ src_ring = ce_state->src_ring;
+
+ ptr += sizeof(struct ce_ring_state);
+ src_ring->nentries = nentries;
+ src_ring->nentries_mask = nentries - 1;
+
+ ath10k_pci_wake(ar);
+ src_ring->sw_index = ath10k_ce_src_ring_read_index_get(ar, ctrl_addr);
+ src_ring->hw_index = src_ring->sw_index;
+
+ src_ring->write_index =
+ ath10k_ce_src_ring_write_index_get(ar, ctrl_addr);
+ ath10k_pci_sleep(ar);
+
+ src_ring->per_transfer_context = (void **)ptr;
+
+ /*
+ * Legacy platforms that do not support cache
+ * coherent DMA are unsupported
+ */
+ src_ring->base_addr_owner_space_unaligned =
+ pci_alloc_consistent(ar_pci->pdev,
+ (nentries * sizeof(struct ce_desc) +
+ CE_DESC_RING_ALIGN),
+ &base_addr);
+ src_ring->base_addr_ce_space_unaligned = base_addr;
+
+ src_ring->base_addr_owner_space = PTR_ALIGN(
+ src_ring->base_addr_owner_space_unaligned,
+ CE_DESC_RING_ALIGN);
+ src_ring->base_addr_ce_space = ALIGN(
+ src_ring->base_addr_ce_space_unaligned,
+ CE_DESC_RING_ALIGN);
+
+ /*
+ * Also allocate a shadow src ring in regular
+ * mem to use for faster access.
+ */
+ src_ring->shadow_base_unaligned =
+ kmalloc((nentries * sizeof(struct ce_desc) +
+ CE_DESC_RING_ALIGN), GFP_KERNEL);
+
+ src_ring->shadow_base = PTR_ALIGN(
+ src_ring->shadow_base_unaligned,
+ CE_DESC_RING_ALIGN);
+
+ ath10k_pci_wake(ar);
+ ath10k_ce_src_ring_base_addr_set(ar, ctrl_addr,
+ src_ring->base_addr_ce_space);
+ ath10k_ce_src_ring_size_set(ar, ctrl_addr, nentries);
+ ath10k_ce_src_ring_dmax_set(ar, ctrl_addr, attr->src_sz_max);
+ ath10k_ce_src_ring_byte_swap_set(ar, ctrl_addr, 0);
+ ath10k_ce_src_ring_lowmark_set(ar, ctrl_addr, 0);
+ ath10k_ce_src_ring_highmark_set(ar, ctrl_addr, nentries);
+ ath10k_pci_sleep(ar);
+
+ return 0;
+}
+
+static int ath10k_ce_init_dest_ring(struct ath10k *ar,
+ unsigned int ce_id,
+ struct ce_state *ce_state,
+ const struct ce_attr *attr)
+{
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ struct ce_ring_state *dest_ring;
+ unsigned int nentries = attr->dest_nentries;
+ unsigned int ce_nbytes;
+ u32 ctrl_addr = ath10k_ce_base_address(ce_id);
+ dma_addr_t base_addr;
+ char *ptr;
+
+ nentries = roundup_pow_of_two(nentries);
+
+ if (ce_state->dest_ring) {
+ WARN_ON(ce_state->dest_ring->nentries != nentries);
+ return 0;
+ }
+
+ ce_nbytes = sizeof(struct ce_ring_state) + (nentries * sizeof(void *));
+ ptr = kzalloc(ce_nbytes, GFP_KERNEL);
+ if (ptr == NULL)
+ return -ENOMEM;
+
+ ce_state->dest_ring = (struct ce_ring_state *)ptr;
+ dest_ring = ce_state->dest_ring;
+
+ ptr += sizeof(struct ce_ring_state);
+ dest_ring->nentries = nentries;
+ dest_ring->nentries_mask = nentries - 1;
+
+ ath10k_pci_wake(ar);
+ dest_ring->sw_index = ath10k_ce_dest_ring_read_index_get(ar, ctrl_addr);
+ dest_ring->write_index =
+ ath10k_ce_dest_ring_write_index_get(ar, ctrl_addr);
+ ath10k_pci_sleep(ar);
+
+ dest_ring->per_transfer_context = (void **)ptr;
+
+ /*
+ * Legacy platforms that do not support cache
+ * coherent DMA are unsupported
+ */
+ dest_ring->base_addr_owner_space_unaligned =
+ pci_alloc_consistent(ar_pci->pdev,
+ (nentries * sizeof(struct ce_desc) +
+ CE_DESC_RING_ALIGN),
+ &base_addr);
+ dest_ring->base_addr_ce_space_unaligned = base_addr;
+
+ /*
+ * Correctly initialize memory to 0 to prevent garbage
+ * data crashing system when download firmware
+ */
+ memset(dest_ring->base_addr_owner_space_unaligned, 0,
+ nentries * sizeof(struct ce_desc) + CE_DESC_RING_ALIGN);
+
+ dest_ring->base_addr_owner_space = PTR_ALIGN(
+ dest_ring->base_addr_owner_space_unaligned,
+ CE_DESC_RING_ALIGN);
+ dest_ring->base_addr_ce_space = ALIGN(
+ dest_ring->base_addr_ce_space_unaligned,
+ CE_DESC_RING_ALIGN);
+
+ ath10k_pci_wake(ar);
+ ath10k_ce_dest_ring_base_addr_set(ar, ctrl_addr,
+ dest_ring->base_addr_ce_space);
+ ath10k_ce_dest_ring_size_set(ar, ctrl_addr, nentries);
+ ath10k_ce_dest_ring_byte_swap_set(ar, ctrl_addr, 0);
+ ath10k_ce_dest_ring_lowmark_set(ar, ctrl_addr, 0);
+ ath10k_ce_dest_ring_highmark_set(ar, ctrl_addr, nentries);
+ ath10k_pci_sleep(ar);
+
+ return 0;
+}
+
+static struct ce_state *ath10k_ce_init_state(struct ath10k *ar,
+ unsigned int ce_id,
+ const struct ce_attr *attr)
+{
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+ struct ce_state *ce_state = NULL;
+ u32 ctrl_addr = ath10k_ce_base_address(ce_id);
+
+ spin_lock_bh(&ar_pci->ce_lock);
+
+ if (!ar_pci->ce_id_to_state[ce_id]) {
+ ce_state = kzalloc(sizeof(*ce_state), GFP_ATOMIC);
+ if (ce_state == NULL) {
+ spin_unlock_bh(&ar_pci->ce_lock);
+ return NULL;
+ }
+
+ ar_pci->ce_id_to_state[ce_id] = ce_state;
+ ce_state->ar = ar;
+ ce_state->id = ce_id;
+ ce_state->ctrl_addr = ctrl_addr;
+ ce_state->state = CE_RUNNING;
+ /* Save attribute flags */
+ ce_state->attr_flags = attr->flags;
+ ce_state->src_sz_max = attr->src_sz_max;
+ }
+
+ spin_unlock_bh(&ar_pci->ce_lock);
+
+ return ce_state;
+}
+
+/*
+ * Initialize a Copy Engine based on caller-supplied attributes.
+ * This may be called once to initialize both source and destination
+ * rings or it may be called twice for separate source and destination
+ * initialization. It may be that only one side or the other is
+ * initialized by software/firmware.
+ */
+struct ce_state *ath10k_ce_init(struct ath10k *ar,
+ unsigned int ce_id,
+ const struct ce_attr *attr)
+{
+ struct ce_state *ce_state;
+ u32 ctrl_addr = ath10k_ce_base_address(ce_id);
+
+ ce_state = ath10k_ce_init_state(ar, ce_id, attr);
+ if (!ce_state) {
+ ath10k_err("Failed to initialize CE state for ID: %d\n", ce_id);
+ return NULL;
+ }
+
+ if (attr->src_nentries) {
+ if (ath10k_ce_init_src_ring(ar, ce_id, ce_state, attr)) {
+ ath10k_err("Failed to initialize CE src ring for ID: %d\n",
+ ce_id);
+ ath10k_ce_deinit(ce_state);
+ return NULL;
+ }
+ }
+
+ if (attr->dest_nentries) {
+ if (ath10k_ce_init_dest_ring(ar, ce_id, ce_state, attr)) {
+ ath10k_err("Failed to initialize CE dest ring for ID: %d\n",
+ ce_id);
+ ath10k_ce_deinit(ce_state);
+ return NULL;
+ }
+ }
+
+ /* Enable CE error interrupts */
+ ath10k_pci_wake(ar);
+ ath10k_ce_error_intr_enable(ar, ctrl_addr);
+ ath10k_pci_sleep(ar);
+
+ return ce_state;
+}
+
+void ath10k_ce_deinit(struct ce_state *ce_state)
+{
+ unsigned int ce_id = ce_state->id;
+ struct ath10k *ar = ce_state->ar;
+ struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+ ce_state->state = CE_UNUSED;
+ ar_pci->ce_id_to_state[ce_id] = NULL;
+
+ if (ce_state->src_ring) {
+ kfree(ce_state->src_ring->shadow_base_unaligned);
+ pci_free_consistent(ar_pci->pdev,
+ (ce_state->src_ring->nentries *
+ sizeof(struct ce_desc) +
+ CE_DESC_RING_ALIGN),
+ ce_state->src_ring->base_addr_owner_space,
+ ce_state->src_ring->base_addr_ce_space);
+ kfree(ce_state->src_ring);
+ }
+
+ if (ce_state->dest_ring) {
+ pci_free_consistent(ar_pci->pdev,
+ (ce_state->dest_ring->nentries *
+ sizeof(struct ce_desc) +
+ CE_DESC_RING_ALIGN),
+ ce_state->dest_ring->base_addr_owner_space,
+ ce_state->dest_ring->base_addr_ce_space);
+ kfree(ce_state->dest_ring);
+ }
+ kfree(ce_state);
+}
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox