Several new features introduced:
- TSO improves throughput on Tx side on systems with weak CPU
- ACS chooses best channel automatically when starting AP
- support for the new boot loader
Bug fixes:
- "wait for del_station to complete"
- "hold wil->mutex while managing vrings"
The rest are small fixes and improvements
Dedy Lansky (1):
wil6210: treat "unhandled event" as warning instead of error
Vladimir Kondratiev (12):
wil6210: support boot loader struct v0 & v1
wil6210: ACS implementation using QCA vendor command
wil6210: debugfs for channel survey
wil6210: count drops in Rx block ack reorder
wil6210: print "ulong" fields in hex format in the debugfs
wil6210: use <> vs. "" for global include
wil6210: wait for del_station to complete
wil6210: hold wil->mutex while managing vrings
wil6210: use wil_fw_error_recovery()
wil6210: skip HW version check for chip debugging
wil6210: TSO implementation
wil6210: improve mgmt frame handling
drivers/net/wireless/ath/wil6210/boot_loader.h | 57 ++++
drivers/net/wireless/ath/wil6210/cfg80211.c | 389 ++++++++++++++++++++++++
drivers/net/wireless/ath/wil6210/debugfs.c | 64 +++-
drivers/net/wireless/ath/wil6210/main.c | 112 +++++--
drivers/net/wireless/ath/wil6210/netdev.c | 5 +-
drivers/net/wireless/ath/wil6210/rx_reorder.c | 2 +
drivers/net/wireless/ath/wil6210/txrx.c | 383 ++++++++++++++++++++++-
drivers/net/wireless/ath/wil6210/txrx.h | 8 +
drivers/net/wireless/ath/wil6210/wil6210.h | 18 +-
drivers/net/wireless/ath/wil6210/wil_platform.c | 2 +-
drivers/net/wireless/ath/wil6210/wmi.c | 129 ++++++--
drivers/net/wireless/ath/wil6210/wmi.h | 38 ++-
12 files changed, 1129 insertions(+), 78 deletions(-)
create mode 100644 drivers/net/wireless/ath/wil6210/boot_loader.h
--
2.1.4
Print channel survey results on the debugfs. New entry
named "survey"
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/debugfs.c | 60 ++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c
index 75219a1..42bc6e6 100644
--- a/drivers/net/wireless/ath/wil6210/debugfs.c
+++ b/drivers/net/wireless/ath/wil6210/debugfs.c
@@ -1414,6 +1414,65 @@ static const struct file_operations fops_sta = {
.llseek = seq_lseek,
};
+/*---------Survey results------------*/
+static int wil_survey_debugfs_show(struct seq_file *s, void *data)
+{
+ struct wil6210_priv *wil = s->private;
+ int i, n_ch;
+ u16 filled;
+
+ if (!wil->survey_ready) {
+ seq_puts(s, "Survey not ready\n");
+ return 0;
+ }
+ seq_printf(s, "dwell_time : %d\n",
+ le32_to_cpu(wil->survey_reply.evt.dwell_time));
+ filled = le16_to_cpu(wil->survey_reply.evt.filled);
+ n_ch = min_t(int, wil->survey_reply.evt.num_scanned_channels,
+ ARRAY_SIZE(wil->survey_reply.ch_info));
+
+#define ACS_FILLED(x) (filled & WMI_ACS_INFO_BITMASK_ ## x) ? \
+ " " __stringify(x) : ""
+ seq_printf(s, "Filled : 0x%04x%s%s%s%s%s\n", filled,
+ ACS_FILLED(BEACON_FOUND),
+ ACS_FILLED(BUSY_TIME),
+ ACS_FILLED(TX_TIME),
+ ACS_FILLED(RX_TIME),
+ ACS_FILLED(NOISE)
+ );
+#undef ACS_FILLED
+ seq_printf(s, "Channels [%d] {\n", n_ch);
+ for (i = 0; i < n_ch; i++) {
+ struct scan_channel_info *ch = &wil->survey_reply.ch_info[i];
+
+ seq_printf(s, " [%d]", ch->channel);
+#define ACS_PRINT(x, str, field) do { if (filled & WMI_ACS_INFO_BITMASK_ ## x) \
+ seq_printf(s, " %s : %d", str, field); \
+ } while (0)
+ ACS_PRINT(BEACON_FOUND, "bcon", ch->beacon_found);
+ ACS_PRINT(BUSY_TIME, "busy", le16_to_cpu(ch->busy_time));
+ ACS_PRINT(TX_TIME, "tx", le16_to_cpu(ch->tx_time));
+ ACS_PRINT(RX_TIME, "rx", le16_to_cpu(ch->rx_time));
+ ACS_PRINT(NOISE, "noise", ch->noise);
+#undef ACS_PRINT
+ seq_puts(s, "\n");
+ }
+ seq_puts(s, "}\n");
+ return 0;
+}
+
+static int wil_survey_seq_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, wil_survey_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations fops_survey = {
+ .open = wil_survey_seq_open,
+ .release = single_release,
+ .read = seq_read,
+ .llseek = seq_lseek,
+};
+
/*----------------*/
static void wil6210_debugfs_init_blobs(struct wil6210_priv *wil,
struct dentry *dbg)
@@ -1460,6 +1519,7 @@ static const struct {
{"link", S_IRUGO, &fops_link},
{"info", S_IRUGO, &fops_info},
{"recovery", S_IRUGO | S_IWUSR, &fops_recovery},
+ {"survey", S_IRUGO, &fops_survey},
};
static void wil6210_debugfs_init_files(struct wil6210_priv *wil,
--
2.1.4
On Sun, Jul 5, 2015 at 10:24 AM, Vladimir Kondratiev
<[email protected]> wrote:
>
> Driver report supported TSO (v4 & v6) and IP checksum offload
> in addition to previously supported features. In data path
> skbs are checked for non-zero gso_size, and when detected sent
> to additional function for processing TSO SKBs. Since HW does not
> fully support TSO, additional effort is required from the driver.
> Driver partitions the data into mss sized descriptors which are
> then DMAed to the HW.
>
> Signed-off-by: Vladimir Shulman <[email protected]>
> Signed-off-by: Vladimir Kondratiev <[email protected]>
> ---
> drivers/net/wireless/ath/wil6210/netdev.c | 5 +-
> drivers/net/wireless/ath/wil6210/txrx.c | 380 +++++++++++++++++++++++++++++-
> drivers/net/wireless/ath/wil6210/txrx.h | 8 +
> 3 files changed, 380 insertions(+), 13 deletions(-)
>
[snip]
> @@ -1113,6 +1129,334 @@ static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
> return 0;
> }
>
> +/**
> + * Sets the descriptor @d up for csum. The corresponding
> + * @skb is used to obtain the protocol and headers length.
> + * Returns the protocol: 0 - not TCP, 1 - TCPv4, 2 - TCPv6.
> + * Note, if d==NULL, the function only returns the protocol result.
> + *
> + * It is very similar to previous wil_tx_desc_offload_setup_tso. This
> + * is "if unrolling" to optimize the critical path.
> + */
> +
> +static int wil_tx_desc_offload_setup(struct vring_tx_desc *d,
> + struct sk_buff *skb){
> + int protocol;
> +
> + if (skb->ip_summed != CHECKSUM_PARTIAL)
> + return 0;
> +
> + d->dma.b11 = ETH_HLEN; /* MAC header length */
> +
> + switch (skb->protocol) {
> + case cpu_to_be16(ETH_P_IP):
> + protocol = ip_hdr(skb)->protocol;
> + d->dma.b11 |= BIT(DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_POS);
> + break;
> + case cpu_to_be16(ETH_P_IPV6):
> + protocol = ipv6_hdr(skb)->nexthdr;
> + break;
> + default:
> + return -EINVAL;
> + }
I'd suggest to change the name of the variable. I think that protocol
is typically IPv4 or IPv6.
Your call of course.
If you only want to know if it is TCP or UDP, you can check gso_type
as well. This will be more efficient I guess since the share_info will
be already in cache, but that's just a guess.
[snip]
> +
> + while (len) {
> + wil_dbg_txrx(wil,
> + "TSO: len %d, rem_data %d, descs_used %d\n",
> + len, rem_data, descs_used);
> +
> + if (descs_used == avail) {
> + wil_err(wil, "TSO: ring overflow\n");
> + goto dma_error;
> + }
> +
> + lenmss = min_t(int, rem_data, len);
> + i = (swhead + descs_used) % vring->size;
> + wil_dbg_txrx(wil, "TSO: lenmss %d, i %d\n", lenmss, i);
> +
> + if (!headlen) {
> + pa = skb_frag_dma_map(dev, frag,
> + frag->size - len, lenmss,
> + DMA_TO_DEVICE);
> + vring->ctx[i].mapped_as = wil_mapped_as_page;
> + } else {
> + pa = dma_map_single(dev,
> + skb->data +
> + skb_headlen(skb) - headlen,
> + lenmss,
> + DMA_TO_DEVICE);
> + vring->ctx[i].mapped_as = wil_mapped_as_single;
> + headlen -= lenmss;
> + }
> +
> + if (unlikely(dma_mapping_error(dev, pa)))
> + goto dma_error;
> +
> + _desc = &vring->va[i].tx;
> +
> + if (!_first_desc) {
> + _first_desc = _desc;
> + first_ctx = &vring->ctx[i];
> + d = first_desc;
> + } else {
> + d = &desc_mem;
> + }
> +
> + wil_tx_desc_map(d, pa, lenmss, vring_index);
> + wil_tx_desc_offload_setup_tso(d, skb, desc_tso_type);
> +
> + /* use tso_type_first only once */
> + desc_tso_type = wil_tso_type_mid;
> +
> + descs_used++; /* desc used so far */
> + sg_desc_cnt++; /* desc used for this segment */
> + len -= lenmss;
> + rem_data -= lenmss;
> +
> + wil_dbg_txrx(wil,
> + "TSO: len %d, rem_data %d, descs_used %d, sg_desc_cnt %d,\n",
> + len, rem_data, descs_used, sg_desc_cnt);
> +
> + /* Close the segment if reached mss size or last frag*/
> + if (rem_data == 0 || (f == nr_frags - 1 && len == 0)) {
> + if (hdr_compensation_need) {
> + /* first segment include hdr desc for
> + * release
> + */
> + hdr_ctx->nr_frags = sg_desc_cnt;
> + wil_tx_desc_set_nr_frags(first_desc,
> + sg_desc_cnt +
> + 1);
> + hdr_compensation_need = false;
> + } else {
> + wil_tx_desc_set_nr_frags(first_desc,
> + sg_desc_cnt);
> + }
> + first_ctx->nr_frags = sg_desc_cnt - 1;
> +
> + wil_tx_last_desc(d);
> +
> + /* first descriptor may also be the last
> + * for this mss - make sure not to copy
> + * it twice
> + */
> + if (first_desc != d)
> + *_first_desc = *first_desc;
> +
> + /*last descriptor will be copied at the end
> + * of this TS processing
> + */
> + if (f < nr_frags - 1 || len > 0)
> + *_desc = *d;
> +
> + rem_data = mss;
> + _first_desc = NULL;
> + sg_desc_cnt = 0;
> + } else if (first_desc != d) /* update mid descriptor */
> + *_desc = *d;
> + }
> + }
So your device is able to replicate and update the IP / TCP header?
I don't really follow what your device is able to do.
You seem to be cutting the frags so that their length sums up to mss.
Which hints that your device can't segment the buffer by itself. OTOH,
I don't see how you treat the IP / TCP header copy and modification.
> +
> + /* first descriptor may also be the last.
> + * in this case d pointer is invalid
> + */
> + if (_first_desc == _desc)
> + d = first_desc;
> +
> + /* Last data descriptor */
> + wil_set_tx_desc_last_tso(d);
> + *_desc = *d;
> +
> + /* Fill the total number of descriptors in first desc (hdr)*/
> + wil_tx_desc_set_nr_frags(hdr_desc, descs_used);
> + *_hdr_desc = *hdr_desc;
> +
> + /* hold reference to skb
> + * to prevent skb release before accounting
> + * in case of immediate "tx done"
> + */
> + vring->ctx[i].skb = skb_get(skb);
> +
> + /* performance monitoring */
> + used = wil_vring_used_tx(vring);
> + if (wil_val_in_range(vring_idle_trsh,
> + used, used + descs_used)) {
> + txdata->idle += get_cycles() - txdata->last_idle;
> + wil_dbg_txrx(wil, "Ring[%2d] not idle %d -> %d\n",
> + vring_index, used, used + descs_used);
> + }
> +
> + /* advance swhead */
> + wil_dbg_txrx(wil, "TSO: Tx swhead %d -> %d\n", swhead, vring->swhead);
> + wil_vring_advance_head(vring, descs_used);
> +
> + /* make sure all writes to descriptors (shared memory) are done before
> + * committing them to HW
> + */
> + wmb();
> +
> + iowrite32(vring->swhead, wil->csr + HOSTADDR(vring->hwtail));
> + return 0;
linux/device.h should be included using <>, not ""
since it is not local include
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/wil_platform.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/wil_platform.c b/drivers/net/wireless/ath/wil6210/wil_platform.c
index de15f14..2e831bf 100644
--- a/drivers/net/wireless/ath/wil6210/wil_platform.c
+++ b/drivers/net/wireless/ath/wil6210/wil_platform.c
@@ -14,7 +14,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include "linux/device.h"
+#include <linux/device.h>
#include "wil_platform.h"
int __init wil_platform_modinit(void)
--
2.1.4
Vladimir Kondratiev <[email protected]> writes:
> Print channel survey results on the debugfs. New entry
> named "survey"
>
> Signed-off-by: Vladimir Kondratiev <[email protected]>
Why? We have nl80211 command for this, if I understood correctly this
more or less duplicates that functionality?
--
Kalle Valo
Vladimir Kondratiev <[email protected]> writes:
> ACS is implemented by registering to QCA vendor ACS sub command.
> On receive of the command from hostapd, driver issues passive
> scan command and block until scan results are available.
> When received, the results are sent up using QCA vendor ACS
> results event
>
> Signed-off-by: Vladimir Shulman <[email protected]>
> Signed-off-by: Vladimir Kondratiev <[email protected]>
I dropped this patch per discussion but now I got a conflict:
Pushing patch "wil6210-improve-mgmt-frame-han" ... done (conflict)
Error: 1 merge conflict(s)
CONFLICT (content): Merge conflict in
drivers/net/wireless/ath/wil6210/wmi.c
Please resend the whole patchset.
--
Kalle Valo
Print channel survey results on the debugfs. New entry
named "survey"
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/debugfs.c | 60 ++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c
index 75219a1..42bc6e6 100644
--- a/drivers/net/wireless/ath/wil6210/debugfs.c
+++ b/drivers/net/wireless/ath/wil6210/debugfs.c
@@ -1414,6 +1414,65 @@ static const struct file_operations fops_sta = {
.llseek = seq_lseek,
};
+/*---------Survey results------------*/
+static int wil_survey_debugfs_show(struct seq_file *s, void *data)
+{
+ struct wil6210_priv *wil = s->private;
+ int i, n_ch;
+ u16 filled;
+
+ if (!wil->survey_ready) {
+ seq_puts(s, "Survey not ready\n");
+ return 0;
+ }
+ seq_printf(s, "dwell_time : %d\n",
+ le32_to_cpu(wil->survey_reply.evt.dwell_time));
+ filled = le16_to_cpu(wil->survey_reply.evt.filled);
+ n_ch = min_t(int, wil->survey_reply.evt.num_scanned_channels,
+ ARRAY_SIZE(wil->survey_reply.ch_info));
+
+#define ACS_FILLED(x) (filled & WMI_ACS_INFO_BITMASK_ ## x) ? \
+ " " __stringify(x) : ""
+ seq_printf(s, "Filled : 0x%04x%s%s%s%s%s\n", filled,
+ ACS_FILLED(BEACON_FOUND),
+ ACS_FILLED(BUSY_TIME),
+ ACS_FILLED(TX_TIME),
+ ACS_FILLED(RX_TIME),
+ ACS_FILLED(NOISE)
+ );
+#undef ACS_FILLED
+ seq_printf(s, "Channels [%d] {\n", n_ch);
+ for (i = 0; i < n_ch; i++) {
+ struct scan_channel_info *ch = &wil->survey_reply.ch_info[i];
+
+ seq_printf(s, " [%d]", ch->channel);
+#define ACS_PRINT(x, str, field) do { if (filled & WMI_ACS_INFO_BITMASK_ ## x) \
+ seq_printf(s, " %s : %d", str, field); \
+ } while (0)
+ ACS_PRINT(BEACON_FOUND, "bcon", ch->beacon_found);
+ ACS_PRINT(BUSY_TIME, "busy", le16_to_cpu(ch->busy_time));
+ ACS_PRINT(TX_TIME, "tx", le16_to_cpu(ch->tx_time));
+ ACS_PRINT(RX_TIME, "rx", le16_to_cpu(ch->rx_time));
+ ACS_PRINT(NOISE, "noise", ch->noise);
+#undef ACS_PRINT
+ seq_puts(s, "\n");
+ }
+ seq_puts(s, "}\n");
+ return 0;
+}
+
+static int wil_survey_seq_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, wil_survey_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations fops_survey = {
+ .open = wil_survey_seq_open,
+ .release = single_release,
+ .read = seq_read,
+ .llseek = seq_lseek,
+};
+
/*----------------*/
static void wil6210_debugfs_init_blobs(struct wil6210_priv *wil,
struct dentry *dbg)
@@ -1460,6 +1519,7 @@ static const struct {
{"link", S_IRUGO, &fops_link},
{"info", S_IRUGO, &fops_info},
{"recovery", S_IRUGO | S_IWUSR, &fops_recovery},
+ {"survey", S_IRUGO, &fops_survey},
};
static void wil6210_debugfs_init_files(struct wil6210_priv *wil,
--
2.1.4
To prevent race when connect flow may run in parallel with
the disconnect event.
Scenario leading to the bug is: while running connect flow on the AP,
STA sends disconnect. log follows.
<7>[ 668.736269] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Configure for connection CID 1
<7>[ 668.736269] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_vring_init_tx() max_mpdu_size 2048
<7>[ 668.736301] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_vring_alloc()
<7>[ 668.736363] wil6210 0000:01:00.0: wlan0: DBG[MISC]vring[1024] 0xffbe8000:d962ce08 0xdb244000
<7>[ 668.736394] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Head 0x00880300 -> 0x00880308
<7>[ 668.736394] wil6210 0000:01:00.0: wlan0: DBG[ WMI]WMI command 0x0821 [28]
<7>[ 668.736426] DBG[ WMI]Cmd 00000000: 20 00 24 00 00 00 00 00 00 00 21 08 00 00 00 00 .$.......!.....
<7>[ 668.736426] DBG[ WMI]cmd 00000000: 00 00 00 00 00 00 5f 5c 00 00 00 00 00 04 00 08 ......_\........
<7>[ 668.736457] DBG[ WMI]cmd 00000010: 01 01 00 00 00 00 00 00 00 00 ff 0f ............
<7>[ 668.736488] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]Pseudo IRQ 0x00000004
<7>[ 668.736519] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Handle WMI 0x1824 (reply_id 0x1821)
<7>[ 668.736519] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]wil6210_mask_irq_pseudo()
<7>[ 668.736519] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]ISR MISC 0x20000000
<7>[ 668.736551] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Handle WMI 0x1003 (reply_id 0x1821)
<7>[ 668.736551] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Disconnect 04:ce:14:00:07:70 reason [proto 3 wmi 4]
<7>[ 668.736582] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil6210_disconnect()
<7>[ 668.736613] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]Thread IRQ
<7>[ 668.736613] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]Thread ISR MISC 0x20000000
<7>[ 668.736644] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]MBOX event
<7>[ 668.736644] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Mbox head 00880330 tail 00880328
<7>[ 668.736676] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Mbox evt 001a 0010 0000 00
<7>[ 668.736676] wil6210 0000:01:00.0: wlan0: DBG[ WMI]WMI event 0x1821 MID 0 @3255145 msec
<7>[ 668.736707] DBG[ WMI]evt 00000000: 1a 00 10 00 00 00 00 10 00 00 21 18 69 ab 31 00 ..........!.i.1.
<7>[ 668.736707] DBG[ WMI]evt 00000010: 01 01 00 00 00 00 00 00 ........
<7>[ 668.736738] wil6210 0000:01:00.0: wlan0: DBG[ WMI]queue_work -> 0
<7>[ 668.736738] wil6210 0000:01:00.0: wlan0: DBG[ WMI]wmi_recv_cmd -> 1 events queued
<7>[ 668.736769] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]wil6210_unmask_irq_pseudo()
<7>[ 668.736832] wil6210 0000:01:00.0: wlan0: DBG[MISC]Disconnect 04:ce:14:00:07:70, CID=1, reason=3
<7>[ 668.736832] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_disconnect_cid(CID 1, status 1)
<7>[ 668.736894] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_vring_fini_tx() id=1
<7>[ 668.736894] wil6210 0000:01:00.0: wlan0: DBG[MISC]free Tx vring 1 [1024] 0xffbe8000:d962ce08 0xdb244000
<7>[ 668.736957] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Handle WMI 0x1821 (reply_id 0x1821)
<7>[ 668.736988] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Complete WMI 0x1821
<7>[ 668.737019] wil6210 0000:01:00.0: wlan0: DBG[ WMI]wmi_call(0x0821->0x1821) completed in 0 msec
<3>[ 668.737019] wil6210 0000:01:00.0: wlan0: Tx config failed, status 0x01
<7>[ 668.739518] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_cfg80211_del_station(04:ce:14:00:07:70, reason=2)
<7>[ 668.739550] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil6210_disconnect()
<7>[ 668.739550] wil6210 0000:01:00.0: wlan0: DBG[MISC]_wil6210_disconnect(bssid=04:ce:14:00:07:70, reason=2, ev-)
<7>[ 668.739581] wil6210 0000:01:00.0: wlan0: DBG[MISC]Disconnect 04:ce:14:00:07:70, CID=-2, reason=2
<7>[ 668.742705] wil6210 0000:01:00.0: wlan0: DBG[MISC]free Tx vring 1 [1024] 0x (null):d962ce08 0x (null)
<3>[ 668.742736] __dma_free_remap: trying to free invalid coherent area: (null)
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/main.c | 18 +++++++++++++-----
drivers/net/wireless/ath/wil6210/txrx.c | 3 +++
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
index 65d15e2..4740edb 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -400,20 +400,26 @@ void wil_bcast_fini(struct wil6210_priv *wil)
static void wil_connect_worker(struct work_struct *work)
{
- int rc;
+ int rc, cid, ringid;
struct wil6210_priv *wil = container_of(work, struct wil6210_priv,
connect_worker);
struct net_device *ndev = wil_to_ndev(wil);
- int cid = wil->pending_connect_cid;
- int ringid = wil_find_free_vring(wil);
+ mutex_lock(&wil->mutex);
+ cid = wil->pending_connect_cid;
if (cid < 0) {
wil_err(wil, "No connection pending\n");
- return;
+ goto out;
+ }
+ ringid = wil_find_free_vring(wil);
+ if (ringid < 0) {
+ wil_err(wil, "No free vring found\n");
+ goto out;
}
- wil_dbg_wmi(wil, "Configure for connection CID %d\n", cid);
+ wil_dbg_wmi(wil, "Configure for connection CID %d vring %d\n",
+ cid, ringid);
rc = wil_vring_init_tx(wil, ringid, 1 << tx_ring_order, cid, 0);
wil->pending_connect_cid = -1;
@@ -423,6 +429,8 @@ static void wil_connect_worker(struct work_struct *work)
} else {
wil->sta[cid].status = wil_sta_unused;
}
+out:
+ mutex_unlock(&wil->mutex);
}
int wil_priv_init(struct wil6210_priv *wil)
diff --git a/drivers/net/wireless/ath/wil6210/txrx.c b/drivers/net/wireless/ath/wil6210/txrx.c
index aa20af8..112192f1 100644
--- a/drivers/net/wireless/ath/wil6210/txrx.c
+++ b/drivers/net/wireless/ath/wil6210/txrx.c
@@ -160,6 +160,7 @@ static void wil_vring_free(struct wil6210_priv *wil, struct vring *vring,
struct device *dev = wil_to_dev(wil);
size_t sz = vring->size * sizeof(vring->va[0]);
+ WARN_ON(!mutex_is_locked(&wil->mutex));
if (tx) {
int vring_index = vring - wil->vring_tx;
@@ -707,6 +708,7 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size,
wil_dbg_misc(wil, "%s() max_mpdu_size %d\n", __func__,
cmd.vring_cfg.tx_sw_ring.max_mpdu_size);
+ WARN_ON(!mutex_is_locked(&wil->mutex));
if (vring->va) {
wil_err(wil, "Tx ring [%d] already allocated\n", id);
@@ -779,6 +781,7 @@ int wil_vring_init_bcast(struct wil6210_priv *wil, int id, int size)
wil_dbg_misc(wil, "%s() max_mpdu_size %d\n", __func__,
cmd.vring_cfg.tx_sw_ring.max_mpdu_size);
+ WARN_ON(!mutex_is_locked(&wil->mutex));
if (vring->va) {
wil_err(wil, "Tx ring [%d] already allocated\n", id);
--
2.1.4
When performing Rx reordering, count skb's dropped
per reorder buffer; and print dropped packets count
on the "stations" debugfs entry
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/debugfs.c | 2 +-
drivers/net/wireless/ath/wil6210/rx_reorder.c | 2 ++
drivers/net/wireless/ath/wil6210/wil6210.h | 2 ++
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c
index 42bc6e6..9ed1a94 100644
--- a/drivers/net/wireless/ath/wil6210/debugfs.c
+++ b/drivers/net/wireless/ath/wil6210/debugfs.c
@@ -1353,7 +1353,7 @@ static void wil_print_rxtid(struct seq_file *s, struct wil_tid_ampdu_rx *r)
else
seq_printf(s, "%c", r->reorder_buf[i] ? '*' : '_');
}
- seq_printf(s, "] last drop 0x%03x\n", r->ssn_last_drop);
+ seq_printf(s, "] drop %llu last 0x%03x\n", r->drop, r->ssn_last_drop);
}
static int wil_sta_debugfs_show(struct seq_file *s, void *data)
diff --git a/drivers/net/wireless/ath/wil6210/rx_reorder.c b/drivers/net/wireless/ath/wil6210/rx_reorder.c
index ca10dcf..e4ac11c 100644
--- a/drivers/net/wireless/ath/wil6210/rx_reorder.c
+++ b/drivers/net/wireless/ath/wil6210/rx_reorder.c
@@ -153,6 +153,7 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock)
/* frame with out of date sequence number */
if (seq_less(seq, r->head_seq_num)) {
r->ssn_last_drop = seq;
+ r->drop++;
dev_kfree_skb(skb);
goto out;
}
@@ -173,6 +174,7 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock)
/* check if we already stored this frame */
if (r->reorder_buf[index]) {
+ r->drop++;
dev_kfree_skb(skb);
goto out;
}
diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h
index 4b7135e..284edda 100644
--- a/drivers/net/wireless/ath/wil6210/wil6210.h
+++ b/drivers/net/wireless/ath/wil6210/wil6210.h
@@ -427,6 +427,7 @@ struct pci_dev;
* @timeout: reset timer value (in TUs).
* @dialog_token: dialog token for aggregation session
* @rcu_head: RCU head used for freeing this struct
+ * @drop: total frames dropped for this reorder buffer
*
* This structure's lifetime is managed by RCU, assignments to
* the array holding it must hold the aggregation mutex.
@@ -444,6 +445,7 @@ struct wil_tid_ampdu_rx {
u16 buf_size;
u16 timeout;
u16 ssn_last_drop;
+ unsigned long long drop;
u8 dialog_token;
bool first_time; /* is it 1-st time this buffer used? */
};
--
2.1.4
Vladimir Kondratiev <[email protected]> writes:
> To prevent race when connect flow may run in parallel with
> the disconnect event.
>
> Scenario leading to the bug is: while running connect flow on the AP,
> STA sends disconnect. log follows.
[...]
> --- a/drivers/net/wireless/ath/wil6210/txrx.c
> +++ b/drivers/net/wireless/ath/wil6210/txrx.c
> @@ -160,6 +160,7 @@ static void wil_vring_free(struct wil6210_priv *wil, struct vring *vring,
> struct device *dev = wil_to_dev(wil);
> size_t sz = vring->size * sizeof(vring->va[0]);
>
> + WARN_ON(!mutex_is_locked(&wil->mutex));
lockdep_assert_held() is supposed to be used for verifying locking, see
ath10k for examples.
--
Kalle Valo
Multiple del_station requests may be sent to the driver by the
supplicant when turning down AP. This may overflow mailbox
between the FW and ucode
Wait till disconnect of one STA completed before sending next command.
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/wmi.c | 29 ++++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index 12b5770..0678fff 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -1151,15 +1151,42 @@ int wmi_get_temperature(struct wil6210_priv *wil, u32 *t_bb, u32 *t_rf)
int wmi_disconnect_sta(struct wil6210_priv *wil, const u8 *mac, u16 reason)
{
+ int rc;
+ u16 reason_code;
struct wmi_disconnect_sta_cmd cmd = {
.disconnect_reason = cpu_to_le16(reason),
};
+ struct {
+ struct wil6210_mbox_hdr_wmi wmi;
+ struct wmi_disconnect_event evt;
+ } __packed reply;
ether_addr_copy(cmd.dst_mac, mac);
wil_dbg_wmi(wil, "%s(%pM, reason %d)\n", __func__, mac, reason);
- return wmi_send(wil, WMI_DISCONNECT_STA_CMDID, &cmd, sizeof(cmd));
+ rc = wmi_call(wil, WMI_DISCONNECT_STA_CMDID, &cmd, sizeof(cmd),
+ WMI_DISCONNECT_EVENTID, &reply, sizeof(reply), 1000);
+ /* failure to disconnect in reasonable time treated as FW error */
+ if (rc) {
+ wil_fw_error_recovery(wil);
+ return rc;
+ }
+
+ /* call event handler manually after processing wmi_call,
+ * to avoid deadlock - disconnect event handler acquires wil->mutex
+ * while it is already held here
+ */
+ reason_code = le16_to_cpu(reply.evt.protocol_reason_status);
+
+ wil_dbg_wmi(wil, "Disconnect %pM reason [proto %d wmi %d]\n",
+ reply.evt.bssid, reason_code,
+ reply.evt.disconnect_reason);
+
+ wil->sinfo_gen++;
+ wil6210_disconnect(wil, reply.evt.bssid, reason_code, true);
+
+ return 0;
}
int wmi_addba(struct wil6210_priv *wil, u8 ringid, u8 size, u16 timeout)
--
2.1.4
There are 2 versions of boot loader struct: v0 and v1.
In the v1, boot loader build version added; as well as
RF status.
Support both versions.
Boot loader structure v1 has RF status; ignore RF error if firmware
not going to be loaded; driver can still be used to interact with the HW
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/boot_loader.h | 57 +++++++++++++++++
drivers/net/wireless/ath/wil6210/main.c | 85 ++++++++++++++++++++------
drivers/net/wireless/ath/wil6210/wil6210.h | 10 ---
3 files changed, 125 insertions(+), 27 deletions(-)
create mode 100644 drivers/net/wireless/ath/wil6210/boot_loader.h
diff --git a/drivers/net/wireless/ath/wil6210/boot_loader.h b/drivers/net/wireless/ath/wil6210/boot_loader.h
new file mode 100644
index 0000000..0655eff
--- /dev/null
+++ b/drivers/net/wireless/ath/wil6210/boot_loader.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2015 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.
+ */
+
+/* This file contains the definitions for the boot loader
+ * for the Qualcomm "Sparrow" 60 Gigabit wireless solution.
+ */
+#ifndef BOOT_LOADER_EXPORT_H_
+#define BOOT_LOADER_EXPORT_H_
+
+struct bl_dedicated_registers_v1 {
+ __le32 boot_loader_ready; /* 0x880A3C driver will poll
+ * this Dword until BL will
+ * set it to 1 (initial value
+ * should be 0)
+ */
+ __le32 boot_loader_struct_version; /* 0x880A40 BL struct ver. */
+ __le16 rf_type; /* 0x880A44 connected RF ID */
+ __le16 rf_status; /* 0x880A46 RF status,
+ * 0 is OK else error
+ */
+ __le32 baseband_type; /* 0x880A48 board type ID */
+ u8 mac_address[6]; /* 0x880A4c BL mac address */
+ u8 bl_version_major; /* 0x880A52 BL ver. major */
+ u8 bl_version_minor; /* 0x880A53 BL ver. minor */
+ __le16 bl_version_subminor; /* 0x880A54 BL ver. subminor */
+ __le16 bl_version_build; /* 0x880A56 BL ver. build */
+} __packed;
+
+/* the following struct is the version 0 struct */
+
+struct bl_dedicated_registers_v0 {
+ __le32 boot_loader_ready; /* 0x880A3C driver will poll
+ * this Dword until BL will
+ * set it to 1 (initial value
+ * should be 0)
+ */
+#define BL_READY (1) /* ready indication */
+ __le32 boot_loader_struct_version; /* 0x880A40 BL struct ver. */
+ __le32 rf_type; /* 0x880A44 connected RF ID */
+ __le32 baseband_type; /* 0x880A48 board type ID */
+ u8 mac_address[6]; /* 0x880A4c BL mac address */
+} __packed;
+
+#endif /* BOOT_LOADER_EXPORT_H_ */
+
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
index b9febab..65d15e2 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -21,6 +21,7 @@
#include "wil6210.h"
#include "txrx.h"
#include "wmi.h"
+#include "boot_loader.h"
#define WAIT_FOR_DISCONNECT_TIMEOUT_MS 2000
#define WAIT_FOR_DISCONNECT_INTERVAL_MS 10
@@ -565,7 +566,8 @@ static int wil_target_reset(struct wil6210_priv *wil)
wil_halt_cpu(wil);
/* clear all boot loader "ready" bits */
- W(RGF_USER_BL + offsetof(struct RGF_BL, ready), 0);
+ W(RGF_USER_BL +
+ offsetof(struct bl_dedicated_registers_v0, boot_loader_ready), 0);
/* Clear Fw Download notification */
C(RGF_USER_USAGE_6, BIT(0));
@@ -607,7 +609,8 @@ static int wil_target_reset(struct wil6210_priv *wil)
/* wait until device ready. typical time is 20..80 msec */
do {
msleep(RST_DELAY);
- x = R(RGF_USER_BL + offsetof(struct RGF_BL, ready));
+ x = R(RGF_USER_BL + offsetof(struct bl_dedicated_registers_v0,
+ boot_loader_ready));
if (x1 != x) {
wil_dbg_misc(wil, "BL.ready 0x%08x => 0x%08x\n", x1, x);
x1 = x;
@@ -617,7 +620,7 @@ static int wil_target_reset(struct wil6210_priv *wil)
x);
return -ETIME;
}
- } while (x != BIT_BL_READY);
+ } while (x != BL_READY);
C(RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);
@@ -641,25 +644,71 @@ void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r)
static int wil_get_bl_info(struct wil6210_priv *wil)
{
struct net_device *ndev = wil_to_ndev(wil);
- struct RGF_BL bl;
-
- wil_memcpy_fromio_32(&bl, wil->csr + HOSTADDR(RGF_USER_BL), sizeof(bl));
- le32_to_cpus(&bl.ready);
- le32_to_cpus(&bl.version);
- le32_to_cpus(&bl.rf_type);
- le32_to_cpus(&bl.baseband_type);
+ union {
+ struct bl_dedicated_registers_v0 bl0;
+ struct bl_dedicated_registers_v1 bl1;
+ } bl;
+ u32 bl_ver;
+ u8 *mac;
+ u16 rf_status;
+
+ bl_ver = R(RGF_USER_BL + offsetof(struct bl_dedicated_registers_v0,
+ boot_loader_struct_version));
+ switch (bl_ver) {
+ case 0:
+ wil_memcpy_fromio_32(&bl, wil->csr + HOSTADDR(RGF_USER_BL),
+ sizeof(bl.bl0));
+ le32_to_cpus(&bl.bl0.boot_loader_ready);
+ le32_to_cpus(&bl.bl0.boot_loader_struct_version);
+ le32_to_cpus(&bl.bl0.rf_type);
+ le32_to_cpus(&bl.bl0.baseband_type);
+ mac = bl.bl0.mac_address;
+ rf_status = 0; /* actually, unknown */
+ wil_info(wil,
+ "Boot Loader struct v%d: MAC = %pM RF = 0x%08x bband = 0x%08x\n",
+ bl_ver, mac,
+ bl.bl0.rf_type, bl.bl0.baseband_type);
+ wil_info(wil, "Boot Loader build unknown for struct v0\n");
+ break;
+ case 1:
+ wil_memcpy_fromio_32(&bl, wil->csr + HOSTADDR(RGF_USER_BL),
+ sizeof(bl.bl1));
+ le32_to_cpus(&bl.bl1.boot_loader_ready);
+ le32_to_cpus(&bl.bl1.boot_loader_struct_version);
+ le16_to_cpus(&bl.bl1.rf_type);
+ rf_status = le16_to_cpu(bl.bl1.rf_status);
+ le32_to_cpus(&bl.bl1.baseband_type);
+ le16_to_cpus(&bl.bl1.bl_version_subminor);
+ le16_to_cpus(&bl.bl1.bl_version_build);
+ mac = bl.bl1.mac_address;
+ wil_info(wil,
+ "Boot Loader struct v%d: MAC = %pM RF = 0x%04x (status 0x%04x) bband = 0x%08x\n",
+ bl_ver, mac,
+ bl.bl1.rf_type, rf_status,
+ bl.bl1.baseband_type);
+ wil_info(wil, "Boot Loader build %d.%d.%d.%d\n",
+ bl.bl1.bl_version_major, bl.bl1.bl_version_minor,
+ bl.bl1.bl_version_subminor, bl.bl1.bl_version_build);
+ break;
+ default:
+ wil_err(wil, "BL: unsupported struct version 0x%08x\n", bl_ver);
+ return -EINVAL;
+ }
- if (!is_valid_ether_addr(bl.mac_address)) {
- wil_err(wil, "BL: Invalid MAC %pM\n", bl.mac_address);
+ if (!is_valid_ether_addr(mac)) {
+ wil_err(wil, "BL: Invalid MAC %pM\n", mac);
return -EINVAL;
}
- ether_addr_copy(ndev->perm_addr, bl.mac_address);
+ ether_addr_copy(ndev->perm_addr, mac);
if (!is_valid_ether_addr(ndev->dev_addr))
- ether_addr_copy(ndev->dev_addr, bl.mac_address);
- wil_info(wil,
- "Boot Loader: ver = %d MAC = %pM RF = 0x%08x bband = 0x%08x\n",
- bl.version, bl.mac_address, bl.rf_type, bl.baseband_type);
+ ether_addr_copy(ndev->dev_addr, mac);
+
+ if (rf_status) {/* bad RF cable? */
+ wil_err(wil, "RF communication error 0x%04x",
+ rf_status);
+ return -EAGAIN;
+ }
return 0;
}
@@ -735,6 +784,8 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw)
return rc;
rc = wil_get_bl_info(wil);
+ if (rc == -EAGAIN && !load_fw) /* ignore RF error if not going up */
+ rc = 0;
if (rc)
return rc;
diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h
index c63e4a3..b79ba49 100644
--- a/drivers/net/wireless/ath/wil6210/wil6210.h
+++ b/drivers/net/wireless/ath/wil6210/wil6210.h
@@ -127,16 +127,6 @@ struct RGF_ICR {
u32 IMC; /* Mask Clear, write 1 to clear */
} __packed;
-struct RGF_BL {
- u32 ready; /* 0x880A3C bit [0] */
-#define BIT_BL_READY BIT(0)
- u32 version; /* 0x880A40 version of the BL struct */
- u32 rf_type; /* 0x880A44 ID of the connected RF */
- u32 baseband_type; /* 0x880A48 ID of the baseband */
- u8 mac_address[ETH_ALEN]; /* 0x880A4C permanent MAC */
- u8 pad[2];
-} __packed;
-
/* registers - FW addresses */
#define RGF_USER_USAGE_1 (0x880004)
#define RGF_USER_USAGE_6 (0x880018)
--
2.1.4
Vladimir Kondratiev <[email protected]> writes:
> There are 2 versions of boot loader struct: v0 and v1.
> In the v1, boot loader build version added; as well as
> RF status.
>
> Support both versions.
>
> Boot loader structure v1 has RF status; ignore RF error if firmware
> not going to be loaded; driver can still be used to interact with the HW
>
> Signed-off-by: Vladimir Kondratiev <[email protected]>
I'll fix this but just so you know:
Importing patch "wil6210-support-boot-loader-st" ... <stdin>:70: new blank line at EOF.
+
warning: 1 line adds whitespace errors.
done
--
Kalle Valo
From: Dedy Lansky <[email protected]>
FW is allowed to generate WMI events that are not handled by this driver.
Treat such case as warning instead of error.
Signed-off-by: Dedy Lansky <[email protected]>
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/wmi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index d3f75de..2cee5c7 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -1362,7 +1362,7 @@ static void wmi_event_handle(struct wil6210_priv *wil,
/* search for handler */
if (!wmi_evt_call_handler(wil, id, evt_data,
len - sizeof(*wmi))) {
- wil_err(wil, "Unhandled event 0x%04x\n", id);
+ wil_info(wil, "Unhandled event 0x%04x\n", id);
}
} else {
wil_err(wil, "Unknown event type\n");
--
2.1.4
Check event length;
hex dump both Rx and Tx frames
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/wmi.c | 54 +++++++++++++++++++++++++++-------
1 file changed, 44 insertions(+), 10 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index 0678fff..d3f75de 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -312,22 +312,44 @@ static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len)
struct wiphy *wiphy = wil_to_wiphy(wil);
struct ieee80211_mgmt *rx_mgmt_frame =
(struct ieee80211_mgmt *)data->payload;
- int ch_no = data->info.channel+1;
- u32 freq = ieee80211_channel_to_frequency(ch_no,
- IEEE80211_BAND_60GHZ);
- struct ieee80211_channel *channel = ieee80211_get_channel(wiphy, freq);
- s32 signal = data->info.sqi;
- __le16 fc = rx_mgmt_frame->frame_control;
- u32 d_len = le32_to_cpu(data->info.len);
- u16 d_status = le16_to_cpu(data->info.status);
-
- wil_dbg_wmi(wil, "MGMT: channel %d MCS %d SNR %d SQI %d%%\n",
+ int flen = len - offsetof(struct wmi_rx_mgmt_packet_event, payload);
+ int ch_no;
+ u32 freq;
+ struct ieee80211_channel *channel;
+ s32 signal;
+ __le16 fc;
+ u32 d_len;
+ u16 d_status;
+
+ if (flen < 0) {
+ wil_err(wil, "MGMT Rx: short event, len %d\n", len);
+ return;
+ }
+
+ d_len = le32_to_cpu(data->info.len);
+ if (d_len != flen) {
+ wil_err(wil,
+ "MGMT Rx: length mismatch, d_len %d should be %d\n",
+ d_len, flen);
+ return;
+ }
+
+ ch_no = data->info.channel + 1;
+ freq = ieee80211_channel_to_frequency(ch_no, IEEE80211_BAND_60GHZ);
+ channel = ieee80211_get_channel(wiphy, freq);
+ signal = data->info.sqi;
+ d_status = le16_to_cpu(data->info.status);
+ fc = rx_mgmt_frame->frame_control;
+
+ wil_dbg_wmi(wil, "MGMT Rx: channel %d MCS %d SNR %d SQI %d%%\n",
data->info.channel, data->info.mcs, data->info.snr,
data->info.sqi);
wil_dbg_wmi(wil, "status 0x%04x len %d fc 0x%04x\n", d_status, d_len,
le16_to_cpu(fc));
wil_dbg_wmi(wil, "qid %d mid %d cid %d\n",
data->info.qid, data->info.mid, data->info.cid);
+ wil_hex_dump_wmi("MGMT Rx ", DUMP_PREFIX_OFFSET, 16, 1, rx_mgmt_frame,
+ d_len, true);
if (!channel) {
wil_err(wil, "Frame on unsupported channel\n");
@@ -363,6 +385,17 @@ static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len)
}
}
+static void wmi_evt_tx_mgmt(struct wil6210_priv *wil, int id, void *d, int len)
+{
+ struct wmi_tx_mgmt_packet_event *data = d;
+ struct ieee80211_mgmt *mgmt_frame =
+ (struct ieee80211_mgmt *)data->payload;
+ int flen = len - offsetof(struct wmi_tx_mgmt_packet_event, payload);
+
+ wil_hex_dump_wmi("MGMT Tx ", DUMP_PREFIX_OFFSET, 16, 1, mgmt_frame,
+ flen, true);
+}
+
static void wmi_evt_scan_complete(struct wil6210_priv *wil, int id,
void *d, int len)
{
@@ -680,6 +713,7 @@ static const struct {
{WMI_READY_EVENTID, wmi_evt_ready},
{WMI_FW_READY_EVENTID, wmi_evt_fw_ready},
{WMI_RX_MGMT_PACKET_EVENTID, wmi_evt_rx_mgmt},
+ {WMI_TX_MGMT_PACKET_EVENTID, wmi_evt_tx_mgmt},
{WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete},
{WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID, wmi_evt_survey_complete},
{WMI_CONNECT_EVENTID, wmi_evt_connect},
--
2.1.4
Driver report supported TSO (v4 & v6) offload
in addition to previously supported features. In data path
skbs are checked for non-zero gso_size, and when detected sent
to additional function for processing TSO SKBs. Since HW does not
fully support TSO, additional effort is required from the driver.
Driver partitions the data into mss sized descriptors which are
then DMAed to the HW.
Signed-off-by: Vladimir Shulman <[email protected]>
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/netdev.c | 4 +-
drivers/net/wireless/ath/wil6210/txrx.c | 380 +++++++++++++++++++++++++++++-
drivers/net/wireless/ath/wil6210/txrx.h | 8 +
3 files changed, 379 insertions(+), 13 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/netdev.c b/drivers/net/wireless/ath/wil6210/netdev.c
index 8ef18ac..25c5116 100644
--- a/drivers/net/wireless/ath/wil6210/netdev.c
+++ b/drivers/net/wireless/ath/wil6210/netdev.c
@@ -173,7 +173,9 @@ void *wil_if_alloc(struct device *dev)
wil_set_ethtoolops(ndev);
ndev->ieee80211_ptr = wdev;
ndev->hw_features = NETIF_F_HW_CSUM | NETIF_F_RXCSUM |
- NETIF_F_SG | NETIF_F_GRO;
+ NETIF_F_SG | NETIF_F_GRO |
+ NETIF_F_TSO | NETIF_F_TSO6;
+
ndev->features |= ndev->hw_features;
SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy));
wdev->netdev = ndev;
diff --git a/drivers/net/wireless/ath/wil6210/txrx.c b/drivers/net/wireless/ath/wil6210/txrx.c
index 112192f1..8a2f2b6 100644
--- a/drivers/net/wireless/ath/wil6210/txrx.c
+++ b/drivers/net/wireless/ath/wil6210/txrx.c
@@ -1061,15 +1061,23 @@ static int wil_tx_desc_map(struct vring_tx_desc *d, dma_addr_t pa, u32 len,
static inline
void wil_tx_desc_set_nr_frags(struct vring_tx_desc *d, int nr_frags)
{
- d->mac.d[2] |= ((nr_frags + 1) <<
- MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_POS);
+ d->mac.d[2] |= (nr_frags << MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_POS);
}
-static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
- struct vring_tx_desc *d,
- struct sk_buff *skb)
-{
+/**
+ * Sets the descriptor @d up for csum and/or TSO offloading. The corresponding
+ * @skb is used to obtain the protocol and headers length.
+ * @tso_desc_type is a descriptor type for TSO: -1 - no TSO send,
+ * 0 - a header, 1 - first data, 2 - middle, 3 - last descriptor.
+ * Returns the protocol: 0 - not TCP, 1 - TCPv4, 2 - TCPv6.
+ * Note, if d==NULL, the function only returns the protocol result.
+ */
+
+static int wil_tx_desc_offload_setup_tso(struct vring_tx_desc *d,
+ struct sk_buff *skb,
+ int tso_desc_type) {
int protocol;
+ int is_ip4 = 0;
if (skb->ip_summed != CHECKSUM_PARTIAL)
return 0;
@@ -1080,6 +1088,7 @@ static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
case cpu_to_be16(ETH_P_IP):
protocol = ip_hdr(skb)->protocol;
d->dma.b11 |= BIT(DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_POS);
+ is_ip4 = 1;
break;
case cpu_to_be16(ETH_P_IPV6):
protocol = ipv6_hdr(skb)->nexthdr;
@@ -1094,6 +1103,13 @@ static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
/* L4 header len: TCP header length */
d->dma.d0 |=
(tcp_hdrlen(skb) & DMA_CFG_DESC_TX_0_L4_LENGTH_MSK);
+
+ /* Setup TSO: bit and desc type */
+ d->dma.d0 |= (BIT(DMA_CFG_DESC_TX_0_TCP_SEG_EN_POS)) |
+ (tso_desc_type <<
+ DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_POS);
+ d->dma.d0 |= (is_ip4 <<
+ DMA_CFG_DESC_TX_0_IPV4_CHECKSUM_EN_POS);
break;
case IPPROTO_UDP:
/* L4 header len: UDP header length */
@@ -1113,6 +1129,334 @@ static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
return 0;
}
+/**
+ * Sets the descriptor @d up for csum. The corresponding
+ * @skb is used to obtain the protocol and headers length.
+ * Returns the protocol: 0 - not TCP, 1 - TCPv4, 2 - TCPv6.
+ * Note, if d==NULL, the function only returns the protocol result.
+ *
+ * It is very similar to previous wil_tx_desc_offload_setup_tso. This
+ * is "if unrolling" to optimize the critical path.
+ */
+
+static int wil_tx_desc_offload_setup(struct vring_tx_desc *d,
+ struct sk_buff *skb){
+ int protocol;
+
+ if (skb->ip_summed != CHECKSUM_PARTIAL)
+ return 0;
+
+ d->dma.b11 = ETH_HLEN; /* MAC header length */
+
+ switch (skb->protocol) {
+ case cpu_to_be16(ETH_P_IP):
+ protocol = ip_hdr(skb)->protocol;
+ d->dma.b11 |= BIT(DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_POS);
+ break;
+ case cpu_to_be16(ETH_P_IPV6):
+ protocol = ipv6_hdr(skb)->nexthdr;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (protocol) {
+ case IPPROTO_TCP:
+ d->dma.d0 |= (2 << DMA_CFG_DESC_TX_0_L4_TYPE_POS);
+ /* L4 header len: TCP header length */
+ d->dma.d0 |=
+ (tcp_hdrlen(skb) & DMA_CFG_DESC_TX_0_L4_LENGTH_MSK);
+ break;
+ case IPPROTO_UDP:
+ /* L4 header len: UDP header length */
+ d->dma.d0 |=
+ (sizeof(struct udphdr) & DMA_CFG_DESC_TX_0_L4_LENGTH_MSK);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ d->dma.ip_length = skb_network_header_len(skb);
+ /* Enable TCP/UDP checksum */
+ d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_TCP_UDP_CHECKSUM_EN_POS);
+ /* Calculate pseudo-header */
+ d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_PSEUDO_HEADER_CALC_EN_POS);
+
+ return 0;
+}
+
+static inline void wil_tx_last_desc(struct vring_tx_desc *d)
+{
+ d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_CMD_EOP_POS) |
+ BIT(DMA_CFG_DESC_TX_0_CMD_MARK_WB_POS) |
+ BIT(DMA_CFG_DESC_TX_0_CMD_DMA_IT_POS);
+}
+
+static inline void wil_set_tx_desc_last_tso(volatile struct vring_tx_desc *d)
+{
+ d->dma.d0 |= wil_tso_type_lst <<
+ DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_POS;
+}
+
+static int __wil_tx_vring_tso(struct wil6210_priv *wil, struct vring *vring,
+ struct sk_buff *skb)
+{
+ struct device *dev = wil_to_dev(wil);
+
+ /* point to descriptors in shared memory */
+ volatile struct vring_tx_desc *_desc = NULL, *_hdr_desc,
+ *_first_desc = NULL;
+
+ /* pointers to shadow descriptors */
+ struct vring_tx_desc desc_mem, hdr_desc_mem, first_desc_mem,
+ *d = &hdr_desc_mem, *hdr_desc = &hdr_desc_mem,
+ *first_desc = &first_desc_mem;
+
+ /* pointer to shadow descriptors' context */
+ struct wil_ctx *hdr_ctx, *first_ctx = NULL;
+
+ int descs_used = 0; /* total number of used descriptors */
+ int sg_desc_cnt = 0; /* number of descriptors for current mss*/
+
+ u32 swhead = vring->swhead;
+ int used, avail = wil_vring_avail_tx(vring);
+ int nr_frags = skb_shinfo(skb)->nr_frags;
+ int min_desc_required = nr_frags + 1;
+ int mss = skb_shinfo(skb)->gso_size; /* payload size w/o headers */
+ int f, len, hdrlen, headlen;
+ int vring_index = vring - wil->vring_tx;
+ struct vring_tx_data *txdata = &wil->vring_tx_data[vring_index];
+ uint i = swhead;
+ dma_addr_t pa;
+ const skb_frag_t *frag = NULL;
+ int rem_data = mss;
+ int lenmss;
+ int hdr_compensation_need = true;
+ int desc_tso_type = wil_tso_type_first;
+
+ wil_dbg_txrx(wil, "%s() %d bytes to vring %d\n",
+ __func__, skb->len, vring_index);
+
+ if (unlikely(!txdata->enabled))
+ return -EINVAL;
+
+ /* A typical page 4K is 3-4 payloads, we assume each fragment
+ * is a full payload, that's how min_desc_required has been
+ * calculated. In real we might need more or less descriptors,
+ * this is the initial check only.
+ */
+ if (unlikely(avail < min_desc_required)) {
+ wil_err_ratelimited(wil,
+ "TSO: Tx ring[%2d] full. No space for %d fragments\n",
+ vring_index, min_desc_required);
+ return -ENOMEM;
+ }
+
+ /* Header Length = MAC header len + IP header len + TCP header len */
+ hdrlen = ETH_HLEN +
+ (int)skb_network_header_len(skb) +
+ tcp_hdrlen(skb);
+
+ if (skb->protocol == cpu_to_be16(ETH_P_IP)) {
+ /* TCP v4, zero out the IP length and IPv4 checksum fields
+ * as required by the offloading doc
+ */
+ ip_hdr(skb)->tot_len = 0;
+ ip_hdr(skb)->check = 0;
+ } else {
+ /* TCP v6, zero out the payload length */
+ ipv6_hdr(skb)->payload_len = 0;
+ }
+
+ _hdr_desc = &vring->va[i].tx;
+
+ pa = dma_map_single(dev, skb->data, hdrlen, DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dev, pa))) {
+ wil_err(wil, "TSO: Skb head DMA map error\n");
+ goto err_exit;
+ }
+
+ wil_tx_desc_map(hdr_desc, pa, hdrlen, vring_index);
+ wil_tx_desc_offload_setup_tso(hdr_desc, skb, wil_tso_type_hdr);
+ wil_tx_last_desc(hdr_desc);
+
+ vring->ctx[i].mapped_as = wil_mapped_as_single;
+ hdr_ctx = &vring->ctx[i];
+
+ descs_used++;
+ headlen = skb_headlen(skb) - hdrlen;
+
+ for (f = headlen ? -1 : 0; f < nr_frags; f++) {
+ if (headlen) {
+ len = headlen;
+ wil_dbg_txrx(wil, "TSO: process skb head, len %u\n",
+ len);
+ } else {
+ frag = &skb_shinfo(skb)->frags[f];
+ len = frag->size;
+ wil_dbg_txrx(wil, "TSO: frag[%d]: len %u\n", f, len);
+ }
+
+ while (len) {
+ wil_dbg_txrx(wil,
+ "TSO: len %d, rem_data %d, descs_used %d\n",
+ len, rem_data, descs_used);
+
+ if (descs_used == avail) {
+ wil_err(wil, "TSO: ring overflow\n");
+ goto dma_error;
+ }
+
+ lenmss = min_t(int, rem_data, len);
+ i = (swhead + descs_used) % vring->size;
+ wil_dbg_txrx(wil, "TSO: lenmss %d, i %d\n", lenmss, i);
+
+ if (!headlen) {
+ pa = skb_frag_dma_map(dev, frag,
+ frag->size - len, lenmss,
+ DMA_TO_DEVICE);
+ vring->ctx[i].mapped_as = wil_mapped_as_page;
+ } else {
+ pa = dma_map_single(dev,
+ skb->data +
+ skb_headlen(skb) - headlen,
+ lenmss,
+ DMA_TO_DEVICE);
+ vring->ctx[i].mapped_as = wil_mapped_as_single;
+ headlen -= lenmss;
+ }
+
+ if (unlikely(dma_mapping_error(dev, pa)))
+ goto dma_error;
+
+ _desc = &vring->va[i].tx;
+
+ if (!_first_desc) {
+ _first_desc = _desc;
+ first_ctx = &vring->ctx[i];
+ d = first_desc;
+ } else {
+ d = &desc_mem;
+ }
+
+ wil_tx_desc_map(d, pa, lenmss, vring_index);
+ wil_tx_desc_offload_setup_tso(d, skb, desc_tso_type);
+
+ /* use tso_type_first only once */
+ desc_tso_type = wil_tso_type_mid;
+
+ descs_used++; /* desc used so far */
+ sg_desc_cnt++; /* desc used for this segment */
+ len -= lenmss;
+ rem_data -= lenmss;
+
+ wil_dbg_txrx(wil,
+ "TSO: len %d, rem_data %d, descs_used %d, sg_desc_cnt %d,\n",
+ len, rem_data, descs_used, sg_desc_cnt);
+
+ /* Close the segment if reached mss size or last frag*/
+ if (rem_data == 0 || (f == nr_frags - 1 && len == 0)) {
+ if (hdr_compensation_need) {
+ /* first segment include hdr desc for
+ * release
+ */
+ hdr_ctx->nr_frags = sg_desc_cnt;
+ wil_tx_desc_set_nr_frags(first_desc,
+ sg_desc_cnt +
+ 1);
+ hdr_compensation_need = false;
+ } else {
+ wil_tx_desc_set_nr_frags(first_desc,
+ sg_desc_cnt);
+ }
+ first_ctx->nr_frags = sg_desc_cnt - 1;
+
+ wil_tx_last_desc(d);
+
+ /* first descriptor may also be the last
+ * for this mss - make sure not to copy
+ * it twice
+ */
+ if (first_desc != d)
+ *_first_desc = *first_desc;
+
+ /*last descriptor will be copied at the end
+ * of this TS processing
+ */
+ if (f < nr_frags - 1 || len > 0)
+ *_desc = *d;
+
+ rem_data = mss;
+ _first_desc = NULL;
+ sg_desc_cnt = 0;
+ } else if (first_desc != d) /* update mid descriptor */
+ *_desc = *d;
+ }
+ }
+
+ /* first descriptor may also be the last.
+ * in this case d pointer is invalid
+ */
+ if (_first_desc == _desc)
+ d = first_desc;
+
+ /* Last data descriptor */
+ wil_set_tx_desc_last_tso(d);
+ *_desc = *d;
+
+ /* Fill the total number of descriptors in first desc (hdr)*/
+ wil_tx_desc_set_nr_frags(hdr_desc, descs_used);
+ *_hdr_desc = *hdr_desc;
+
+ /* hold reference to skb
+ * to prevent skb release before accounting
+ * in case of immediate "tx done"
+ */
+ vring->ctx[i].skb = skb_get(skb);
+
+ /* performance monitoring */
+ used = wil_vring_used_tx(vring);
+ if (wil_val_in_range(vring_idle_trsh,
+ used, used + descs_used)) {
+ txdata->idle += get_cycles() - txdata->last_idle;
+ wil_dbg_txrx(wil, "Ring[%2d] not idle %d -> %d\n",
+ vring_index, used, used + descs_used);
+ }
+
+ /* advance swhead */
+ wil_dbg_txrx(wil, "TSO: Tx swhead %d -> %d\n", swhead, vring->swhead);
+ wil_vring_advance_head(vring, descs_used);
+
+ /* make sure all writes to descriptors (shared memory) are done before
+ * committing them to HW
+ */
+ wmb();
+
+ iowrite32(vring->swhead, wil->csr + HOSTADDR(vring->hwtail));
+ return 0;
+
+dma_error:
+ wil_err(wil, "TSO: DMA map page error\n");
+ while (descs_used > 0) {
+ struct wil_ctx *ctx;
+
+ i = (swhead + descs_used) % vring->size;
+ d = (struct vring_tx_desc *)&vring->va[i].tx;
+ _desc = &vring->va[i].tx;
+ *d = *_desc;
+ _desc->dma.status = TX_DMA_STATUS_DU;
+ ctx = &vring->ctx[i];
+ wil_txdesc_unmap(dev, d, ctx);
+ if (ctx->skb)
+ dev_kfree_skb_any(ctx->skb);
+ memset(ctx, 0, sizeof(*ctx));
+ descs_used--;
+ }
+
+err_exit:
+ return -EINVAL;
+}
+
static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
struct sk_buff *skb)
{
@@ -1131,7 +1475,8 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
bool mcast = (vring_index == wil->bcast_vring);
uint len = skb_headlen(skb);
- wil_dbg_txrx(wil, "%s()\n", __func__);
+ wil_dbg_txrx(wil, "%s() %d bytes to vring %d\n",
+ __func__, skb->len, vring_index);
if (unlikely(!txdata->enabled))
return -EINVAL;
@@ -1162,14 +1507,14 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
d->mac.d[0] |= (1 << MAC_CFG_DESC_TX_0_MCS_INDEX_POS);
}
/* Process TCP/UDP checksum offloading */
- if (unlikely(wil_tx_desc_offload_cksum_set(wil, d, skb))) {
+ if (unlikely(wil_tx_desc_offload_setup(d, skb))) {
wil_err(wil, "Tx[%2d] Failed to set cksum, drop packet\n",
vring_index);
goto dma_error;
}
vring->ctx[i].nr_frags = nr_frags;
- wil_tx_desc_set_nr_frags(d, nr_frags);
+ wil_tx_desc_set_nr_frags(d, nr_frags + 1);
/* middle segments */
for (; f < nr_frags; f++) {
@@ -1193,7 +1538,7 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
* if it succeeded for 1-st descriptor,
* it will succeed here too
*/
- wil_tx_desc_offload_cksum_set(wil, d, skb);
+ wil_tx_desc_offload_setup(d, skb);
}
/* for the last seg only */
d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_CMD_EOP_POS);
@@ -1224,6 +1569,12 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
wil_dbg_txrx(wil, "Tx[%2d] swhead %d -> %d\n", vring_index, swhead,
vring->swhead);
trace_wil6210_tx(vring_index, swhead, skb->len, nr_frags);
+
+ /* make sure all writes to descriptors (shared memory) are done before
+ * committing them to HW
+ */
+ wmb();
+
iowrite32(vring->swhead, wil->csr + HOSTADDR(vring->hwtail));
return 0;
@@ -1257,8 +1608,12 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
int rc;
spin_lock(&txdata->lock);
- rc = __wil_tx_vring(wil, vring, skb);
+
+ rc = (skb_is_gso(skb) ? __wil_tx_vring_tso : __wil_tx_vring)
+ (wil, vring, skb);
+
spin_unlock(&txdata->lock);
+
return rc;
}
@@ -1385,7 +1740,8 @@ int wil_tx_complete(struct wil6210_priv *wil, int ringid)
struct wil_ctx *ctx = &vring->ctx[vring->swtail];
/**
* For the fragmented skb, HW will set DU bit only for the
- * last fragment. look for it
+ * last fragment. look for it.
+ * In TSO the first DU will include hdr desc
*/
int lf = (vring->swtail + ctx->nr_frags) % vring->size;
/* TODO: check we are not past head */
diff --git a/drivers/net/wireless/ath/wil6210/txrx.h b/drivers/net/wireless/ath/wil6210/txrx.h
index 0c46384..82a8f9a 100644
--- a/drivers/net/wireless/ath/wil6210/txrx.h
+++ b/drivers/net/wireless/ath/wil6210/txrx.h
@@ -291,6 +291,14 @@ struct vring_tx_dma {
__le16 length;
} __packed;
+/* TSO type used in dma descriptor d0 bits 11-12 */
+enum {
+ wil_tso_type_hdr = 0,
+ wil_tso_type_first = 1,
+ wil_tso_type_mid = 2,
+ wil_tso_type_lst = 3,
+};
+
/* Rx descriptor - MAC part
* [dword 0]
* bit 0.. 3 : tid:4 The QoS (b3-0) TID Field
--
2.1.4
On Thu, Jul 9, 2015 at 2:37 PM, Vladimir Kondratiev
<[email protected]> wrote:
> On Wednesday, July 08, 2015 10:06:37 PM Emmanuel Grumbach wrote:
>> So your device is able to replicate and update the IP / TCP header?
>> I don't really follow what your device is able to do.
>> You seem to be cutting the frags so that their length sums up to mss.
>> Which hints that your device can't segment the buffer by itself. OTOH,
>> I don't see how you treat the IP / TCP header copy and modification.
>>
> Emmanuel:
> Yes, it is correct - hardware know to replicate IP/TCP header; and
> DMA written in such a way that I have to arrange fragments to
> sums up to MSS.
Ok - thanks for clarifying this.
>
> So this code fragment is OK. We tested it with lots of traffic.
> However, after your comments for another code fragment,
> I found there is a way to do it better. I'll rework and send updated patch.
>
I am working on something similar but our hardware does nothing for
us. I hope to send my version next week as an RFC.
> Kalle: please drop this patch. The rest of patches should apply cleanly
> without this one.
>
> Thanks, Vladimir
In the debugfs, there is "ulong" attribute printing. It is used for
bitmap printing, and more appropriate format would be hexadecimal,
not decimal.
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/debugfs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c
index 9ed1a94..4cdc5e2 100644
--- a/drivers/net/wireless/ath/wil6210/debugfs.c
+++ b/drivers/net/wireless/ath/wil6210/debugfs.c
@@ -306,7 +306,7 @@ static int wil_debugfs_ulong_get(void *data, u64 *val)
}
DEFINE_SIMPLE_ATTRIBUTE(wil_fops_ulong, wil_debugfs_ulong_get,
- wil_debugfs_ulong_set, "%llu\n");
+ wil_debugfs_ulong_set, "0x%llx\n");
static struct dentry *wil_debugfs_create_ulong(const char *name, umode_t mode,
struct dentry *parent,
--
2.1.4
linux/device.h should be included using <>, not ""
since it is not local include
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/wil_platform.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/wil_platform.c b/drivers/net/wireless/ath/wil6210/wil_platform.c
index de15f14..2e831bf 100644
--- a/drivers/net/wireless/ath/wil6210/wil_platform.c
+++ b/drivers/net/wireless/ath/wil6210/wil_platform.c
@@ -14,7 +14,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include "linux/device.h"
+#include <linux/device.h>
#include "wil_platform.h"
int __init wil_platform_modinit(void)
--
2.1.4
ACS is implemented by registering to QCA vendor ACS sub command.
On receive of the command from hostapd, driver issues passive
scan command and block until scan results are available.
When received, the results are sent up using QCA vendor ACS
results event
Signed-off-by: Vladimir Shulman <[email protected]>
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/cfg80211.c | 389 ++++++++++++++++++++++++++++
drivers/net/wireless/ath/wil6210/wil6210.h | 6 +
drivers/net/wireless/ath/wil6210/wmi.c | 44 +++-
drivers/net/wireless/ath/wil6210/wmi.h | 38 ++-
4 files changed, 464 insertions(+), 13 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c
index e4be2d9..5f05388 100644
--- a/drivers/net/wireless/ath/wil6210/cfg80211.c
+++ b/drivers/net/wireless/ath/wil6210/cfg80211.c
@@ -14,10 +14,28 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <linux/moduleparam.h>
#include <linux/etherdevice.h>
+#include <net/netlink.h>
#include "wil6210.h"
#include "wmi.h"
+static unsigned short scan_dwell_time = WMI_SCAN_DWELL_TIME_MS;
+module_param(scan_dwell_time, ushort, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(scan_dwell_time, " Scan dwell time");
+
+static unsigned short acs_ch_weight[4] = {120, 100, 100, 100};
+module_param_array(acs_ch_weight, ushort, NULL, 0);
+MODULE_PARM_DESC(acs_ch_weight, " Channel weight in %. This is channel priority for ACS");
+
+/* in case of channels' noise values all zero, applying weights will not work.
+ * to avoid such a case, we will add some small positive value to
+ * all channels' noise calculation
+ */
+#define ACS_CH_NOISE_INIT_VAL (100)
+
+#define ACS_DEFAULT_BEST_CHANNEL 2
+
#define CHAN60G(_channel, _flags) { \
.band = IEEE80211_BAND_60GHZ, \
.center_freq = 56160 + (2160 * (_channel)), \
@@ -34,6 +52,72 @@ static struct ieee80211_channel wil_60ghz_channels[] = {
/* channel 4 not supported yet */
};
+/* Vendor id to be used in vendor specific command and events
+ * to user space.
+ * NOTE: The authoritative place for definition of QCA_NL80211_VENDOR_ID,
+ * vendor subcmd definitions prefixed with QCA_NL80211_VENDOR_SUBCMD, and
+ * qca_wlan_vendor_attr is open source file src/common/qca-vendor.h in
+ * git://w1.fi/srv/git/hostap.git; the values here are just a copy of that
+ */
+
+#define QCA_NL80211_VENDOR_ID 0x001374
+
+enum qca_wlan_vendor_attr_acs_offload {
+ QCA_WLAN_VENDOR_ATTR_ACS_CHANNEL_INVALID = 0,
+ QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL,
+ QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL,
+ QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE,
+ QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED,
+ QCA_WLAN_VENDOR_ATTR_ACS_HT40_ENABLED,
+ /* keep last */
+ QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST,
+ QCA_WLAN_VENDOR_ATTR_ACS_MAX =
+ QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST - 1
+};
+
+enum qca_wlan_vendor_acs_hw_mode {
+ QCA_ACS_MODE_IEEE80211B,
+ QCA_ACS_MODE_IEEE80211G,
+ QCA_ACS_MODE_IEEE80211A,
+ QCA_ACS_MODE_IEEE80211AD,
+};
+
+static const struct
+nla_policy qca_wlan_acs_vendor_attr[QCA_WLAN_VENDOR_ATTR_ACS_MAX + 1] = {
+ [QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE] = { .type = NLA_U8 },
+};
+
+enum qca_nl80211_vendor_subcmds {
+ QCA_NL80211_VENDOR_SUBCMD_DO_ACS = 54,
+};
+
+enum qca_nl80211_vendor_subcmds_index {
+ QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX,
+};
+
+static int wil_do_acs(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len);
+
+/* vendor specific commands */
+static const struct wiphy_vendor_command wil_nl80211_vendor_commands[] = {
+ [QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX] = {
+ .info.vendor_id = QCA_NL80211_VENDOR_ID,
+ .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_DO_ACS,
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+ WIPHY_VENDOR_CMD_NEED_NETDEV |
+ WIPHY_VENDOR_CMD_NEED_RUNNING,
+ .doit = wil_do_acs
+ },
+};
+
+/* vendor specific events */
+static const struct nl80211_vendor_cmd_info wil_nl80211_vendor_events[] = {
+ [QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX] = {
+ .vendor_id = QCA_NL80211_VENDOR_ID,
+ .subcmd = QCA_NL80211_VENDOR_SUBCMD_DO_ACS
+ },
+};
+
static struct ieee80211_supported_band wil_band_60ghz = {
.channels = wil_60ghz_channels,
.n_channels = ARRAY_SIZE(wil_60ghz_channels),
@@ -1140,6 +1224,11 @@ static void wil_wiphy_init(struct wiphy *wiphy)
wiphy->n_cipher_suites = ARRAY_SIZE(wil_cipher_suites);
wiphy->mgmt_stypes = wil_mgmt_stypes;
wiphy->features |= NL80211_FEATURE_SK_TX_STATUS;
+
+ wiphy->n_vendor_commands = ARRAY_SIZE(wil_nl80211_vendor_commands);
+ wiphy->vendor_commands = wil_nl80211_vendor_commands;
+ wiphy->vendor_events = wil_nl80211_vendor_events;
+ wiphy->n_vendor_events = ARRAY_SIZE(wil_nl80211_vendor_events);
}
struct wireless_dev *wil_cfg80211_init(struct device *dev)
@@ -1190,3 +1279,303 @@ void wil_wdev_free(struct wil6210_priv *wil)
wiphy_free(wdev->wiphy);
kfree(wdev);
}
+
+static int wil_start_acs_survey(struct wil6210_priv *wil, uint dwell_time,
+ struct ieee80211_channel *channels,
+ u8 num_channels)
+{
+ int rc, i;
+ unsigned long to = msecs_to_jiffies(WMI_SURVEY_TIMEOUT_MS);
+ struct {
+ struct wmi_start_scan_cmd cmd;
+ struct {
+ u8 channel;
+ u8 reserved;
+ } channel_list[4];
+ } __packed scan_cmd = {
+ .cmd = {
+ .scan_type = WMI_PASSIVE_SCAN,
+ .dwell_time = cpu_to_le32(dwell_time),
+ .num_channels = min_t(u8, num_channels,
+ ARRAY_SIZE(wil_60ghz_channels)),
+ },
+ };
+
+ wil->survey_ready = false;
+ memset(&wil->survey_reply, 0, sizeof(wil->survey_reply));
+
+ for (i = 0; i < scan_cmd.cmd.num_channels; i++) {
+ u8 ch = channels[i].hw_value;
+
+ if (ch == 0) {
+ wil_err(wil, "ACS requested for wrong channel\n");
+ return -EINVAL;
+ }
+ wil_dbg_misc(wil, "ACS channel %d : %d MHz\n",
+ ch, channels[i].center_freq);
+ scan_cmd.channel_list[i].channel = ch - 1;
+ }
+
+ /* send scan command with the requested channel and wait
+ * for results
+ */
+ rc = wmi_send(wil, WMI_START_SCAN_CMDID, &scan_cmd, sizeof(scan_cmd));
+ if (rc) {
+ wil_err(wil, "ACS passive Scan failed (0x%08x)\n", rc);
+ return rc;
+ }
+
+ if (wait_event_interruptible_timeout(wil->wq, wil->survey_ready, to)
+ < 0) {
+ wil_err(wil, "%s: ACS survey interrupted\n", __func__);
+ return -ERESTARTSYS;
+ }
+
+ if (!wil->survey_ready) {
+ wil_err(wil, "ACS survey time out\n");
+ return -ETIME;
+ }
+
+ /* The results in survey_reply */
+ wil_dbg_misc(wil, "ACS scan success, filled mask: 0x%08X\n",
+ le16_to_cpu(wil->survey_reply.evt.filled));
+
+ return 0;
+}
+
+static u8 wil_acs_calc_channel(struct wil6210_priv *wil)
+{
+ int i, best_channel = ACS_DEFAULT_BEST_CHANNEL - 1;
+ struct scan_channel_info *ch;
+ u64 dwell_time = le32_to_cpu(wil->survey_reply.evt.dwell_time);
+ u16 filled = le16_to_cpu(wil->survey_reply.evt.filled);
+ u8 num_channels = wil->survey_reply.evt.num_scanned_channels;
+ u64 busy_time, tx_time;
+ u64 min_i_ch = (u64)-1, cur_i_ch;
+ u8 p_min = 0, ch_noise;
+
+ wil_dbg_misc(wil, "%s: filled info: 0x%04X, for %u channels\n",
+ __func__, filled, num_channels);
+
+ if (!num_channels) {
+ wil_err(wil, "%s: received results with no channel info\n",
+ __func__);
+ return 0;
+ }
+
+ /* find P_min */
+ if (filled & WMI_ACS_INFO_BITMASK_NOISE) {
+ p_min = wil->survey_reply.ch_info[0].noise;
+
+ for (i = 1; i < num_channels; i++)
+ p_min = min(p_min, wil->survey_reply.ch_info[i].noise);
+ }
+
+ wil_dbg_misc(wil, "%s: p_min is %u\n", __func__, p_min);
+
+ /* Choosing channel according to the following formula:
+ * 16 bit fixed point math
+ * I_ch = { [ (T_busy - T_tx) << 16 ] /
+ * (T_dwell - T_tx) } * 2^(P_rx - P_min)
+ */
+ for (i = 0; i < num_channels; i++) {
+ ch = &wil->survey_reply.ch_info[i];
+
+ if (ch->channel > 3) {
+ wil_err(wil,
+ "%s: invalid channel number %d\n",
+ __func__, ch->channel + 1);
+ continue;
+ }
+
+ busy_time = filled & WMI_ACS_INFO_BITMASK_BUSY_TIME ?
+ le16_to_cpu(ch->busy_time) : 0;
+
+ tx_time = filled & WMI_ACS_INFO_BITMASK_TX_TIME ?
+ le16_to_cpu(ch->tx_time) : 0;
+
+ ch_noise = filled & WMI_ACS_INFO_BITMASK_NOISE ? ch->noise : 0;
+
+ wil_dbg_misc(wil,
+ "%s: Ch[%d]: busy %llu, tx %llu, noise %u, dwell %llu\n",
+ __func__, ch->channel + 1, busy_time, tx_time,
+ ch_noise, dwell_time);
+
+ if (dwell_time == tx_time) {
+ wil_err(wil,
+ "%s: Ch[%d] dwell_time == tx_time: %llu\n",
+ __func__, ch->channel + 1, dwell_time);
+ continue;
+ }
+
+ cur_i_ch = (busy_time - tx_time) << 16;
+ do_div(cur_i_ch,
+ ((dwell_time - tx_time) << (ch_noise - p_min)));
+
+ /* Apply channel priority */
+ cur_i_ch = (cur_i_ch + ACS_CH_NOISE_INIT_VAL) *
+ acs_ch_weight[ch->channel];
+ do_div(cur_i_ch, 100);
+
+ wil_dbg_misc(wil, "%s: Ch[%d] w %u, I_ch %llu\n", __func__,
+ ch->channel + 1, acs_ch_weight[ch->channel],
+ cur_i_ch);
+
+ if (i == 0 || cur_i_ch < min_i_ch) {
+ min_i_ch = cur_i_ch;
+ best_channel = ch->channel;
+ }
+ }
+
+ wil_dbg_misc(wil, "%s: best channel %d with I_ch of %llu\n", __func__,
+ best_channel + 1, min_i_ch);
+
+ return best_channel;
+}
+
+static void wil_acs_report_channel(struct wil6210_priv *wil)
+{
+ struct sk_buff *vendor_event;
+ int ret_val;
+ struct nlattr *nla;
+ u8 channel = wil_acs_calc_channel(wil);
+
+ vendor_event = cfg80211_vendor_event_alloc(wil_to_wiphy(wil), NULL,
+ 2 + 4 + NLMSG_HDRLEN,
+ QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX,
+ GFP_KERNEL);
+ if (!vendor_event) {
+ wil_err(wil, "cfg80211_vendor_event_alloc failed\n");
+ return;
+ }
+
+ /* Send the IF INDEX to differentiate the ACS event for each interface
+ * TODO: To be update once cfg80211 APIs are updated to accept if_index
+ */
+ nla_nest_cancel(vendor_event, ((void **)vendor_event->cb)[2]);
+
+ ret_val = nla_put_u32(vendor_event, NL80211_ATTR_IFINDEX,
+ wil_to_ndev(wil)->ifindex);
+ if (ret_val) {
+ wil_err(wil, "NL80211_ATTR_IFINDEX put fail\n");
+ kfree_skb(vendor_event);
+ return;
+ }
+
+ nla = nla_nest_start(vendor_event, NL80211_ATTR_VENDOR_DATA);
+ ((void **)vendor_event->cb)[2] = nla;
+
+ /* channel indices used by fw are zero based and those used upper
+ * layers are 1 based: must add 1
+ */
+ ret_val = nla_put_u8(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL,
+ channel + 1);
+ if (ret_val) {
+ wil_err(wil,
+ "QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL put fail\n");
+ kfree_skb(vendor_event);
+ return;
+ }
+
+ /* must report secondary channel always, 0 is harmless */
+ ret_val = nla_put_u8(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL, 0);
+ if (ret_val) {
+ wil_err(wil,
+ "QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL put fail\n");
+ kfree_skb(vendor_event);
+ return;
+ }
+
+ cfg80211_vendor_event(vendor_event, GFP_KERNEL);
+}
+
+static int wil_do_acs(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len)
+{
+ struct wil6210_priv *wil = wdev_to_wil(wdev);
+ struct sk_buff *temp_skbuff;
+ int rc;
+ struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_ACS_MAX + 1];
+ u8 hw_mode;
+ struct ieee80211_channel reg_channels[ARRAY_SIZE(wil_60ghz_channels)];
+ int num_channels;
+ const struct ieee80211_reg_rule *reg_rule;
+ int i;
+
+ rc = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_ACS_MAX, data, data_len,
+ qca_wlan_acs_vendor_attr);
+ if (rc) {
+ wil_err(wil, "%s: Invalid ATTR\n", __func__);
+ goto out;
+ }
+
+ if (!tb[QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE]) {
+ wil_err(wil, "%s: Attr hw_mode failed\n", __func__);
+ goto out;
+ }
+
+ hw_mode = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE]);
+
+ if (hw_mode != QCA_ACS_MODE_IEEE80211AD) {
+ wil_err(wil, "%s: Illegal HW mode (%d), must be %d (11AD)\n",
+ __func__, hw_mode, QCA_ACS_MODE_IEEE80211AD);
+ goto out;
+ }
+
+ /* get list of channels allowed by regulatory */
+ num_channels = 0;
+ for (i = 0; i < ARRAY_SIZE(wil_60ghz_channels); i++) {
+ u32 ch_center_freq =
+ MHZ_TO_KHZ(wil_60ghz_channels[i].center_freq);
+ reg_rule = freq_reg_info(wiphy, ch_center_freq);
+ if (IS_ERR(reg_rule)) {
+ wil_dbg_misc(wil,
+ "%s: channel %d (%d) reg db err %ld\n",
+ __func__, wil_60ghz_channels[i].hw_value,
+ wil_60ghz_channels[i].center_freq,
+ PTR_ERR(reg_rule));
+ continue;
+ }
+
+ /* we assume if active scan allowed, we can use the
+ * channel to start AP on it
+ */
+ if (!(reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)) {
+ reg_channels[i] = wil_60ghz_channels[i];
+ num_channels++;
+ wil_dbg_misc(wil, "%s: Adding ch %d to ACS scan\n",
+ __func__, wil_60ghz_channels[i].hw_value);
+ } else {
+ wil_dbg_misc(wil,
+ "%s: channel %d (%d) can't be used: 0x%08X\n",
+ __func__, wil_60ghz_channels[i].hw_value,
+ wil_60ghz_channels[i].center_freq,
+ reg_rule->flags);
+ }
+ }
+
+ if (!num_channels) {
+ wil_err(wil,
+ "ACS aborted. Couldn't find channels allowed by regulatory\n");
+ rc = -EPERM;
+ goto out;
+ }
+
+ /* start acs survey*/
+ rc = wil_start_acs_survey(wil, scan_dwell_time, reg_channels,
+ num_channels);
+
+ if (!rc)
+ wil_acs_report_channel(wil);
+out:
+ if (0 == rc) {
+ temp_skbuff = cfg80211_vendor_cmd_alloc_reply_skb(wiphy,
+ NLMSG_HDRLEN);
+ if (temp_skbuff)
+ return cfg80211_vendor_cmd_reply(temp_skbuff);
+ }
+ return rc;
+}
+
diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h
index b79ba49..4b7135e 100644
--- a/drivers/net/wireless/ath/wil6210/wil6210.h
+++ b/drivers/net/wireless/ath/wil6210/wil6210.h
@@ -22,6 +22,7 @@
#include <net/cfg80211.h>
#include <linux/timex.h>
#include <linux/types.h>
+#include "wmi.h"
#include "wil_platform.h"
extern bool no_fw_recovery;
@@ -619,6 +620,11 @@ struct wil6210_priv {
struct wil_platform_ops platform_ops;
struct pmc_ctx pmc;
+ bool survey_ready;
+ struct {
+ struct wmi_acs_passive_scan_complete_event evt;
+ struct scan_channel_info ch_info[4];
+ } __packed survey_reply;
};
#define wil_to_wiphy(i) (i->wdev->wiphy)
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index c759759..12b5770 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -382,6 +382,27 @@ static void wmi_evt_scan_complete(struct wil6210_priv *wil, int id,
}
}
+static void wmi_evt_survey_complete(struct wil6210_priv *wil, int id,
+ void *d, int len)
+{
+ struct wmi_acs_passive_scan_complete_event *evt =
+ (struct wmi_acs_passive_scan_complete_event *)d;
+
+ int expected_size = sizeof(struct wmi_acs_passive_scan_complete_event) +
+ evt->num_scanned_channels *
+ sizeof(struct scan_channel_info);
+
+ if (len < expected_size) {
+ wil_err(wil, "Survey event size is too small (%d): expected %d channels\n",
+ len, evt->num_scanned_channels);
+ return;
+ }
+
+ memcpy(&wil->survey_reply, d, sizeof(wil->survey_reply));
+ wil->survey_ready = true;
+ wake_up_interruptible(&wil->wq);
+}
+
static void wmi_evt_connect(struct wil6210_priv *wil, int id, void *d, int len)
{
struct net_device *ndev = wil_to_ndev(wil);
@@ -656,17 +677,18 @@ static const struct {
void (*handler)(struct wil6210_priv *wil, int eventid,
void *data, int data_len);
} wmi_evt_handlers[] = {
- {WMI_READY_EVENTID, wmi_evt_ready},
- {WMI_FW_READY_EVENTID, wmi_evt_fw_ready},
- {WMI_RX_MGMT_PACKET_EVENTID, wmi_evt_rx_mgmt},
- {WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete},
- {WMI_CONNECT_EVENTID, wmi_evt_connect},
- {WMI_DISCONNECT_EVENTID, wmi_evt_disconnect},
- {WMI_EAPOL_RX_EVENTID, wmi_evt_eapol_rx},
- {WMI_BA_STATUS_EVENTID, wmi_evt_ba_status},
- {WMI_RCP_ADDBA_REQ_EVENTID, wmi_evt_addba_rx_req},
- {WMI_DELBA_EVENTID, wmi_evt_delba},
- {WMI_VRING_EN_EVENTID, wmi_evt_vring_en},
+ {WMI_READY_EVENTID, wmi_evt_ready},
+ {WMI_FW_READY_EVENTID, wmi_evt_fw_ready},
+ {WMI_RX_MGMT_PACKET_EVENTID, wmi_evt_rx_mgmt},
+ {WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete},
+ {WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID, wmi_evt_survey_complete},
+ {WMI_CONNECT_EVENTID, wmi_evt_connect},
+ {WMI_DISCONNECT_EVENTID, wmi_evt_disconnect},
+ {WMI_EAPOL_RX_EVENTID, wmi_evt_eapol_rx},
+ {WMI_BA_STATUS_EVENTID, wmi_evt_ba_status},
+ {WMI_RCP_ADDBA_REQ_EVENTID, wmi_evt_addba_rx_req},
+ {WMI_DELBA_EVENTID, wmi_evt_delba},
+ {WMI_VRING_EN_EVENTID, wmi_evt_vring_en},
};
/*
diff --git a/drivers/net/wireless/ath/wil6210/wmi.h b/drivers/net/wireless/ath/wil6210/wmi.h
index 6e90e78..b44f7ae 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.h
+++ b/drivers/net/wireless/ath/wil6210/wmi.h
@@ -33,6 +33,8 @@
#define WMI_MAC_LEN (6)
#define WMI_PROX_RANGE_NUM (3)
#define WMI_MAX_LOSS_DMG_BEACONS (32)
+#define WMI_SCAN_DWELL_TIME_MS (100)
+#define WMI_SURVEY_TIMEOUT_MS (10000)
/* List of Commands */
enum wmi_command_id {
@@ -288,7 +290,7 @@ struct wmi_delete_cipher_key_cmd {
enum wmi_scan_type {
WMI_LONG_SCAN = 0,
WMI_SHORT_SCAN = 1,
- WMI_PBC_SCAN = 2,
+ WMI_PASSIVE_SCAN = 2,
WMI_DIRECT_SCAN = 3,
WMI_ACTIVE_SCAN = 4,
};
@@ -296,7 +298,7 @@ enum wmi_scan_type {
struct wmi_start_scan_cmd {
u8 direct_scan_mac_addr[6];
u8 reserved[2];
- __le32 home_dwell_time; /* Max duration in the home channel(ms) */
+ __le32 dwell_time; /* duration in the home channel(ms) */
__le32 force_scan_interval; /* Time interval between scans (ms)*/
u8 scan_type; /* wmi_scan_type */
u8 num_channels; /* how many channels follow */
@@ -943,6 +945,7 @@ enum wmi_event_id {
WMI_EAPOL_RX_EVENTID = 0x9002,
WMI_MAC_ADDR_RESP_EVENTID = 0x9003,
WMI_FW_VER_EVENTID = 0x9004,
+ WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID = 0x9005,
};
/*
@@ -1127,6 +1130,37 @@ struct wmi_scan_complete_event {
} __packed;
/*
+ * WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENT
+ */
+enum wmi_acs_info_bitmask_e {
+ WMI_ACS_INFO_BITMASK_BEACON_FOUND = 0x01,
+ WMI_ACS_INFO_BITMASK_BUSY_TIME = 0x02,
+ WMI_ACS_INFO_BITMASK_TX_TIME = 0x04,
+ WMI_ACS_INFO_BITMASK_RX_TIME = 0x08,
+ WMI_ACS_INFO_BITMASK_NOISE = 0x10,
+};
+
+struct scan_channel_info {
+ u8 channel;
+ u8 beacon_found;
+ __le16 busy_time;
+ __le16 tx_time;
+ __le16 rx_time;
+ u8 noise;
+ u8 reserved[3];
+} __packed;
+
+struct wmi_acs_passive_scan_complete_event {
+ __le32 dwell_time;
+ __le16 filled; /* valid fields within channel info according to
+ * their appearance in struct order
+ */
+ u8 num_scanned_channels;
+ u8 reserved;
+ struct scan_channel_info scan_time_list[0];
+} __packed;
+
+/*
* WMI_BA_STATUS_EVENTID
*/
enum wmi_vring_ba_status {
--
2.1.4
In the debugfs, there is "ulong" attribute printing. It is used for
bitmap printing, and more appropriate format would be hexadecimal,
not decimal.
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/debugfs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c
index 9ed1a94..4cdc5e2 100644
--- a/drivers/net/wireless/ath/wil6210/debugfs.c
+++ b/drivers/net/wireless/ath/wil6210/debugfs.c
@@ -306,7 +306,7 @@ static int wil_debugfs_ulong_get(void *data, u64 *val)
}
DEFINE_SIMPLE_ATTRIBUTE(wil_fops_ulong, wil_debugfs_ulong_get,
- wil_debugfs_ulong_set, "%llu\n");
+ wil_debugfs_ulong_set, "0x%llx\n");
static struct dentry *wil_debugfs_create_ulong(const char *name, umode_t mode,
struct dentry *parent,
--
2.1.4
Multiple del_station requests may be sent to the driver by the
supplicant when turning down AP. This may overflow mailbox
between the FW and ucode
Wait till disconnect of one STA completed before sending next command.
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/wmi.c | 29 ++++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index 12b5770..0678fff 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -1151,15 +1151,42 @@ int wmi_get_temperature(struct wil6210_priv *wil, u32 *t_bb, u32 *t_rf)
int wmi_disconnect_sta(struct wil6210_priv *wil, const u8 *mac, u16 reason)
{
+ int rc;
+ u16 reason_code;
struct wmi_disconnect_sta_cmd cmd = {
.disconnect_reason = cpu_to_le16(reason),
};
+ struct {
+ struct wil6210_mbox_hdr_wmi wmi;
+ struct wmi_disconnect_event evt;
+ } __packed reply;
ether_addr_copy(cmd.dst_mac, mac);
wil_dbg_wmi(wil, "%s(%pM, reason %d)\n", __func__, mac, reason);
- return wmi_send(wil, WMI_DISCONNECT_STA_CMDID, &cmd, sizeof(cmd));
+ rc = wmi_call(wil, WMI_DISCONNECT_STA_CMDID, &cmd, sizeof(cmd),
+ WMI_DISCONNECT_EVENTID, &reply, sizeof(reply), 1000);
+ /* failure to disconnect in reasonable time treated as FW error */
+ if (rc) {
+ wil_fw_error_recovery(wil);
+ return rc;
+ }
+
+ /* call event handler manually after processing wmi_call,
+ * to avoid deadlock - disconnect event handler acquires wil->mutex
+ * while it is already held here
+ */
+ reason_code = le16_to_cpu(reply.evt.protocol_reason_status);
+
+ wil_dbg_wmi(wil, "Disconnect %pM reason [proto %d wmi %d]\n",
+ reply.evt.bssid, reason_code,
+ reply.evt.disconnect_reason);
+
+ wil->sinfo_gen++;
+ wil6210_disconnect(wil, reply.evt.bssid, reason_code, true);
+
+ return 0;
}
int wmi_addba(struct wil6210_priv *wil, u8 ringid, u8 size, u16 timeout)
--
2.1.4
Check event length;
hex dump both Rx and Tx frames
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/wmi.c | 54 +++++++++++++++++++++++++++-------
1 file changed, 44 insertions(+), 10 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index 0678fff..d3f75de 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -312,22 +312,44 @@ static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len)
struct wiphy *wiphy = wil_to_wiphy(wil);
struct ieee80211_mgmt *rx_mgmt_frame =
(struct ieee80211_mgmt *)data->payload;
- int ch_no = data->info.channel+1;
- u32 freq = ieee80211_channel_to_frequency(ch_no,
- IEEE80211_BAND_60GHZ);
- struct ieee80211_channel *channel = ieee80211_get_channel(wiphy, freq);
- s32 signal = data->info.sqi;
- __le16 fc = rx_mgmt_frame->frame_control;
- u32 d_len = le32_to_cpu(data->info.len);
- u16 d_status = le16_to_cpu(data->info.status);
-
- wil_dbg_wmi(wil, "MGMT: channel %d MCS %d SNR %d SQI %d%%\n",
+ int flen = len - offsetof(struct wmi_rx_mgmt_packet_event, payload);
+ int ch_no;
+ u32 freq;
+ struct ieee80211_channel *channel;
+ s32 signal;
+ __le16 fc;
+ u32 d_len;
+ u16 d_status;
+
+ if (flen < 0) {
+ wil_err(wil, "MGMT Rx: short event, len %d\n", len);
+ return;
+ }
+
+ d_len = le32_to_cpu(data->info.len);
+ if (d_len != flen) {
+ wil_err(wil,
+ "MGMT Rx: length mismatch, d_len %d should be %d\n",
+ d_len, flen);
+ return;
+ }
+
+ ch_no = data->info.channel + 1;
+ freq = ieee80211_channel_to_frequency(ch_no, IEEE80211_BAND_60GHZ);
+ channel = ieee80211_get_channel(wiphy, freq);
+ signal = data->info.sqi;
+ d_status = le16_to_cpu(data->info.status);
+ fc = rx_mgmt_frame->frame_control;
+
+ wil_dbg_wmi(wil, "MGMT Rx: channel %d MCS %d SNR %d SQI %d%%\n",
data->info.channel, data->info.mcs, data->info.snr,
data->info.sqi);
wil_dbg_wmi(wil, "status 0x%04x len %d fc 0x%04x\n", d_status, d_len,
le16_to_cpu(fc));
wil_dbg_wmi(wil, "qid %d mid %d cid %d\n",
data->info.qid, data->info.mid, data->info.cid);
+ wil_hex_dump_wmi("MGMT Rx ", DUMP_PREFIX_OFFSET, 16, 1, rx_mgmt_frame,
+ d_len, true);
if (!channel) {
wil_err(wil, "Frame on unsupported channel\n");
@@ -363,6 +385,17 @@ static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len)
}
}
+static void wmi_evt_tx_mgmt(struct wil6210_priv *wil, int id, void *d, int len)
+{
+ struct wmi_tx_mgmt_packet_event *data = d;
+ struct ieee80211_mgmt *mgmt_frame =
+ (struct ieee80211_mgmt *)data->payload;
+ int flen = len - offsetof(struct wmi_tx_mgmt_packet_event, payload);
+
+ wil_hex_dump_wmi("MGMT Tx ", DUMP_PREFIX_OFFSET, 16, 1, mgmt_frame,
+ flen, true);
+}
+
static void wmi_evt_scan_complete(struct wil6210_priv *wil, int id,
void *d, int len)
{
@@ -680,6 +713,7 @@ static const struct {
{WMI_READY_EVENTID, wmi_evt_ready},
{WMI_FW_READY_EVENTID, wmi_evt_fw_ready},
{WMI_RX_MGMT_PACKET_EVENTID, wmi_evt_rx_mgmt},
+ {WMI_TX_MGMT_PACKET_EVENTID, wmi_evt_tx_mgmt},
{WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete},
{WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID, wmi_evt_survey_complete},
{WMI_CONNECT_EVENTID, wmi_evt_connect},
--
2.1.4
When loading with debug_fw flag, do not bail out on
unknown chipId
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/main.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
index cdc82cf..0d64e73 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -746,9 +746,6 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw)
wil_dbg_misc(wil, "%s()\n", __func__);
- if (wil->hw_version == HW_VER_UNKNOWN)
- return -ENODEV;
-
WARN_ON(!mutex_is_locked(&wil->mutex));
WARN_ON(test_bit(wil_status_napi_en, wil->status));
@@ -763,6 +760,9 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw)
return 0;
}
+ if (wil->hw_version == HW_VER_UNKNOWN)
+ return -ENODEV;
+
cancel_work_sync(&wil->disconnect_worker);
wil6210_disconnect(wil, NULL, WLAN_REASON_DEAUTH_LEAVING, false);
wil_bcast_fini(wil);
--
2.1.4
On Sun, 2015-07-05 at 10:24 +0300, Vladimir Kondratiev wrote:
> ACS is implemented by registering to QCA vendor ACS sub command.
> On receive of the command from hostapd, driver issues passive
> scan command and block until scan results are available.
> When received, the results are sent up using QCA vendor ACS
> results event
>
I'm not convinced that we really want vendor commands upstream in the
kernel. There at least we could go the extra mile to make proper non
-vendor APIs, no?
johannes
When loading with debug_fw flag, do not bail out on
unknown chipId
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/main.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
index cdc82cf..0d64e73 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -746,9 +746,6 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw)
wil_dbg_misc(wil, "%s()\n", __func__);
- if (wil->hw_version == HW_VER_UNKNOWN)
- return -ENODEV;
-
WARN_ON(!mutex_is_locked(&wil->mutex));
WARN_ON(test_bit(wil_status_napi_en, wil->status));
@@ -763,6 +760,9 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw)
return 0;
}
+ if (wil->hw_version == HW_VER_UNKNOWN)
+ return -ENODEV;
+
cancel_work_sync(&wil->disconnect_worker);
wil6210_disconnect(wil, NULL, WLAN_REASON_DEAUTH_LEAVING, false);
wil_bcast_fini(wil);
--
2.1.4
When performing Rx reordering, count skb's dropped
per reorder buffer; and print dropped packets count
on the "stations" debugfs entry
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/debugfs.c | 2 +-
drivers/net/wireless/ath/wil6210/rx_reorder.c | 2 ++
drivers/net/wireless/ath/wil6210/wil6210.h | 2 ++
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c
index 42bc6e6..9ed1a94 100644
--- a/drivers/net/wireless/ath/wil6210/debugfs.c
+++ b/drivers/net/wireless/ath/wil6210/debugfs.c
@@ -1353,7 +1353,7 @@ static void wil_print_rxtid(struct seq_file *s, struct wil_tid_ampdu_rx *r)
else
seq_printf(s, "%c", r->reorder_buf[i] ? '*' : '_');
}
- seq_printf(s, "] last drop 0x%03x\n", r->ssn_last_drop);
+ seq_printf(s, "] drop %llu last 0x%03x\n", r->drop, r->ssn_last_drop);
}
static int wil_sta_debugfs_show(struct seq_file *s, void *data)
diff --git a/drivers/net/wireless/ath/wil6210/rx_reorder.c b/drivers/net/wireless/ath/wil6210/rx_reorder.c
index ca10dcf..e4ac11c 100644
--- a/drivers/net/wireless/ath/wil6210/rx_reorder.c
+++ b/drivers/net/wireless/ath/wil6210/rx_reorder.c
@@ -153,6 +153,7 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock)
/* frame with out of date sequence number */
if (seq_less(seq, r->head_seq_num)) {
r->ssn_last_drop = seq;
+ r->drop++;
dev_kfree_skb(skb);
goto out;
}
@@ -173,6 +174,7 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock)
/* check if we already stored this frame */
if (r->reorder_buf[index]) {
+ r->drop++;
dev_kfree_skb(skb);
goto out;
}
diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h
index 4b7135e..284edda 100644
--- a/drivers/net/wireless/ath/wil6210/wil6210.h
+++ b/drivers/net/wireless/ath/wil6210/wil6210.h
@@ -427,6 +427,7 @@ struct pci_dev;
* @timeout: reset timer value (in TUs).
* @dialog_token: dialog token for aggregation session
* @rcu_head: RCU head used for freeing this struct
+ * @drop: total frames dropped for this reorder buffer
*
* This structure's lifetime is managed by RCU, assignments to
* the array holding it must hold the aggregation mutex.
@@ -444,6 +445,7 @@ struct wil_tid_ampdu_rx {
u16 buf_size;
u16 timeout;
u16 ssn_last_drop;
+ unsigned long long drop;
u8 dialog_token;
bool first_time; /* is it 1-st time this buffer used? */
};
--
2.1.4
Use function wil_fw_error_recovery() instead of inline equivalent code
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/main.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
index 4740edb..cdc82cf 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -271,8 +271,7 @@ static void wil_scan_timer_fn(ulong x)
clear_bit(wil_status_fwready, wil->status);
wil_err(wil, "Scan timeout detected, start fw error recovery\n");
- wil->recovery_state = fw_recovery_pending;
- schedule_work(&wil->fw_error_worker);
+ wil_fw_error_recovery(wil);
}
static int wil_wait_for_recovery(struct wil6210_priv *wil)
--
2.1.4
ACS is implemented by registering to QCA vendor ACS sub command.
On receive of the command from hostapd, driver issues passive
scan command and block until scan results are available.
When received, the results are sent up using QCA vendor ACS
results event
Signed-off-by: Vladimir Shulman <[email protected]>
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/cfg80211.c | 389 ++++++++++++++++++++++++++++
drivers/net/wireless/ath/wil6210/wil6210.h | 6 +
drivers/net/wireless/ath/wil6210/wmi.c | 44 +++-
drivers/net/wireless/ath/wil6210/wmi.h | 38 ++-
4 files changed, 464 insertions(+), 13 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c
index e4be2d9..5f05388 100644
--- a/drivers/net/wireless/ath/wil6210/cfg80211.c
+++ b/drivers/net/wireless/ath/wil6210/cfg80211.c
@@ -14,10 +14,28 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <linux/moduleparam.h>
#include <linux/etherdevice.h>
+#include <net/netlink.h>
#include "wil6210.h"
#include "wmi.h"
+static unsigned short scan_dwell_time = WMI_SCAN_DWELL_TIME_MS;
+module_param(scan_dwell_time, ushort, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(scan_dwell_time, " Scan dwell time");
+
+static unsigned short acs_ch_weight[4] = {120, 100, 100, 100};
+module_param_array(acs_ch_weight, ushort, NULL, 0);
+MODULE_PARM_DESC(acs_ch_weight, " Channel weight in %. This is channel priority for ACS");
+
+/* in case of channels' noise values all zero, applying weights will not work.
+ * to avoid such a case, we will add some small positive value to
+ * all channels' noise calculation
+ */
+#define ACS_CH_NOISE_INIT_VAL (100)
+
+#define ACS_DEFAULT_BEST_CHANNEL 2
+
#define CHAN60G(_channel, _flags) { \
.band = IEEE80211_BAND_60GHZ, \
.center_freq = 56160 + (2160 * (_channel)), \
@@ -34,6 +52,72 @@ static struct ieee80211_channel wil_60ghz_channels[] = {
/* channel 4 not supported yet */
};
+/* Vendor id to be used in vendor specific command and events
+ * to user space.
+ * NOTE: The authoritative place for definition of QCA_NL80211_VENDOR_ID,
+ * vendor subcmd definitions prefixed with QCA_NL80211_VENDOR_SUBCMD, and
+ * qca_wlan_vendor_attr is open source file src/common/qca-vendor.h in
+ * git://w1.fi/srv/git/hostap.git; the values here are just a copy of that
+ */
+
+#define QCA_NL80211_VENDOR_ID 0x001374
+
+enum qca_wlan_vendor_attr_acs_offload {
+ QCA_WLAN_VENDOR_ATTR_ACS_CHANNEL_INVALID = 0,
+ QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL,
+ QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL,
+ QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE,
+ QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED,
+ QCA_WLAN_VENDOR_ATTR_ACS_HT40_ENABLED,
+ /* keep last */
+ QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST,
+ QCA_WLAN_VENDOR_ATTR_ACS_MAX =
+ QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST - 1
+};
+
+enum qca_wlan_vendor_acs_hw_mode {
+ QCA_ACS_MODE_IEEE80211B,
+ QCA_ACS_MODE_IEEE80211G,
+ QCA_ACS_MODE_IEEE80211A,
+ QCA_ACS_MODE_IEEE80211AD,
+};
+
+static const struct
+nla_policy qca_wlan_acs_vendor_attr[QCA_WLAN_VENDOR_ATTR_ACS_MAX + 1] = {
+ [QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE] = { .type = NLA_U8 },
+};
+
+enum qca_nl80211_vendor_subcmds {
+ QCA_NL80211_VENDOR_SUBCMD_DO_ACS = 54,
+};
+
+enum qca_nl80211_vendor_subcmds_index {
+ QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX,
+};
+
+static int wil_do_acs(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len);
+
+/* vendor specific commands */
+static const struct wiphy_vendor_command wil_nl80211_vendor_commands[] = {
+ [QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX] = {
+ .info.vendor_id = QCA_NL80211_VENDOR_ID,
+ .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_DO_ACS,
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+ WIPHY_VENDOR_CMD_NEED_NETDEV |
+ WIPHY_VENDOR_CMD_NEED_RUNNING,
+ .doit = wil_do_acs
+ },
+};
+
+/* vendor specific events */
+static const struct nl80211_vendor_cmd_info wil_nl80211_vendor_events[] = {
+ [QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX] = {
+ .vendor_id = QCA_NL80211_VENDOR_ID,
+ .subcmd = QCA_NL80211_VENDOR_SUBCMD_DO_ACS
+ },
+};
+
static struct ieee80211_supported_band wil_band_60ghz = {
.channels = wil_60ghz_channels,
.n_channels = ARRAY_SIZE(wil_60ghz_channels),
@@ -1140,6 +1224,11 @@ static void wil_wiphy_init(struct wiphy *wiphy)
wiphy->n_cipher_suites = ARRAY_SIZE(wil_cipher_suites);
wiphy->mgmt_stypes = wil_mgmt_stypes;
wiphy->features |= NL80211_FEATURE_SK_TX_STATUS;
+
+ wiphy->n_vendor_commands = ARRAY_SIZE(wil_nl80211_vendor_commands);
+ wiphy->vendor_commands = wil_nl80211_vendor_commands;
+ wiphy->vendor_events = wil_nl80211_vendor_events;
+ wiphy->n_vendor_events = ARRAY_SIZE(wil_nl80211_vendor_events);
}
struct wireless_dev *wil_cfg80211_init(struct device *dev)
@@ -1190,3 +1279,303 @@ void wil_wdev_free(struct wil6210_priv *wil)
wiphy_free(wdev->wiphy);
kfree(wdev);
}
+
+static int wil_start_acs_survey(struct wil6210_priv *wil, uint dwell_time,
+ struct ieee80211_channel *channels,
+ u8 num_channels)
+{
+ int rc, i;
+ unsigned long to = msecs_to_jiffies(WMI_SURVEY_TIMEOUT_MS);
+ struct {
+ struct wmi_start_scan_cmd cmd;
+ struct {
+ u8 channel;
+ u8 reserved;
+ } channel_list[4];
+ } __packed scan_cmd = {
+ .cmd = {
+ .scan_type = WMI_PASSIVE_SCAN,
+ .dwell_time = cpu_to_le32(dwell_time),
+ .num_channels = min_t(u8, num_channels,
+ ARRAY_SIZE(wil_60ghz_channels)),
+ },
+ };
+
+ wil->survey_ready = false;
+ memset(&wil->survey_reply, 0, sizeof(wil->survey_reply));
+
+ for (i = 0; i < scan_cmd.cmd.num_channels; i++) {
+ u8 ch = channels[i].hw_value;
+
+ if (ch == 0) {
+ wil_err(wil, "ACS requested for wrong channel\n");
+ return -EINVAL;
+ }
+ wil_dbg_misc(wil, "ACS channel %d : %d MHz\n",
+ ch, channels[i].center_freq);
+ scan_cmd.channel_list[i].channel = ch - 1;
+ }
+
+ /* send scan command with the requested channel and wait
+ * for results
+ */
+ rc = wmi_send(wil, WMI_START_SCAN_CMDID, &scan_cmd, sizeof(scan_cmd));
+ if (rc) {
+ wil_err(wil, "ACS passive Scan failed (0x%08x)\n", rc);
+ return rc;
+ }
+
+ if (wait_event_interruptible_timeout(wil->wq, wil->survey_ready, to)
+ < 0) {
+ wil_err(wil, "%s: ACS survey interrupted\n", __func__);
+ return -ERESTARTSYS;
+ }
+
+ if (!wil->survey_ready) {
+ wil_err(wil, "ACS survey time out\n");
+ return -ETIME;
+ }
+
+ /* The results in survey_reply */
+ wil_dbg_misc(wil, "ACS scan success, filled mask: 0x%08X\n",
+ le16_to_cpu(wil->survey_reply.evt.filled));
+
+ return 0;
+}
+
+static u8 wil_acs_calc_channel(struct wil6210_priv *wil)
+{
+ int i, best_channel = ACS_DEFAULT_BEST_CHANNEL - 1;
+ struct scan_channel_info *ch;
+ u64 dwell_time = le32_to_cpu(wil->survey_reply.evt.dwell_time);
+ u16 filled = le16_to_cpu(wil->survey_reply.evt.filled);
+ u8 num_channels = wil->survey_reply.evt.num_scanned_channels;
+ u64 busy_time, tx_time;
+ u64 min_i_ch = (u64)-1, cur_i_ch;
+ u8 p_min = 0, ch_noise;
+
+ wil_dbg_misc(wil, "%s: filled info: 0x%04X, for %u channels\n",
+ __func__, filled, num_channels);
+
+ if (!num_channels) {
+ wil_err(wil, "%s: received results with no channel info\n",
+ __func__);
+ return 0;
+ }
+
+ /* find P_min */
+ if (filled & WMI_ACS_INFO_BITMASK_NOISE) {
+ p_min = wil->survey_reply.ch_info[0].noise;
+
+ for (i = 1; i < num_channels; i++)
+ p_min = min(p_min, wil->survey_reply.ch_info[i].noise);
+ }
+
+ wil_dbg_misc(wil, "%s: p_min is %u\n", __func__, p_min);
+
+ /* Choosing channel according to the following formula:
+ * 16 bit fixed point math
+ * I_ch = { [ (T_busy - T_tx) << 16 ] /
+ * (T_dwell - T_tx) } * 2^(P_rx - P_min)
+ */
+ for (i = 0; i < num_channels; i++) {
+ ch = &wil->survey_reply.ch_info[i];
+
+ if (ch->channel > 3) {
+ wil_err(wil,
+ "%s: invalid channel number %d\n",
+ __func__, ch->channel + 1);
+ continue;
+ }
+
+ busy_time = filled & WMI_ACS_INFO_BITMASK_BUSY_TIME ?
+ le16_to_cpu(ch->busy_time) : 0;
+
+ tx_time = filled & WMI_ACS_INFO_BITMASK_TX_TIME ?
+ le16_to_cpu(ch->tx_time) : 0;
+
+ ch_noise = filled & WMI_ACS_INFO_BITMASK_NOISE ? ch->noise : 0;
+
+ wil_dbg_misc(wil,
+ "%s: Ch[%d]: busy %llu, tx %llu, noise %u, dwell %llu\n",
+ __func__, ch->channel + 1, busy_time, tx_time,
+ ch_noise, dwell_time);
+
+ if (dwell_time == tx_time) {
+ wil_err(wil,
+ "%s: Ch[%d] dwell_time == tx_time: %llu\n",
+ __func__, ch->channel + 1, dwell_time);
+ continue;
+ }
+
+ cur_i_ch = (busy_time - tx_time) << 16;
+ do_div(cur_i_ch,
+ ((dwell_time - tx_time) << (ch_noise - p_min)));
+
+ /* Apply channel priority */
+ cur_i_ch = (cur_i_ch + ACS_CH_NOISE_INIT_VAL) *
+ acs_ch_weight[ch->channel];
+ do_div(cur_i_ch, 100);
+
+ wil_dbg_misc(wil, "%s: Ch[%d] w %u, I_ch %llu\n", __func__,
+ ch->channel + 1, acs_ch_weight[ch->channel],
+ cur_i_ch);
+
+ if (i == 0 || cur_i_ch < min_i_ch) {
+ min_i_ch = cur_i_ch;
+ best_channel = ch->channel;
+ }
+ }
+
+ wil_dbg_misc(wil, "%s: best channel %d with I_ch of %llu\n", __func__,
+ best_channel + 1, min_i_ch);
+
+ return best_channel;
+}
+
+static void wil_acs_report_channel(struct wil6210_priv *wil)
+{
+ struct sk_buff *vendor_event;
+ int ret_val;
+ struct nlattr *nla;
+ u8 channel = wil_acs_calc_channel(wil);
+
+ vendor_event = cfg80211_vendor_event_alloc(wil_to_wiphy(wil), NULL,
+ 2 + 4 + NLMSG_HDRLEN,
+ QCA_NL80211_VENDOR_SUBCMD_DO_ACS_INDEX,
+ GFP_KERNEL);
+ if (!vendor_event) {
+ wil_err(wil, "cfg80211_vendor_event_alloc failed\n");
+ return;
+ }
+
+ /* Send the IF INDEX to differentiate the ACS event for each interface
+ * TODO: To be update once cfg80211 APIs are updated to accept if_index
+ */
+ nla_nest_cancel(vendor_event, ((void **)vendor_event->cb)[2]);
+
+ ret_val = nla_put_u32(vendor_event, NL80211_ATTR_IFINDEX,
+ wil_to_ndev(wil)->ifindex);
+ if (ret_val) {
+ wil_err(wil, "NL80211_ATTR_IFINDEX put fail\n");
+ kfree_skb(vendor_event);
+ return;
+ }
+
+ nla = nla_nest_start(vendor_event, NL80211_ATTR_VENDOR_DATA);
+ ((void **)vendor_event->cb)[2] = nla;
+
+ /* channel indices used by fw are zero based and those used upper
+ * layers are 1 based: must add 1
+ */
+ ret_val = nla_put_u8(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL,
+ channel + 1);
+ if (ret_val) {
+ wil_err(wil,
+ "QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL put fail\n");
+ kfree_skb(vendor_event);
+ return;
+ }
+
+ /* must report secondary channel always, 0 is harmless */
+ ret_val = nla_put_u8(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL, 0);
+ if (ret_val) {
+ wil_err(wil,
+ "QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL put fail\n");
+ kfree_skb(vendor_event);
+ return;
+ }
+
+ cfg80211_vendor_event(vendor_event, GFP_KERNEL);
+}
+
+static int wil_do_acs(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len)
+{
+ struct wil6210_priv *wil = wdev_to_wil(wdev);
+ struct sk_buff *temp_skbuff;
+ int rc;
+ struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_ACS_MAX + 1];
+ u8 hw_mode;
+ struct ieee80211_channel reg_channels[ARRAY_SIZE(wil_60ghz_channels)];
+ int num_channels;
+ const struct ieee80211_reg_rule *reg_rule;
+ int i;
+
+ rc = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_ACS_MAX, data, data_len,
+ qca_wlan_acs_vendor_attr);
+ if (rc) {
+ wil_err(wil, "%s: Invalid ATTR\n", __func__);
+ goto out;
+ }
+
+ if (!tb[QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE]) {
+ wil_err(wil, "%s: Attr hw_mode failed\n", __func__);
+ goto out;
+ }
+
+ hw_mode = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE]);
+
+ if (hw_mode != QCA_ACS_MODE_IEEE80211AD) {
+ wil_err(wil, "%s: Illegal HW mode (%d), must be %d (11AD)\n",
+ __func__, hw_mode, QCA_ACS_MODE_IEEE80211AD);
+ goto out;
+ }
+
+ /* get list of channels allowed by regulatory */
+ num_channels = 0;
+ for (i = 0; i < ARRAY_SIZE(wil_60ghz_channels); i++) {
+ u32 ch_center_freq =
+ MHZ_TO_KHZ(wil_60ghz_channels[i].center_freq);
+ reg_rule = freq_reg_info(wiphy, ch_center_freq);
+ if (IS_ERR(reg_rule)) {
+ wil_dbg_misc(wil,
+ "%s: channel %d (%d) reg db err %ld\n",
+ __func__, wil_60ghz_channels[i].hw_value,
+ wil_60ghz_channels[i].center_freq,
+ PTR_ERR(reg_rule));
+ continue;
+ }
+
+ /* we assume if active scan allowed, we can use the
+ * channel to start AP on it
+ */
+ if (!(reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)) {
+ reg_channels[i] = wil_60ghz_channels[i];
+ num_channels++;
+ wil_dbg_misc(wil, "%s: Adding ch %d to ACS scan\n",
+ __func__, wil_60ghz_channels[i].hw_value);
+ } else {
+ wil_dbg_misc(wil,
+ "%s: channel %d (%d) can't be used: 0x%08X\n",
+ __func__, wil_60ghz_channels[i].hw_value,
+ wil_60ghz_channels[i].center_freq,
+ reg_rule->flags);
+ }
+ }
+
+ if (!num_channels) {
+ wil_err(wil,
+ "ACS aborted. Couldn't find channels allowed by regulatory\n");
+ rc = -EPERM;
+ goto out;
+ }
+
+ /* start acs survey*/
+ rc = wil_start_acs_survey(wil, scan_dwell_time, reg_channels,
+ num_channels);
+
+ if (!rc)
+ wil_acs_report_channel(wil);
+out:
+ if (0 == rc) {
+ temp_skbuff = cfg80211_vendor_cmd_alloc_reply_skb(wiphy,
+ NLMSG_HDRLEN);
+ if (temp_skbuff)
+ return cfg80211_vendor_cmd_reply(temp_skbuff);
+ }
+ return rc;
+}
+
diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h
index b79ba49..4b7135e 100644
--- a/drivers/net/wireless/ath/wil6210/wil6210.h
+++ b/drivers/net/wireless/ath/wil6210/wil6210.h
@@ -22,6 +22,7 @@
#include <net/cfg80211.h>
#include <linux/timex.h>
#include <linux/types.h>
+#include "wmi.h"
#include "wil_platform.h"
extern bool no_fw_recovery;
@@ -619,6 +620,11 @@ struct wil6210_priv {
struct wil_platform_ops platform_ops;
struct pmc_ctx pmc;
+ bool survey_ready;
+ struct {
+ struct wmi_acs_passive_scan_complete_event evt;
+ struct scan_channel_info ch_info[4];
+ } __packed survey_reply;
};
#define wil_to_wiphy(i) (i->wdev->wiphy)
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index c759759..12b5770 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -382,6 +382,27 @@ static void wmi_evt_scan_complete(struct wil6210_priv *wil, int id,
}
}
+static void wmi_evt_survey_complete(struct wil6210_priv *wil, int id,
+ void *d, int len)
+{
+ struct wmi_acs_passive_scan_complete_event *evt =
+ (struct wmi_acs_passive_scan_complete_event *)d;
+
+ int expected_size = sizeof(struct wmi_acs_passive_scan_complete_event) +
+ evt->num_scanned_channels *
+ sizeof(struct scan_channel_info);
+
+ if (len < expected_size) {
+ wil_err(wil, "Survey event size is too small (%d): expected %d channels\n",
+ len, evt->num_scanned_channels);
+ return;
+ }
+
+ memcpy(&wil->survey_reply, d, sizeof(wil->survey_reply));
+ wil->survey_ready = true;
+ wake_up_interruptible(&wil->wq);
+}
+
static void wmi_evt_connect(struct wil6210_priv *wil, int id, void *d, int len)
{
struct net_device *ndev = wil_to_ndev(wil);
@@ -656,17 +677,18 @@ static const struct {
void (*handler)(struct wil6210_priv *wil, int eventid,
void *data, int data_len);
} wmi_evt_handlers[] = {
- {WMI_READY_EVENTID, wmi_evt_ready},
- {WMI_FW_READY_EVENTID, wmi_evt_fw_ready},
- {WMI_RX_MGMT_PACKET_EVENTID, wmi_evt_rx_mgmt},
- {WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete},
- {WMI_CONNECT_EVENTID, wmi_evt_connect},
- {WMI_DISCONNECT_EVENTID, wmi_evt_disconnect},
- {WMI_EAPOL_RX_EVENTID, wmi_evt_eapol_rx},
- {WMI_BA_STATUS_EVENTID, wmi_evt_ba_status},
- {WMI_RCP_ADDBA_REQ_EVENTID, wmi_evt_addba_rx_req},
- {WMI_DELBA_EVENTID, wmi_evt_delba},
- {WMI_VRING_EN_EVENTID, wmi_evt_vring_en},
+ {WMI_READY_EVENTID, wmi_evt_ready},
+ {WMI_FW_READY_EVENTID, wmi_evt_fw_ready},
+ {WMI_RX_MGMT_PACKET_EVENTID, wmi_evt_rx_mgmt},
+ {WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete},
+ {WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID, wmi_evt_survey_complete},
+ {WMI_CONNECT_EVENTID, wmi_evt_connect},
+ {WMI_DISCONNECT_EVENTID, wmi_evt_disconnect},
+ {WMI_EAPOL_RX_EVENTID, wmi_evt_eapol_rx},
+ {WMI_BA_STATUS_EVENTID, wmi_evt_ba_status},
+ {WMI_RCP_ADDBA_REQ_EVENTID, wmi_evt_addba_rx_req},
+ {WMI_DELBA_EVENTID, wmi_evt_delba},
+ {WMI_VRING_EN_EVENTID, wmi_evt_vring_en},
};
/*
diff --git a/drivers/net/wireless/ath/wil6210/wmi.h b/drivers/net/wireless/ath/wil6210/wmi.h
index 6e90e78..b44f7ae 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.h
+++ b/drivers/net/wireless/ath/wil6210/wmi.h
@@ -33,6 +33,8 @@
#define WMI_MAC_LEN (6)
#define WMI_PROX_RANGE_NUM (3)
#define WMI_MAX_LOSS_DMG_BEACONS (32)
+#define WMI_SCAN_DWELL_TIME_MS (100)
+#define WMI_SURVEY_TIMEOUT_MS (10000)
/* List of Commands */
enum wmi_command_id {
@@ -288,7 +290,7 @@ struct wmi_delete_cipher_key_cmd {
enum wmi_scan_type {
WMI_LONG_SCAN = 0,
WMI_SHORT_SCAN = 1,
- WMI_PBC_SCAN = 2,
+ WMI_PASSIVE_SCAN = 2,
WMI_DIRECT_SCAN = 3,
WMI_ACTIVE_SCAN = 4,
};
@@ -296,7 +298,7 @@ enum wmi_scan_type {
struct wmi_start_scan_cmd {
u8 direct_scan_mac_addr[6];
u8 reserved[2];
- __le32 home_dwell_time; /* Max duration in the home channel(ms) */
+ __le32 dwell_time; /* duration in the home channel(ms) */
__le32 force_scan_interval; /* Time interval between scans (ms)*/
u8 scan_type; /* wmi_scan_type */
u8 num_channels; /* how many channels follow */
@@ -943,6 +945,7 @@ enum wmi_event_id {
WMI_EAPOL_RX_EVENTID = 0x9002,
WMI_MAC_ADDR_RESP_EVENTID = 0x9003,
WMI_FW_VER_EVENTID = 0x9004,
+ WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID = 0x9005,
};
/*
@@ -1127,6 +1130,37 @@ struct wmi_scan_complete_event {
} __packed;
/*
+ * WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENT
+ */
+enum wmi_acs_info_bitmask_e {
+ WMI_ACS_INFO_BITMASK_BEACON_FOUND = 0x01,
+ WMI_ACS_INFO_BITMASK_BUSY_TIME = 0x02,
+ WMI_ACS_INFO_BITMASK_TX_TIME = 0x04,
+ WMI_ACS_INFO_BITMASK_RX_TIME = 0x08,
+ WMI_ACS_INFO_BITMASK_NOISE = 0x10,
+};
+
+struct scan_channel_info {
+ u8 channel;
+ u8 beacon_found;
+ __le16 busy_time;
+ __le16 tx_time;
+ __le16 rx_time;
+ u8 noise;
+ u8 reserved[3];
+} __packed;
+
+struct wmi_acs_passive_scan_complete_event {
+ __le32 dwell_time;
+ __le16 filled; /* valid fields within channel info according to
+ * their appearance in struct order
+ */
+ u8 num_scanned_channels;
+ u8 reserved;
+ struct scan_channel_info scan_time_list[0];
+} __packed;
+
+/*
* WMI_BA_STATUS_EVENTID
*/
enum wmi_vring_ba_status {
--
2.1.4
To prevent race when connect flow may run in parallel with
the disconnect event.
Scenario leading to the bug is: while running connect flow on the AP,
STA sends disconnect. log follows.
<7>[ 668.736269] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Configure for connection CID 1
<7>[ 668.736269] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_vring_init_tx() max_mpdu_size 2048
<7>[ 668.736301] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_vring_alloc()
<7>[ 668.736363] wil6210 0000:01:00.0: wlan0: DBG[MISC]vring[1024] 0xffbe8000:d962ce08 0xdb244000
<7>[ 668.736394] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Head 0x00880300 -> 0x00880308
<7>[ 668.736394] wil6210 0000:01:00.0: wlan0: DBG[ WMI]WMI command 0x0821 [28]
<7>[ 668.736426] DBG[ WMI]Cmd 00000000: 20 00 24 00 00 00 00 00 00 00 21 08 00 00 00 00 .$.......!.....
<7>[ 668.736426] DBG[ WMI]cmd 00000000: 00 00 00 00 00 00 5f 5c 00 00 00 00 00 04 00 08 ......_\........
<7>[ 668.736457] DBG[ WMI]cmd 00000010: 01 01 00 00 00 00 00 00 00 00 ff 0f ............
<7>[ 668.736488] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]Pseudo IRQ 0x00000004
<7>[ 668.736519] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Handle WMI 0x1824 (reply_id 0x1821)
<7>[ 668.736519] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]wil6210_mask_irq_pseudo()
<7>[ 668.736519] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]ISR MISC 0x20000000
<7>[ 668.736551] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Handle WMI 0x1003 (reply_id 0x1821)
<7>[ 668.736551] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Disconnect 04:ce:14:00:07:70 reason [proto 3 wmi 4]
<7>[ 668.736582] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil6210_disconnect()
<7>[ 668.736613] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]Thread IRQ
<7>[ 668.736613] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]Thread ISR MISC 0x20000000
<7>[ 668.736644] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]MBOX event
<7>[ 668.736644] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Mbox head 00880330 tail 00880328
<7>[ 668.736676] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Mbox evt 001a 0010 0000 00
<7>[ 668.736676] wil6210 0000:01:00.0: wlan0: DBG[ WMI]WMI event 0x1821 MID 0 @3255145 msec
<7>[ 668.736707] DBG[ WMI]evt 00000000: 1a 00 10 00 00 00 00 10 00 00 21 18 69 ab 31 00 ..........!.i.1.
<7>[ 668.736707] DBG[ WMI]evt 00000010: 01 01 00 00 00 00 00 00 ........
<7>[ 668.736738] wil6210 0000:01:00.0: wlan0: DBG[ WMI]queue_work -> 0
<7>[ 668.736738] wil6210 0000:01:00.0: wlan0: DBG[ WMI]wmi_recv_cmd -> 1 events queued
<7>[ 668.736769] wil6210 0000:01:00.0: wlan0: DBG[ IRQ]wil6210_unmask_irq_pseudo()
<7>[ 668.736832] wil6210 0000:01:00.0: wlan0: DBG[MISC]Disconnect 04:ce:14:00:07:70, CID=1, reason=3
<7>[ 668.736832] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_disconnect_cid(CID 1, status 1)
<7>[ 668.736894] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_vring_fini_tx() id=1
<7>[ 668.736894] wil6210 0000:01:00.0: wlan0: DBG[MISC]free Tx vring 1 [1024] 0xffbe8000:d962ce08 0xdb244000
<7>[ 668.736957] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Handle WMI 0x1821 (reply_id 0x1821)
<7>[ 668.736988] wil6210 0000:01:00.0: wlan0: DBG[ WMI]Complete WMI 0x1821
<7>[ 668.737019] wil6210 0000:01:00.0: wlan0: DBG[ WMI]wmi_call(0x0821->0x1821) completed in 0 msec
<3>[ 668.737019] wil6210 0000:01:00.0: wlan0: Tx config failed, status 0x01
<7>[ 668.739518] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil_cfg80211_del_station(04:ce:14:00:07:70, reason=2)
<7>[ 668.739550] wil6210 0000:01:00.0: wlan0: DBG[MISC]wil6210_disconnect()
<7>[ 668.739550] wil6210 0000:01:00.0: wlan0: DBG[MISC]_wil6210_disconnect(bssid=04:ce:14:00:07:70, reason=2, ev-)
<7>[ 668.739581] wil6210 0000:01:00.0: wlan0: DBG[MISC]Disconnect 04:ce:14:00:07:70, CID=-2, reason=2
<7>[ 668.742705] wil6210 0000:01:00.0: wlan0: DBG[MISC]free Tx vring 1 [1024] 0x (null):d962ce08 0x (null)
<3>[ 668.742736] __dma_free_remap: trying to free invalid coherent area: (null)
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/main.c | 18 +++++++++++++-----
drivers/net/wireless/ath/wil6210/txrx.c | 3 +++
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
index 65d15e2..4740edb 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -400,20 +400,26 @@ void wil_bcast_fini(struct wil6210_priv *wil)
static void wil_connect_worker(struct work_struct *work)
{
- int rc;
+ int rc, cid, ringid;
struct wil6210_priv *wil = container_of(work, struct wil6210_priv,
connect_worker);
struct net_device *ndev = wil_to_ndev(wil);
- int cid = wil->pending_connect_cid;
- int ringid = wil_find_free_vring(wil);
+ mutex_lock(&wil->mutex);
+ cid = wil->pending_connect_cid;
if (cid < 0) {
wil_err(wil, "No connection pending\n");
- return;
+ goto out;
+ }
+ ringid = wil_find_free_vring(wil);
+ if (ringid < 0) {
+ wil_err(wil, "No free vring found\n");
+ goto out;
}
- wil_dbg_wmi(wil, "Configure for connection CID %d\n", cid);
+ wil_dbg_wmi(wil, "Configure for connection CID %d vring %d\n",
+ cid, ringid);
rc = wil_vring_init_tx(wil, ringid, 1 << tx_ring_order, cid, 0);
wil->pending_connect_cid = -1;
@@ -423,6 +429,8 @@ static void wil_connect_worker(struct work_struct *work)
} else {
wil->sta[cid].status = wil_sta_unused;
}
+out:
+ mutex_unlock(&wil->mutex);
}
int wil_priv_init(struct wil6210_priv *wil)
diff --git a/drivers/net/wireless/ath/wil6210/txrx.c b/drivers/net/wireless/ath/wil6210/txrx.c
index aa20af8..112192f1 100644
--- a/drivers/net/wireless/ath/wil6210/txrx.c
+++ b/drivers/net/wireless/ath/wil6210/txrx.c
@@ -160,6 +160,7 @@ static void wil_vring_free(struct wil6210_priv *wil, struct vring *vring,
struct device *dev = wil_to_dev(wil);
size_t sz = vring->size * sizeof(vring->va[0]);
+ WARN_ON(!mutex_is_locked(&wil->mutex));
if (tx) {
int vring_index = vring - wil->vring_tx;
@@ -707,6 +708,7 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size,
wil_dbg_misc(wil, "%s() max_mpdu_size %d\n", __func__,
cmd.vring_cfg.tx_sw_ring.max_mpdu_size);
+ WARN_ON(!mutex_is_locked(&wil->mutex));
if (vring->va) {
wil_err(wil, "Tx ring [%d] already allocated\n", id);
@@ -779,6 +781,7 @@ int wil_vring_init_bcast(struct wil6210_priv *wil, int id, int size)
wil_dbg_misc(wil, "%s() max_mpdu_size %d\n", __func__,
cmd.vring_cfg.tx_sw_ring.max_mpdu_size);
+ WARN_ON(!mutex_is_locked(&wil->mutex));
if (vring->va) {
wil_err(wil, "Tx ring [%d] already allocated\n", id);
--
2.1.4
There are 2 versions of boot loader struct: v0 and v1.
In the v1, boot loader build version added; as well as
RF status.
Support both versions.
Boot loader structure v1 has RF status; ignore RF error if firmware
not going to be loaded; driver can still be used to interact with the HW
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/boot_loader.h | 57 +++++++++++++++++
drivers/net/wireless/ath/wil6210/main.c | 85 ++++++++++++++++++++------
drivers/net/wireless/ath/wil6210/wil6210.h | 10 ---
3 files changed, 125 insertions(+), 27 deletions(-)
create mode 100644 drivers/net/wireless/ath/wil6210/boot_loader.h
diff --git a/drivers/net/wireless/ath/wil6210/boot_loader.h b/drivers/net/wireless/ath/wil6210/boot_loader.h
new file mode 100644
index 0000000..0655eff
--- /dev/null
+++ b/drivers/net/wireless/ath/wil6210/boot_loader.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2015 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.
+ */
+
+/* This file contains the definitions for the boot loader
+ * for the Qualcomm "Sparrow" 60 Gigabit wireless solution.
+ */
+#ifndef BOOT_LOADER_EXPORT_H_
+#define BOOT_LOADER_EXPORT_H_
+
+struct bl_dedicated_registers_v1 {
+ __le32 boot_loader_ready; /* 0x880A3C driver will poll
+ * this Dword until BL will
+ * set it to 1 (initial value
+ * should be 0)
+ */
+ __le32 boot_loader_struct_version; /* 0x880A40 BL struct ver. */
+ __le16 rf_type; /* 0x880A44 connected RF ID */
+ __le16 rf_status; /* 0x880A46 RF status,
+ * 0 is OK else error
+ */
+ __le32 baseband_type; /* 0x880A48 board type ID */
+ u8 mac_address[6]; /* 0x880A4c BL mac address */
+ u8 bl_version_major; /* 0x880A52 BL ver. major */
+ u8 bl_version_minor; /* 0x880A53 BL ver. minor */
+ __le16 bl_version_subminor; /* 0x880A54 BL ver. subminor */
+ __le16 bl_version_build; /* 0x880A56 BL ver. build */
+} __packed;
+
+/* the following struct is the version 0 struct */
+
+struct bl_dedicated_registers_v0 {
+ __le32 boot_loader_ready; /* 0x880A3C driver will poll
+ * this Dword until BL will
+ * set it to 1 (initial value
+ * should be 0)
+ */
+#define BL_READY (1) /* ready indication */
+ __le32 boot_loader_struct_version; /* 0x880A40 BL struct ver. */
+ __le32 rf_type; /* 0x880A44 connected RF ID */
+ __le32 baseband_type; /* 0x880A48 board type ID */
+ u8 mac_address[6]; /* 0x880A4c BL mac address */
+} __packed;
+
+#endif /* BOOT_LOADER_EXPORT_H_ */
+
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
index b9febab..65d15e2 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -21,6 +21,7 @@
#include "wil6210.h"
#include "txrx.h"
#include "wmi.h"
+#include "boot_loader.h"
#define WAIT_FOR_DISCONNECT_TIMEOUT_MS 2000
#define WAIT_FOR_DISCONNECT_INTERVAL_MS 10
@@ -565,7 +566,8 @@ static int wil_target_reset(struct wil6210_priv *wil)
wil_halt_cpu(wil);
/* clear all boot loader "ready" bits */
- W(RGF_USER_BL + offsetof(struct RGF_BL, ready), 0);
+ W(RGF_USER_BL +
+ offsetof(struct bl_dedicated_registers_v0, boot_loader_ready), 0);
/* Clear Fw Download notification */
C(RGF_USER_USAGE_6, BIT(0));
@@ -607,7 +609,8 @@ static int wil_target_reset(struct wil6210_priv *wil)
/* wait until device ready. typical time is 20..80 msec */
do {
msleep(RST_DELAY);
- x = R(RGF_USER_BL + offsetof(struct RGF_BL, ready));
+ x = R(RGF_USER_BL + offsetof(struct bl_dedicated_registers_v0,
+ boot_loader_ready));
if (x1 != x) {
wil_dbg_misc(wil, "BL.ready 0x%08x => 0x%08x\n", x1, x);
x1 = x;
@@ -617,7 +620,7 @@ static int wil_target_reset(struct wil6210_priv *wil)
x);
return -ETIME;
}
- } while (x != BIT_BL_READY);
+ } while (x != BL_READY);
C(RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);
@@ -641,25 +644,71 @@ void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r)
static int wil_get_bl_info(struct wil6210_priv *wil)
{
struct net_device *ndev = wil_to_ndev(wil);
- struct RGF_BL bl;
-
- wil_memcpy_fromio_32(&bl, wil->csr + HOSTADDR(RGF_USER_BL), sizeof(bl));
- le32_to_cpus(&bl.ready);
- le32_to_cpus(&bl.version);
- le32_to_cpus(&bl.rf_type);
- le32_to_cpus(&bl.baseband_type);
+ union {
+ struct bl_dedicated_registers_v0 bl0;
+ struct bl_dedicated_registers_v1 bl1;
+ } bl;
+ u32 bl_ver;
+ u8 *mac;
+ u16 rf_status;
+
+ bl_ver = R(RGF_USER_BL + offsetof(struct bl_dedicated_registers_v0,
+ boot_loader_struct_version));
+ switch (bl_ver) {
+ case 0:
+ wil_memcpy_fromio_32(&bl, wil->csr + HOSTADDR(RGF_USER_BL),
+ sizeof(bl.bl0));
+ le32_to_cpus(&bl.bl0.boot_loader_ready);
+ le32_to_cpus(&bl.bl0.boot_loader_struct_version);
+ le32_to_cpus(&bl.bl0.rf_type);
+ le32_to_cpus(&bl.bl0.baseband_type);
+ mac = bl.bl0.mac_address;
+ rf_status = 0; /* actually, unknown */
+ wil_info(wil,
+ "Boot Loader struct v%d: MAC = %pM RF = 0x%08x bband = 0x%08x\n",
+ bl_ver, mac,
+ bl.bl0.rf_type, bl.bl0.baseband_type);
+ wil_info(wil, "Boot Loader build unknown for struct v0\n");
+ break;
+ case 1:
+ wil_memcpy_fromio_32(&bl, wil->csr + HOSTADDR(RGF_USER_BL),
+ sizeof(bl.bl1));
+ le32_to_cpus(&bl.bl1.boot_loader_ready);
+ le32_to_cpus(&bl.bl1.boot_loader_struct_version);
+ le16_to_cpus(&bl.bl1.rf_type);
+ rf_status = le16_to_cpu(bl.bl1.rf_status);
+ le32_to_cpus(&bl.bl1.baseband_type);
+ le16_to_cpus(&bl.bl1.bl_version_subminor);
+ le16_to_cpus(&bl.bl1.bl_version_build);
+ mac = bl.bl1.mac_address;
+ wil_info(wil,
+ "Boot Loader struct v%d: MAC = %pM RF = 0x%04x (status 0x%04x) bband = 0x%08x\n",
+ bl_ver, mac,
+ bl.bl1.rf_type, rf_status,
+ bl.bl1.baseband_type);
+ wil_info(wil, "Boot Loader build %d.%d.%d.%d\n",
+ bl.bl1.bl_version_major, bl.bl1.bl_version_minor,
+ bl.bl1.bl_version_subminor, bl.bl1.bl_version_build);
+ break;
+ default:
+ wil_err(wil, "BL: unsupported struct version 0x%08x\n", bl_ver);
+ return -EINVAL;
+ }
- if (!is_valid_ether_addr(bl.mac_address)) {
- wil_err(wil, "BL: Invalid MAC %pM\n", bl.mac_address);
+ if (!is_valid_ether_addr(mac)) {
+ wil_err(wil, "BL: Invalid MAC %pM\n", mac);
return -EINVAL;
}
- ether_addr_copy(ndev->perm_addr, bl.mac_address);
+ ether_addr_copy(ndev->perm_addr, mac);
if (!is_valid_ether_addr(ndev->dev_addr))
- ether_addr_copy(ndev->dev_addr, bl.mac_address);
- wil_info(wil,
- "Boot Loader: ver = %d MAC = %pM RF = 0x%08x bband = 0x%08x\n",
- bl.version, bl.mac_address, bl.rf_type, bl.baseband_type);
+ ether_addr_copy(ndev->dev_addr, mac);
+
+ if (rf_status) {/* bad RF cable? */
+ wil_err(wil, "RF communication error 0x%04x",
+ rf_status);
+ return -EAGAIN;
+ }
return 0;
}
@@ -735,6 +784,8 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw)
return rc;
rc = wil_get_bl_info(wil);
+ if (rc == -EAGAIN && !load_fw) /* ignore RF error if not going up */
+ rc = 0;
if (rc)
return rc;
diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h
index c63e4a3..b79ba49 100644
--- a/drivers/net/wireless/ath/wil6210/wil6210.h
+++ b/drivers/net/wireless/ath/wil6210/wil6210.h
@@ -127,16 +127,6 @@ struct RGF_ICR {
u32 IMC; /* Mask Clear, write 1 to clear */
} __packed;
-struct RGF_BL {
- u32 ready; /* 0x880A3C bit [0] */
-#define BIT_BL_READY BIT(0)
- u32 version; /* 0x880A40 version of the BL struct */
- u32 rf_type; /* 0x880A44 ID of the connected RF */
- u32 baseband_type; /* 0x880A48 ID of the baseband */
- u8 mac_address[ETH_ALEN]; /* 0x880A4C permanent MAC */
- u8 pad[2];
-} __packed;
-
/* registers - FW addresses */
#define RGF_USER_USAGE_1 (0x880004)
#define RGF_USER_USAGE_6 (0x880018)
--
2.1.4
Several new features introduced:
- TSO improves throughput on Tx side on systems with weak CPU
- ACS chooses best channel automatically when starting AP
- support for the new boot loader
Bug fixes:
- "wait for del_station to complete"
- "hold wil->mutex while managing vrings"
The rest are small fixes and improvements
V2: found bug in the commit for TSO - by mistake, reported was
NETIF_F_IP_CSUM and NETIF_F_IPV6_CSUM. Re-sending whole series
to avoid confusion
Dedy Lansky (1):
wil6210: treat "unhandled event" as warning instead of error
Vladimir Kondratiev (12):
wil6210: support boot loader struct v0 & v1
wil6210: ACS implementation using QCA vendor command
wil6210: debugfs for channel survey
wil6210: count drops in Rx block ack reorder
wil6210: print "ulong" fields in hex format in the debugfs
wil6210: use <> vs. "" for global include
wil6210: wait for del_station to complete
wil6210: hold wil->mutex while managing vrings
wil6210: use wil_fw_error_recovery()
wil6210: skip HW version check for chip debugging
wil6210: TSO implementation
wil6210: improve mgmt frame handling
drivers/net/wireless/ath/wil6210/boot_loader.h | 57 ++++
drivers/net/wireless/ath/wil6210/cfg80211.c | 389 ++++++++++++++++++++++++
drivers/net/wireless/ath/wil6210/debugfs.c | 64 +++-
drivers/net/wireless/ath/wil6210/main.c | 112 +++++--
drivers/net/wireless/ath/wil6210/netdev.c | 4 +-
drivers/net/wireless/ath/wil6210/rx_reorder.c | 2 +
drivers/net/wireless/ath/wil6210/txrx.c | 383 ++++++++++++++++++++++-
drivers/net/wireless/ath/wil6210/txrx.h | 8 +
drivers/net/wireless/ath/wil6210/wil6210.h | 18 +-
drivers/net/wireless/ath/wil6210/wil_platform.c | 2 +-
drivers/net/wireless/ath/wil6210/wmi.c | 129 ++++++--
drivers/net/wireless/ath/wil6210/wmi.h | 38 ++-
12 files changed, 1128 insertions(+), 78 deletions(-)
create mode 100644 drivers/net/wireless/ath/wil6210/boot_loader.h
--
2.1.4
On Wednesday, July 08, 2015 10:06:37 PM Emmanuel Grumbach wrote:
> So your device is able to replicate and update the IP / TCP header?
> I don't really follow what your device is able to do.
> You seem to be cutting the frags so that their length sums up to mss.
> Which hints that your device can't segment the buffer by itself. OTOH,
> I don't see how you treat the IP / TCP header copy and modification.
>
Emmanuel:
Yes, it is correct - hardware know to replicate IP/TCP header; and
DMA written in such a way that I have to arrange fragments to
sums up to MSS.
So this code fragment is OK. We tested it with lots of traffic.
However, after your comments for another code fragment,
I found there is a way to do it better. I'll rework and send updated patch.
Kalle: please drop this patch. The rest of patches should apply cleanly
without this one.
Thanks, Vladimir
Vladimir Kondratiev <[email protected]> writes:
> On Wednesday, July 08, 2015 10:06:37 PM Emmanuel Grumbach wrote:
>> So your device is able to replicate and update the IP / TCP header?
>> I don't really follow what your device is able to do.
>> You seem to be cutting the frags so that their length sums up to mss.
>> Which hints that your device can't segment the buffer by itself. OTOH,
>> I don't see how you treat the IP / TCP header copy and modification.
>>
> Emmanuel:
> Yes, it is correct - hardware know to replicate IP/TCP header; and
> DMA written in such a way that I have to arrange fragments to
> sums up to MSS.
>
> So this code fragment is OK. We tested it with lots of traffic.
> However, after your comments for another code fragment,
> I found there is a way to do it better. I'll rework and send updated patch.
>
> Kalle: please drop this patch. The rest of patches should apply cleanly
> without this one.
Ok, dropped this patch.
--
Kalle Valo
Driver report supported TSO (v4 & v6) and IP checksum offload
in addition to previously supported features. In data path
skbs are checked for non-zero gso_size, and when detected sent
to additional function for processing TSO SKBs. Since HW does not
fully support TSO, additional effort is required from the driver.
Driver partitions the data into mss sized descriptors which are
then DMAed to the HW.
Signed-off-by: Vladimir Shulman <[email protected]>
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/netdev.c | 5 +-
drivers/net/wireless/ath/wil6210/txrx.c | 380 +++++++++++++++++++++++++++++-
drivers/net/wireless/ath/wil6210/txrx.h | 8 +
3 files changed, 380 insertions(+), 13 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/netdev.c b/drivers/net/wireless/ath/wil6210/netdev.c
index 8ef18ac..0a8a8ed 100644
--- a/drivers/net/wireless/ath/wil6210/netdev.c
+++ b/drivers/net/wireless/ath/wil6210/netdev.c
@@ -173,7 +173,10 @@ void *wil_if_alloc(struct device *dev)
wil_set_ethtoolops(ndev);
ndev->ieee80211_ptr = wdev;
ndev->hw_features = NETIF_F_HW_CSUM | NETIF_F_RXCSUM |
- NETIF_F_SG | NETIF_F_GRO;
+ NETIF_F_SG | NETIF_F_GRO |
+ NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_IP_CSUM |
+ NETIF_F_IPV6_CSUM;
+
ndev->features |= ndev->hw_features;
SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy));
wdev->netdev = ndev;
diff --git a/drivers/net/wireless/ath/wil6210/txrx.c b/drivers/net/wireless/ath/wil6210/txrx.c
index 112192f1..8a2f2b6 100644
--- a/drivers/net/wireless/ath/wil6210/txrx.c
+++ b/drivers/net/wireless/ath/wil6210/txrx.c
@@ -1061,15 +1061,23 @@ static int wil_tx_desc_map(struct vring_tx_desc *d, dma_addr_t pa, u32 len,
static inline
void wil_tx_desc_set_nr_frags(struct vring_tx_desc *d, int nr_frags)
{
- d->mac.d[2] |= ((nr_frags + 1) <<
- MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_POS);
+ d->mac.d[2] |= (nr_frags << MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_POS);
}
-static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
- struct vring_tx_desc *d,
- struct sk_buff *skb)
-{
+/**
+ * Sets the descriptor @d up for csum and/or TSO offloading. The corresponding
+ * @skb is used to obtain the protocol and headers length.
+ * @tso_desc_type is a descriptor type for TSO: -1 - no TSO send,
+ * 0 - a header, 1 - first data, 2 - middle, 3 - last descriptor.
+ * Returns the protocol: 0 - not TCP, 1 - TCPv4, 2 - TCPv6.
+ * Note, if d==NULL, the function only returns the protocol result.
+ */
+
+static int wil_tx_desc_offload_setup_tso(struct vring_tx_desc *d,
+ struct sk_buff *skb,
+ int tso_desc_type) {
int protocol;
+ int is_ip4 = 0;
if (skb->ip_summed != CHECKSUM_PARTIAL)
return 0;
@@ -1080,6 +1088,7 @@ static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
case cpu_to_be16(ETH_P_IP):
protocol = ip_hdr(skb)->protocol;
d->dma.b11 |= BIT(DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_POS);
+ is_ip4 = 1;
break;
case cpu_to_be16(ETH_P_IPV6):
protocol = ipv6_hdr(skb)->nexthdr;
@@ -1094,6 +1103,13 @@ static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
/* L4 header len: TCP header length */
d->dma.d0 |=
(tcp_hdrlen(skb) & DMA_CFG_DESC_TX_0_L4_LENGTH_MSK);
+
+ /* Setup TSO: bit and desc type */
+ d->dma.d0 |= (BIT(DMA_CFG_DESC_TX_0_TCP_SEG_EN_POS)) |
+ (tso_desc_type <<
+ DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_POS);
+ d->dma.d0 |= (is_ip4 <<
+ DMA_CFG_DESC_TX_0_IPV4_CHECKSUM_EN_POS);
break;
case IPPROTO_UDP:
/* L4 header len: UDP header length */
@@ -1113,6 +1129,334 @@ static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
return 0;
}
+/**
+ * Sets the descriptor @d up for csum. The corresponding
+ * @skb is used to obtain the protocol and headers length.
+ * Returns the protocol: 0 - not TCP, 1 - TCPv4, 2 - TCPv6.
+ * Note, if d==NULL, the function only returns the protocol result.
+ *
+ * It is very similar to previous wil_tx_desc_offload_setup_tso. This
+ * is "if unrolling" to optimize the critical path.
+ */
+
+static int wil_tx_desc_offload_setup(struct vring_tx_desc *d,
+ struct sk_buff *skb){
+ int protocol;
+
+ if (skb->ip_summed != CHECKSUM_PARTIAL)
+ return 0;
+
+ d->dma.b11 = ETH_HLEN; /* MAC header length */
+
+ switch (skb->protocol) {
+ case cpu_to_be16(ETH_P_IP):
+ protocol = ip_hdr(skb)->protocol;
+ d->dma.b11 |= BIT(DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_POS);
+ break;
+ case cpu_to_be16(ETH_P_IPV6):
+ protocol = ipv6_hdr(skb)->nexthdr;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (protocol) {
+ case IPPROTO_TCP:
+ d->dma.d0 |= (2 << DMA_CFG_DESC_TX_0_L4_TYPE_POS);
+ /* L4 header len: TCP header length */
+ d->dma.d0 |=
+ (tcp_hdrlen(skb) & DMA_CFG_DESC_TX_0_L4_LENGTH_MSK);
+ break;
+ case IPPROTO_UDP:
+ /* L4 header len: UDP header length */
+ d->dma.d0 |=
+ (sizeof(struct udphdr) & DMA_CFG_DESC_TX_0_L4_LENGTH_MSK);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ d->dma.ip_length = skb_network_header_len(skb);
+ /* Enable TCP/UDP checksum */
+ d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_TCP_UDP_CHECKSUM_EN_POS);
+ /* Calculate pseudo-header */
+ d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_PSEUDO_HEADER_CALC_EN_POS);
+
+ return 0;
+}
+
+static inline void wil_tx_last_desc(struct vring_tx_desc *d)
+{
+ d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_CMD_EOP_POS) |
+ BIT(DMA_CFG_DESC_TX_0_CMD_MARK_WB_POS) |
+ BIT(DMA_CFG_DESC_TX_0_CMD_DMA_IT_POS);
+}
+
+static inline void wil_set_tx_desc_last_tso(volatile struct vring_tx_desc *d)
+{
+ d->dma.d0 |= wil_tso_type_lst <<
+ DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_POS;
+}
+
+static int __wil_tx_vring_tso(struct wil6210_priv *wil, struct vring *vring,
+ struct sk_buff *skb)
+{
+ struct device *dev = wil_to_dev(wil);
+
+ /* point to descriptors in shared memory */
+ volatile struct vring_tx_desc *_desc = NULL, *_hdr_desc,
+ *_first_desc = NULL;
+
+ /* pointers to shadow descriptors */
+ struct vring_tx_desc desc_mem, hdr_desc_mem, first_desc_mem,
+ *d = &hdr_desc_mem, *hdr_desc = &hdr_desc_mem,
+ *first_desc = &first_desc_mem;
+
+ /* pointer to shadow descriptors' context */
+ struct wil_ctx *hdr_ctx, *first_ctx = NULL;
+
+ int descs_used = 0; /* total number of used descriptors */
+ int sg_desc_cnt = 0; /* number of descriptors for current mss*/
+
+ u32 swhead = vring->swhead;
+ int used, avail = wil_vring_avail_tx(vring);
+ int nr_frags = skb_shinfo(skb)->nr_frags;
+ int min_desc_required = nr_frags + 1;
+ int mss = skb_shinfo(skb)->gso_size; /* payload size w/o headers */
+ int f, len, hdrlen, headlen;
+ int vring_index = vring - wil->vring_tx;
+ struct vring_tx_data *txdata = &wil->vring_tx_data[vring_index];
+ uint i = swhead;
+ dma_addr_t pa;
+ const skb_frag_t *frag = NULL;
+ int rem_data = mss;
+ int lenmss;
+ int hdr_compensation_need = true;
+ int desc_tso_type = wil_tso_type_first;
+
+ wil_dbg_txrx(wil, "%s() %d bytes to vring %d\n",
+ __func__, skb->len, vring_index);
+
+ if (unlikely(!txdata->enabled))
+ return -EINVAL;
+
+ /* A typical page 4K is 3-4 payloads, we assume each fragment
+ * is a full payload, that's how min_desc_required has been
+ * calculated. In real we might need more or less descriptors,
+ * this is the initial check only.
+ */
+ if (unlikely(avail < min_desc_required)) {
+ wil_err_ratelimited(wil,
+ "TSO: Tx ring[%2d] full. No space for %d fragments\n",
+ vring_index, min_desc_required);
+ return -ENOMEM;
+ }
+
+ /* Header Length = MAC header len + IP header len + TCP header len */
+ hdrlen = ETH_HLEN +
+ (int)skb_network_header_len(skb) +
+ tcp_hdrlen(skb);
+
+ if (skb->protocol == cpu_to_be16(ETH_P_IP)) {
+ /* TCP v4, zero out the IP length and IPv4 checksum fields
+ * as required by the offloading doc
+ */
+ ip_hdr(skb)->tot_len = 0;
+ ip_hdr(skb)->check = 0;
+ } else {
+ /* TCP v6, zero out the payload length */
+ ipv6_hdr(skb)->payload_len = 0;
+ }
+
+ _hdr_desc = &vring->va[i].tx;
+
+ pa = dma_map_single(dev, skb->data, hdrlen, DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dev, pa))) {
+ wil_err(wil, "TSO: Skb head DMA map error\n");
+ goto err_exit;
+ }
+
+ wil_tx_desc_map(hdr_desc, pa, hdrlen, vring_index);
+ wil_tx_desc_offload_setup_tso(hdr_desc, skb, wil_tso_type_hdr);
+ wil_tx_last_desc(hdr_desc);
+
+ vring->ctx[i].mapped_as = wil_mapped_as_single;
+ hdr_ctx = &vring->ctx[i];
+
+ descs_used++;
+ headlen = skb_headlen(skb) - hdrlen;
+
+ for (f = headlen ? -1 : 0; f < nr_frags; f++) {
+ if (headlen) {
+ len = headlen;
+ wil_dbg_txrx(wil, "TSO: process skb head, len %u\n",
+ len);
+ } else {
+ frag = &skb_shinfo(skb)->frags[f];
+ len = frag->size;
+ wil_dbg_txrx(wil, "TSO: frag[%d]: len %u\n", f, len);
+ }
+
+ while (len) {
+ wil_dbg_txrx(wil,
+ "TSO: len %d, rem_data %d, descs_used %d\n",
+ len, rem_data, descs_used);
+
+ if (descs_used == avail) {
+ wil_err(wil, "TSO: ring overflow\n");
+ goto dma_error;
+ }
+
+ lenmss = min_t(int, rem_data, len);
+ i = (swhead + descs_used) % vring->size;
+ wil_dbg_txrx(wil, "TSO: lenmss %d, i %d\n", lenmss, i);
+
+ if (!headlen) {
+ pa = skb_frag_dma_map(dev, frag,
+ frag->size - len, lenmss,
+ DMA_TO_DEVICE);
+ vring->ctx[i].mapped_as = wil_mapped_as_page;
+ } else {
+ pa = dma_map_single(dev,
+ skb->data +
+ skb_headlen(skb) - headlen,
+ lenmss,
+ DMA_TO_DEVICE);
+ vring->ctx[i].mapped_as = wil_mapped_as_single;
+ headlen -= lenmss;
+ }
+
+ if (unlikely(dma_mapping_error(dev, pa)))
+ goto dma_error;
+
+ _desc = &vring->va[i].tx;
+
+ if (!_first_desc) {
+ _first_desc = _desc;
+ first_ctx = &vring->ctx[i];
+ d = first_desc;
+ } else {
+ d = &desc_mem;
+ }
+
+ wil_tx_desc_map(d, pa, lenmss, vring_index);
+ wil_tx_desc_offload_setup_tso(d, skb, desc_tso_type);
+
+ /* use tso_type_first only once */
+ desc_tso_type = wil_tso_type_mid;
+
+ descs_used++; /* desc used so far */
+ sg_desc_cnt++; /* desc used for this segment */
+ len -= lenmss;
+ rem_data -= lenmss;
+
+ wil_dbg_txrx(wil,
+ "TSO: len %d, rem_data %d, descs_used %d, sg_desc_cnt %d,\n",
+ len, rem_data, descs_used, sg_desc_cnt);
+
+ /* Close the segment if reached mss size or last frag*/
+ if (rem_data == 0 || (f == nr_frags - 1 && len == 0)) {
+ if (hdr_compensation_need) {
+ /* first segment include hdr desc for
+ * release
+ */
+ hdr_ctx->nr_frags = sg_desc_cnt;
+ wil_tx_desc_set_nr_frags(first_desc,
+ sg_desc_cnt +
+ 1);
+ hdr_compensation_need = false;
+ } else {
+ wil_tx_desc_set_nr_frags(first_desc,
+ sg_desc_cnt);
+ }
+ first_ctx->nr_frags = sg_desc_cnt - 1;
+
+ wil_tx_last_desc(d);
+
+ /* first descriptor may also be the last
+ * for this mss - make sure not to copy
+ * it twice
+ */
+ if (first_desc != d)
+ *_first_desc = *first_desc;
+
+ /*last descriptor will be copied at the end
+ * of this TS processing
+ */
+ if (f < nr_frags - 1 || len > 0)
+ *_desc = *d;
+
+ rem_data = mss;
+ _first_desc = NULL;
+ sg_desc_cnt = 0;
+ } else if (first_desc != d) /* update mid descriptor */
+ *_desc = *d;
+ }
+ }
+
+ /* first descriptor may also be the last.
+ * in this case d pointer is invalid
+ */
+ if (_first_desc == _desc)
+ d = first_desc;
+
+ /* Last data descriptor */
+ wil_set_tx_desc_last_tso(d);
+ *_desc = *d;
+
+ /* Fill the total number of descriptors in first desc (hdr)*/
+ wil_tx_desc_set_nr_frags(hdr_desc, descs_used);
+ *_hdr_desc = *hdr_desc;
+
+ /* hold reference to skb
+ * to prevent skb release before accounting
+ * in case of immediate "tx done"
+ */
+ vring->ctx[i].skb = skb_get(skb);
+
+ /* performance monitoring */
+ used = wil_vring_used_tx(vring);
+ if (wil_val_in_range(vring_idle_trsh,
+ used, used + descs_used)) {
+ txdata->idle += get_cycles() - txdata->last_idle;
+ wil_dbg_txrx(wil, "Ring[%2d] not idle %d -> %d\n",
+ vring_index, used, used + descs_used);
+ }
+
+ /* advance swhead */
+ wil_dbg_txrx(wil, "TSO: Tx swhead %d -> %d\n", swhead, vring->swhead);
+ wil_vring_advance_head(vring, descs_used);
+
+ /* make sure all writes to descriptors (shared memory) are done before
+ * committing them to HW
+ */
+ wmb();
+
+ iowrite32(vring->swhead, wil->csr + HOSTADDR(vring->hwtail));
+ return 0;
+
+dma_error:
+ wil_err(wil, "TSO: DMA map page error\n");
+ while (descs_used > 0) {
+ struct wil_ctx *ctx;
+
+ i = (swhead + descs_used) % vring->size;
+ d = (struct vring_tx_desc *)&vring->va[i].tx;
+ _desc = &vring->va[i].tx;
+ *d = *_desc;
+ _desc->dma.status = TX_DMA_STATUS_DU;
+ ctx = &vring->ctx[i];
+ wil_txdesc_unmap(dev, d, ctx);
+ if (ctx->skb)
+ dev_kfree_skb_any(ctx->skb);
+ memset(ctx, 0, sizeof(*ctx));
+ descs_used--;
+ }
+
+err_exit:
+ return -EINVAL;
+}
+
static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
struct sk_buff *skb)
{
@@ -1131,7 +1475,8 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
bool mcast = (vring_index == wil->bcast_vring);
uint len = skb_headlen(skb);
- wil_dbg_txrx(wil, "%s()\n", __func__);
+ wil_dbg_txrx(wil, "%s() %d bytes to vring %d\n",
+ __func__, skb->len, vring_index);
if (unlikely(!txdata->enabled))
return -EINVAL;
@@ -1162,14 +1507,14 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
d->mac.d[0] |= (1 << MAC_CFG_DESC_TX_0_MCS_INDEX_POS);
}
/* Process TCP/UDP checksum offloading */
- if (unlikely(wil_tx_desc_offload_cksum_set(wil, d, skb))) {
+ if (unlikely(wil_tx_desc_offload_setup(d, skb))) {
wil_err(wil, "Tx[%2d] Failed to set cksum, drop packet\n",
vring_index);
goto dma_error;
}
vring->ctx[i].nr_frags = nr_frags;
- wil_tx_desc_set_nr_frags(d, nr_frags);
+ wil_tx_desc_set_nr_frags(d, nr_frags + 1);
/* middle segments */
for (; f < nr_frags; f++) {
@@ -1193,7 +1538,7 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
* if it succeeded for 1-st descriptor,
* it will succeed here too
*/
- wil_tx_desc_offload_cksum_set(wil, d, skb);
+ wil_tx_desc_offload_setup(d, skb);
}
/* for the last seg only */
d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_CMD_EOP_POS);
@@ -1224,6 +1569,12 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
wil_dbg_txrx(wil, "Tx[%2d] swhead %d -> %d\n", vring_index, swhead,
vring->swhead);
trace_wil6210_tx(vring_index, swhead, skb->len, nr_frags);
+
+ /* make sure all writes to descriptors (shared memory) are done before
+ * committing them to HW
+ */
+ wmb();
+
iowrite32(vring->swhead, wil->csr + HOSTADDR(vring->hwtail));
return 0;
@@ -1257,8 +1608,12 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
int rc;
spin_lock(&txdata->lock);
- rc = __wil_tx_vring(wil, vring, skb);
+
+ rc = (skb_is_gso(skb) ? __wil_tx_vring_tso : __wil_tx_vring)
+ (wil, vring, skb);
+
spin_unlock(&txdata->lock);
+
return rc;
}
@@ -1385,7 +1740,8 @@ int wil_tx_complete(struct wil6210_priv *wil, int ringid)
struct wil_ctx *ctx = &vring->ctx[vring->swtail];
/**
* For the fragmented skb, HW will set DU bit only for the
- * last fragment. look for it
+ * last fragment. look for it.
+ * In TSO the first DU will include hdr desc
*/
int lf = (vring->swtail + ctx->nr_frags) % vring->size;
/* TODO: check we are not past head */
diff --git a/drivers/net/wireless/ath/wil6210/txrx.h b/drivers/net/wireless/ath/wil6210/txrx.h
index 0c46384..82a8f9a 100644
--- a/drivers/net/wireless/ath/wil6210/txrx.h
+++ b/drivers/net/wireless/ath/wil6210/txrx.h
@@ -291,6 +291,14 @@ struct vring_tx_dma {
__le16 length;
} __packed;
+/* TSO type used in dma descriptor d0 bits 11-12 */
+enum {
+ wil_tso_type_hdr = 0,
+ wil_tso_type_first = 1,
+ wil_tso_type_mid = 2,
+ wil_tso_type_lst = 3,
+};
+
/* Rx descriptor - MAC part
* [dword 0]
* bit 0.. 3 : tid:4 The QoS (b3-0) TID Field
--
2.1.4
Use function wil_fw_error_recovery() instead of inline equivalent code
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/main.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
index 4740edb..cdc82cf 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -271,8 +271,7 @@ static void wil_scan_timer_fn(ulong x)
clear_bit(wil_status_fwready, wil->status);
wil_err(wil, "Scan timeout detected, start fw error recovery\n");
- wil->recovery_state = fw_recovery_pending;
- schedule_work(&wil->fw_error_worker);
+ wil_fw_error_recovery(wil);
}
static int wil_wait_for_recovery(struct wil6210_priv *wil)
--
2.1.4
From: Dedy Lansky <[email protected]>
FW is allowed to generate WMI events that are not handled by this driver.
Treat such case as warning instead of error.
Signed-off-by: Dedy Lansky <[email protected]>
Signed-off-by: Vladimir Kondratiev <[email protected]>
---
drivers/net/wireless/ath/wil6210/wmi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index d3f75de..2cee5c7 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -1362,7 +1362,7 @@ static void wmi_event_handle(struct wil6210_priv *wil,
/* search for handler */
if (!wmi_evt_call_handler(wil, id, evt_data,
len - sizeof(*wmi))) {
- wil_err(wil, "Unhandled event 0x%04x\n", id);
+ wil_info(wil, "Unhandled event 0x%04x\n", id);
}
} else {
wil_err(wil, "Unknown event type\n");
--
2.1.4
On Sunday, July 05, 2015 12:35:16 PM Johannes Berg wrote:
> On Sun, 2015-07-05 at 10:24 +0300, Vladimir Kondratiev wrote:
> > ACS is implemented by registering to QCA vendor ACS sub command.
> > On receive of the command from hostapd, driver issues passive
> > scan command and block until scan results are available.
> > When received, the results are sent up using QCA vendor ACS
> > results event
>
> I'm not convinced that we really want vendor commands upstream in the
> kernel. There at least we could go the extra mile to make proper non
> -vendor APIs, no?
>
> johannes
We are looking into this and will be back soon.
Thanks, Vladimir