From 28120935bc38e3861a885d745ec6ebe3f603611e Mon Sep 17 00:00:00 2001 From: Imre Kaloz Date: Thu, 25 Dec 2014 05:29:17 +0100 Subject: Initial commit (10.2.6.1.p4-20141224) Signed-off-by: Imre Kaloz --- Kconfig | 16 + Makefile | 30 + Makefile.external | 30 + Makefile.kernel | 9 + bin/firmware/7.2.6.1/88W8864.bin | Bin 0 -> 113244 bytes bin/firmware/Marvell_license.txt | 37 + bin/powertable/Mamba/Mamba_FCC_v1.2_5G4TX.ini | 39 + mwl_debug.c | 218 ++ mwl_debug.h | 118 + mwl_dev.h | 482 ++++ mwl_fwcmd.c | 3551 +++++++++++++++++++++++++ mwl_fwcmd.h | 178 ++ mwl_fwdl.c | 219 ++ mwl_fwdl.h | 32 + mwl_mac80211.c | 850 ++++++ mwl_mac80211.h | 35 + mwl_main.c | 1070 ++++++++ mwl_rx.c | 528 ++++ mwl_rx.h | 33 + mwl_sysadpt.h | 65 + mwl_tx.c | 891 +++++++ mwl_tx.h | 37 + test/README | 78 + test/hostapd.conf.1.multi_bssid | 20 + test/hostapd.conf.1.open | 14 + test/hostapd.conf.36.multi_bssid | 20 + test/hostapd.conf.36.open | 14 + test/hostapd.conf.36.open.80mhz | 16 + test/hostapd.conf.36.wpa2pskaes | 18 + test/hostapd.conf.36.wpa2pskaes.80mhz | 21 + test/setup.sh | 4 + test/setup_multi_bssid.sh | 5 + 32 files changed, 8678 insertions(+) create mode 100644 Kconfig create mode 100644 Makefile create mode 100644 Makefile.external create mode 100644 Makefile.kernel create mode 100644 bin/firmware/7.2.6.1/88W8864.bin create mode 100644 bin/firmware/Marvell_license.txt create mode 100644 bin/powertable/Mamba/Mamba_FCC_v1.2_5G4TX.ini create mode 100644 mwl_debug.c create mode 100644 mwl_debug.h create mode 100644 mwl_dev.h create mode 100644 mwl_fwcmd.c create mode 100644 mwl_fwcmd.h create mode 100644 mwl_fwdl.c create mode 100644 mwl_fwdl.h create mode 100644 mwl_mac80211.c create mode 100644 mwl_mac80211.h create mode 100644 mwl_main.c create mode 100644 mwl_rx.c create mode 100644 mwl_rx.h create mode 100644 mwl_sysadpt.h create mode 100644 mwl_tx.c create mode 100644 mwl_tx.h create mode 100644 test/README create mode 100644 test/hostapd.conf.1.multi_bssid create mode 100644 test/hostapd.conf.1.open create mode 100644 test/hostapd.conf.36.multi_bssid create mode 100644 test/hostapd.conf.36.open create mode 100644 test/hostapd.conf.36.open.80mhz create mode 100644 test/hostapd.conf.36.wpa2pskaes create mode 100644 test/hostapd.conf.36.wpa2pskaes.80mhz create mode 100644 test/setup.sh create mode 100644 test/setup_multi_bssid.sh diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..ef3b3b0 --- /dev/null +++ b/Kconfig @@ -0,0 +1,16 @@ +config MWLWIFI + tristate "Marvell Wireless WiFi driver (mwlwifi)" + depends on m + depends on PCI && MAC80211 + select FW_LOADER + ---help--- + Select to build the driver supporting the: + + Marvell Wireless WiFi 88W8864 Chip. + + This driver uses the kernel's mac80211 subsystem. + + If you want to compile the driver as a module (= code which can be + inserted in and removed from the running kernel whenever you want), + say M here and read . The + module will be called mwlwifi. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cb06392 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +obj-m += mwlwifi.o + +mwlwifi-objs += mwl_main.o +mwlwifi-objs += mwl_mac80211.o +mwlwifi-objs += mwl_fwdl.o +mwlwifi-objs += mwl_fwcmd.o +mwlwifi-objs += mwl_tx.o +mwlwifi-objs += mwl_rx.o +mwlwifi-objs += mwl_debug.o + +AS = $(CROSS_COMPILE)as +LD = $(CROSS_COMPILE)ld +CC = $(CROSS_COMPILE)gcc + +ifeq (1, $(MWLDBG)) +EXTRA_CFLAGS+= -DMWL_DEBUG +endif + +EXTRA_CFLAGS+= -I${KDIR} +EXTRA_CFLAGS+= -O2 -funroll-loops + +EXTRA_CFLAGS+= -I${PWD} + +all: + $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules + +clean: + rm -f *.o *.a *.s *.ko *.ko.cmd *.o.cmd *.mod.* .mwlwifi.* + rm -rf modules.order Module.symvers .tmp_versions + find . -name ".*.o.cmd" -exec rm -f {} \; diff --git a/Makefile.external b/Makefile.external new file mode 100644 index 0000000..cb06392 --- /dev/null +++ b/Makefile.external @@ -0,0 +1,30 @@ +obj-m += mwlwifi.o + +mwlwifi-objs += mwl_main.o +mwlwifi-objs += mwl_mac80211.o +mwlwifi-objs += mwl_fwdl.o +mwlwifi-objs += mwl_fwcmd.o +mwlwifi-objs += mwl_tx.o +mwlwifi-objs += mwl_rx.o +mwlwifi-objs += mwl_debug.o + +AS = $(CROSS_COMPILE)as +LD = $(CROSS_COMPILE)ld +CC = $(CROSS_COMPILE)gcc + +ifeq (1, $(MWLDBG)) +EXTRA_CFLAGS+= -DMWL_DEBUG +endif + +EXTRA_CFLAGS+= -I${KDIR} +EXTRA_CFLAGS+= -O2 -funroll-loops + +EXTRA_CFLAGS+= -I${PWD} + +all: + $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules + +clean: + rm -f *.o *.a *.s *.ko *.ko.cmd *.o.cmd *.mod.* .mwlwifi.* + rm -rf modules.order Module.symvers .tmp_versions + find . -name ".*.o.cmd" -exec rm -f {} \; diff --git a/Makefile.kernel b/Makefile.kernel new file mode 100644 index 0000000..8fee3bd --- /dev/null +++ b/Makefile.kernel @@ -0,0 +1,9 @@ +obj-$(CPTCFG_MWLWIFI) += mwlwifi.o + +mwlwifi-objs += mwl_main.o +mwlwifi-objs += mwl_mac80211.o +mwlwifi-objs += mwl_fwdl.o +mwlwifi-objs += mwl_fwcmd.o +mwlwifi-objs += mwl_tx.o +mwlwifi-objs += mwl_rx.o +mwlwifi-objs += mwl_debug.o diff --git a/bin/firmware/7.2.6.1/88W8864.bin b/bin/firmware/7.2.6.1/88W8864.bin new file mode 100644 index 0000000..6aaee12 Binary files /dev/null and b/bin/firmware/7.2.6.1/88W8864.bin differ diff --git a/bin/firmware/Marvell_license.txt b/bin/firmware/Marvell_license.txt new file mode 100644 index 0000000..fe7d6de --- /dev/null +++ b/bin/firmware/Marvell_license.txt @@ -0,0 +1,37 @@ +WIRELESS CORE LIBRARY & FIRMWARE LICENSE TERMS +Copyright (c) 2002-2014 Marvell International Ltd. +All rights reserved. + +Redistribution. Redistribution and use in binary form, without modification, +are permitted provided that the following conditions are met: + +* Redistributions must reproduce the above copyright notice and the following +disclaimer in the documentation and/or other materials provided with the +distribution. + +* Neither the name of Marvell International Ltd. nor the names of its suppliers +may be used to endorse or promote products derived from this software without +specific prior written permission. + +* No reverse engineering, decompilation, or disassembly of this software is +permitted. Limited patent license. Marvell International Ltd. grants a +world-wide, royalty-free, non-exclusive license under patents it now or +hereafter owns or controls to make, have made, use, import, offer to sell and +sell ("Utilize") this software, but solely to the extent that any such patent +is necessary to Utilize the software alone, or in combination with an +operating system licensed under an approved Open Source license as listed by +the Open Source Initiative at http://opensource.org/licenses. The patent +license shall not apply to any other combinations which include this +software. No hardware per se is licensed hereunder. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/bin/powertable/Mamba/Mamba_FCC_v1.2_5G4TX.ini b/bin/powertable/Mamba/Mamba_FCC_v1.2_5G4TX.ini new file mode 100644 index 0000000..11a8304 --- /dev/null +++ b/bin/powertable/Mamba/Mamba_FCC_v1.2_5G4TX.ini @@ -0,0 +1,39 @@ +1 0 0x17 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x0F 0x0F 0x0F 0x0F 0x00 0x00 0x00 0x00 off 0x00F +2 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +3 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +4 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +5 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +6 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +7 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +8 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +9 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +10 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +11 0 0x17 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +12 0 0x17 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +13 0 0x17 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +14 0 0x17 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 off 0x00F +36 0 0x08 0x08 0x08 0x08 0x08 0x08 0x08 0x08 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 off 0x00F +40 0 0x08 0x08 0x08 0x08 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 off 0x00F +44 0 0x08 0x08 0x08 0x08 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 off 0x00F +48 0 0x08 0x08 0x08 0x08 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 0x09 off 0x00F +52 0 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 off 0x00F +56 0 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 off 0x00F +60 0 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 off 0x00F +64 0 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 off 0x00F +100 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +104 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +108 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +112 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +116 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +120 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +124 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +128 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +132 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +136 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +140 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 off 0x00F +149 0 0x16 0x16 0x16 0x16 0x14 0x14 0x14 0x14 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 off 0x00F +153 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 off 0x00F +157 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 off 0x00F +161 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 off 0x00F +165 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 off 0x00F + diff --git a/mwl_debug.c b/mwl_debug.c new file mode 100644 index 0000000..821b0c0 --- /dev/null +++ b/mwl_debug.c @@ -0,0 +1,218 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file implements debug related functions. +* +*/ + +#include +#include +#include + +#include "mwl_debug.h" + +/* CONSTANTS AND MACROS +*/ + +#define WLDBG_CLASSES ( \ + DBG_CLASS_PANIC | \ + DBG_CLASS_ERROR | \ + DBG_CLASS_WARNING | \ + DBG_CLASS_INFO | \ + DBG_CLASS_DATA | \ + DBG_CLASS_ENTER | \ + DBG_CLASS_EXIT) + +/* PRIVATE VARIABLES +*/ + +static u32 dbg_levels = 0; + +/* PUBLIC FUNCTION DEFINITION +*/ + +void mwl_debug_prt(u32 classlevel, const char *func, const char *format, ...) +{ + unsigned char *debug_string; + u32 level = classlevel & 0x0000ffff; + u32 class = classlevel & 0xffff0000; + va_list a_start; + + if (classlevel != 0) { + + if ((class & WLDBG_CLASSES) != class) + return; + + if ((level & dbg_levels) != level) { + + if (class != DBG_CLASS_PANIC && class != DBG_CLASS_ERROR) + return; + } + } + + if ((debug_string = kmalloc(1024, GFP_ATOMIC)) == NULL) + return; + + if (format != NULL) { + + va_start(a_start, format); + vsprintf(debug_string, format, a_start); + va_end(a_start); + + } else { + + debug_string[0] = '\0'; + } + + switch (class) { + + case DBG_CLASS_ENTER: + printk("Enter %s() ...\n", func); + break; + case DBG_CLASS_EXIT: + printk("... Exit %s()\n", func); + break; + case DBG_CLASS_WARNING: + printk("WARNING: "); + break; + case DBG_CLASS_ERROR: + printk("ERROR: "); + break; + case DBG_CLASS_PANIC: + printk("PANIC: "); + break; + default: + break; + } + + if (strlen(debug_string) > 0) { + + if (debug_string[strlen(debug_string)-1] == '\n') + debug_string[strlen(debug_string)-1] = '\0'; + printk("%s(): %s\n", func, debug_string); + } + + kfree(debug_string); +} + +void mwl_debug_prtdata(u32 classlevel, const char *func, const void *data, int len, const char *format, ...) +{ + unsigned char *dbg_string; + unsigned char dbg_data[16] = ""; + unsigned char *memptr = (unsigned char *)data; + u32 level = classlevel & 0x0000ffff; + u32 class = classlevel & 0xffff0000; + int curr_byte = 0; + int num_bytes = 0; + int offset = 0; + va_list a_start; + + if ((class & WLDBG_CLASSES) != class) + return; + + if ((level & dbg_levels) != level) + return; + + if ((dbg_string = kmalloc(len + 1024, GFP_ATOMIC)) == NULL) + return; + + if (format != NULL) { + + va_start(a_start, format); + vsprintf(dbg_string, format, a_start); + va_end(a_start); + + } else { + + dbg_string[0] = '\0'; + } + + if (strlen(dbg_string) > 0) { + + if (dbg_string[strlen(dbg_string) - 1] == '\n') + dbg_string[strlen(dbg_string)-1] = '\0'; + printk("%s() %s\n", func, dbg_string); + + } else { + + printk("%s()\n", func); + } + + for (curr_byte = 0; curr_byte < len; curr_byte = curr_byte + 8) { + + if ((curr_byte + 8) < len) { + + printk("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + *(memptr + curr_byte + 0), + *(memptr + curr_byte + 1), + *(memptr + curr_byte + 2), + *(memptr + curr_byte + 3), + *(memptr + curr_byte + 4), + *(memptr + curr_byte + 5), + *(memptr + curr_byte + 6), + *(memptr + curr_byte + 7)); + } else { + + num_bytes = len - curr_byte; + offset = curr_byte; + for (curr_byte = 0; curr_byte < num_bytes; curr_byte++) { + + sprintf(dbg_data, "0x%02x ", *(memptr + offset + curr_byte)); + strcat(dbg_string, dbg_data); + } + printk("%s\n", dbg_string); + break; + } + } + + kfree(dbg_string); +} + +void mwl_debug_dumpdata(const void *data, int len, char *marker) +{ + unsigned char *memptr = (unsigned char *)data; + int curr_byte = 0; + int num_bytes = 0; + int offset = 0; + + printk("%s\n", marker); + + for (curr_byte = 0; curr_byte < len; curr_byte = curr_byte + 8) { + + if ((curr_byte + 8) < len) { + + printk("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + *(memptr + curr_byte + 0), + *(memptr + curr_byte + 1), + *(memptr + curr_byte + 2), + *(memptr + curr_byte + 3), + *(memptr + curr_byte + 4), + *(memptr + curr_byte + 5), + *(memptr + curr_byte + 6), + *(memptr + curr_byte + 7)); + } else { + + num_bytes = len - curr_byte; + offset = curr_byte; + for (curr_byte = 0; curr_byte < num_bytes; curr_byte++) + printk("0x%02x ", *(memptr + offset + curr_byte)); + printk("\n\n"); + break; + } + } +} diff --git a/mwl_debug.h b/mwl_debug.h new file mode 100644 index 0000000..6e9d74e --- /dev/null +++ b/mwl_debug.h @@ -0,0 +1,118 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file defines debug related functions. +* +*/ + +#ifndef _mwl_debug_h_ +#define _mwl_debug_h_ + +#include + +/* CONSTANTS AND MACROS +*/ + +#define DBG_LEVEL_0 (1<<0) /* mwl_main.c */ +#define DBG_LEVEL_1 (1<<1) /* mwl_fwdl.c */ +#define DBG_LEVEL_2 (1<<2) /* mwl_fwcmd.c */ +#define DBG_LEVEL_3 (1<<3) /* mwl_tx.c */ +#define DBG_LEVEL_4 (1<<4) /* mwl_rx.c */ +#define DBG_LEVEL_5 (1<<5) /* mwl_mac80211.c */ +#define DBG_LEVEL_6 (1<<6) +#define DBG_LEVEL_7 (1<<7) +#define DBG_LEVEL_8 (1<<8) +#define DBG_LEVEL_9 (1<<9) +#define DBG_LEVEL_10 (1<<10) +#define DBG_LEVEL_11 (1<<11) +#define DBG_LEVEL_12 (1<<12) +#define DBG_LEVEL_13 (1<<13) +#define DBG_LEVEL_14 (1<<14) +#define DBG_LEVEL_15 (1<<15) + +#define DBG_CLASS_PANIC (1<<16) +#define DBG_CLASS_ERROR (1<<17) +#define DBG_CLASS_WARNING (1<<18) +#define DBG_CLASS_ENTER (1<<19) +#define DBG_CLASS_EXIT (1<<20) +#define DBG_CLASS_INFO (1<<21) +#define DBG_CLASS_DATA (1<<22) +#define DBG_CLASS_7 (1<<23) +#define DBG_CLASS_8 (1<<24) +#define DBG_CLASS_9 (1<<25) +#define DBG_CLASS_10 (1<<26) +#define DBG_CLASS_11 (1<<27) +#define DBG_CLASS_12 (1<<28) +#define DBG_CLASS_13 (1<<29) +#define DBG_CLASS_14 (1<<30) +#define DBG_CLASS_15 (1<<31) + +#define WLDBG_PRINT(...) \ + mwl_debug_prt(0, __FUNCTION__, __VA_ARGS__) + +#ifdef MWL_DEBUG + +#define WLDBG_DUMP_DATA(classlevel, data, len) \ + mwl_debug_prtdata(classlevel|DBG_CLASS_DATA, __FUNCTION__, data, len, NULL) + +#define WLDBG_ENTER(classlevel) \ + mwl_debug_prt(classlevel|DBG_CLASS_ENTER, __FUNCTION__, NULL) + +#define WLDBG_ENTER_INFO(classlevel, ...) \ + mwl_debug_prt(classlevel|DBG_CLASS_ENTER, __FUNCTION__, __VA_ARGS__) + +#define WLDBG_EXIT(classlevel) \ + mwl_debug_prt(classlevel|DBG_CLASS_EXIT, __FUNCTION__, NULL) + +#define WLDBG_EXIT_INFO(classlevel, ...) \ + mwl_debug_prt(classlevel|DBG_CLASS_EXIT, __FUNCTION__, __VA_ARGS__) + +#define WLDBG_INFO(classlevel, ...) \ + mwl_debug_prt(classlevel|DBG_CLASS_INFO, __FUNCTION__, __VA_ARGS__) + +#define WLDBG_WARNING(classlevel, ...) \ + mwl_debug_prt(classlevel|DBG_CLASS_WARNING, __FUNCTION__, __VA_ARGS__) + +#define WLDBG_ERROR(classlevel, ...) \ + mwl_debug_prt(classlevel|DBG_CLASS_ERROR, __FUNCTION__, __VA_ARGS__) + +#define WLDBG_PANIC(classlevel, ...) \ + mwl_debug_prt(classlevel|DBG_CLASS_PANIC, __FUNCTION__, __VA_ARGS__) + +#else + +#define WLDBG_DUMP_DATA(classlevel, data, len) +#define WLDBG_ENTER(classlevel) +#define WLDBG_ENTER_INFO(classlevel, ...) +#define WLDBG_EXIT(classlevel) +#define WLDBG_EXIT_INFO(classlevel, ...) +#define WLDBG_INFO(classlevel, ...) +#define WLDBG_WARNING(classlevel, ...) +#define WLDBG_ERROR(classlevel, ...) +#define WLDBG_PANIC(classlevel, ...) + +#endif /* MWL_DEBUG */ + +/* PUBLIC FUNCTION DECLARATION +*/ + +void mwl_debug_prt(u32 classlevel, const char *func, const char *format, ...); +void mwl_debug_prtdata(u32 classlevel, const char *func, const void *data, int len, const char *format, ...); +void mwl_debug_dumpdata(const void *data, int len, char *marker); + +#endif /* _mwl_debug_h_ */ diff --git a/mwl_dev.h b/mwl_dev.h new file mode 100644 index 0000000..8c9148e --- /dev/null +++ b/mwl_dev.h @@ -0,0 +1,482 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file defines device related information. +* +*/ + +#ifndef _mwl_dev_h_ +#define _mwl_dev_h_ + +#include +#include +#include +#include +#include + +/* CONSTANTS AND MACROS +*/ + +/* Map to 0x80000000 (Bus control) on BAR0 +*/ +#define MACREG_REG_H2A_INTERRUPT_EVENTS 0x00000C18 /* (From host to ARM) */ +#define MACREG_REG_H2A_INTERRUPT_CAUSE 0x00000C1C /* (From host to ARM) */ +#define MACREG_REG_H2A_INTERRUPT_MASK 0x00000C20 /* (From host to ARM) */ +#define MACREG_REG_H2A_INTERRUPT_CLEAR_SEL 0x00000C24 /* (From host to ARM) */ +#define MACREG_REG_H2A_INTERRUPT_STATUS_MASK 0x00000C28 /* (From host to ARM) */ + +#define MACREG_REG_A2H_INTERRUPT_EVENTS 0x00000C2C /* (From ARM to host) */ +#define MACREG_REG_A2H_INTERRUPT_CAUSE 0x00000C30 /* (From ARM to host) */ +#define MACREG_REG_A2H_INTERRUPT_MASK 0x00000C34 /* (From ARM to host) */ +#define MACREG_REG_A2H_INTERRUPT_CLEAR_SEL 0x00000C38 /* (From ARM to host) */ +#define MACREG_REG_A2H_INTERRUPT_STATUS_MASK 0x00000C3C /* (From ARM to host) */ + +/* Map to 0x80000000 on BAR1 +*/ +#define MACREG_REG_GEN_PTR 0x00000C10 +#define MACREG_REG_INT_CODE 0x00000C14 +#define MACREG_REG_SCRATCH 0x00000C40 +#define MACREG_REG_FW_PRESENT 0x0000BFFC + +/* Bit definitio for MACREG_REG_A2H_INTERRUPT_CAUSE (A2HRIC) +*/ +#define MACREG_A2HRIC_BIT_TX_DONE 0x00000001 /* bit 0 */ +#define MACREG_A2HRIC_BIT_RX_RDY 0x00000002 /* bit 1 */ +#define MACREG_A2HRIC_BIT_OPC_DONE 0x00000004 /* bit 2 */ +#define MACREG_A2HRIC_BIT_MAC_EVENT 0x00000008 /* bit 3 */ +#define MACREG_A2HRIC_BIT_RX_PROBLEM 0x00000010 /* bit 4 */ +#define MACREG_A2HRIC_BIT_RADIO_OFF 0x00000020 /* bit 5 */ +#define MACREG_A2HRIC_BIT_RADIO_ON 0x00000040 /* bit 6 */ +#define MACREG_A2HRIC_BIT_RADAR_DETECT 0x00000080 /* bit 7 - IEEE80211_DH */ +#define MACREG_A2HRIC_BIT_ICV_ERROR 0x00000100 /* bit 8 */ +#define MACREG_A2HRIC_BIT_WEAKIV_ERROR 0x00000200 /* bit 9 */ +#define MACREG_A2HRIC_BIT_QUEUE_EMPTY (1<<10) +#define MACREG_A2HRIC_BIT_QUEUE_FULL (1<<11) +#define MACREG_A2HRIC_BIT_CHAN_SWITCH (1<<12) /* IEEE80211_DH */ +#define MACREG_A2HRIC_BIT_TX_WATCHDOG (1<<13) +#define MACREG_A2HRIC_BA_WATCHDOG (1<<14) +#define MACREG_A2HRIC_BIT_SSU_DONE (1<<16) +#define MACREG_A2HRIC_CONSEC_TXFAIL (1<<17) /* 15 taken by ISR_TXACK */ + +#define ISR_SRC_BITS ((MACREG_A2HRIC_BIT_RX_RDY) | \ + (MACREG_A2HRIC_BIT_TX_DONE) | \ + (MACREG_A2HRIC_BIT_OPC_DONE) | \ + (MACREG_A2HRIC_BIT_MAC_EVENT)| \ + (MACREG_A2HRIC_BIT_WEAKIV_ERROR)| \ + (MACREG_A2HRIC_BIT_ICV_ERROR)| \ + (MACREG_A2HRIC_BIT_SSU_DONE) | \ + (MACREG_A2HRIC_BIT_RADAR_DETECT)| \ + (MACREG_A2HRIC_BIT_CHAN_SWITCH)| \ + (MACREG_A2HRIC_BIT_TX_WATCHDOG)| \ + (MACREG_A2HRIC_BIT_QUEUE_EMPTY)| \ + (MACREG_A2HRIC_BA_WATCHDOG) | \ + (MACREG_A2HRIC_CONSEC_TXFAIL)) + +#define MACREG_A2HRIC_BIT_MASK ISR_SRC_BITS + +/* Bit definitio for MACREG_REG_H2A_INTERRUPT_CAUSE (H2ARIC) +*/ +#define MACREG_H2ARIC_BIT_PPA_READY 0x00000001 /* bit 0 */ +#define MACREG_H2ARIC_BIT_DOOR_BELL 0x00000002 /* bit 1 */ +#define MACREG_H2ARIC_BIT_PS 0x00000004 /* bit 2 */ +#define MACREG_H2ARIC_BIT_PSPOLL 0x00000008 /* bit 3 */ +#define ISR_RESET (1<<15) +#define ISR_RESET_AP33 (1<<26) + +/* Data descriptor related constants +*/ +#define EAGLE_RXD_CTRL_DRIVER_OWN 0x00 +#define EAGLE_RXD_CTRL_OS_OWN 0x04 +#define EAGLE_RXD_CTRL_DMA_OWN 0x80 + +#define EAGLE_RXD_STATUS_IDLE 0x00 +#define EAGLE_RXD_STATUS_OK 0x01 +#define EAGLE_RXD_STATUS_MULTICAST_RX 0x02 +#define EAGLE_RXD_STATUS_BROADCAST_RX 0x04 +#define EAGLE_RXD_STATUS_FRAGMENT_RX 0x08 + +#define EAGLE_TXD_STATUS_IDLE 0x00000000 +#define EAGLE_TXD_STATUS_USED 0x00000001 +#define EAGLE_TXD_STATUS_OK 0x00000001 +#define EAGLE_TXD_STATUS_OK_RETRY 0x00000002 +#define EAGLE_TXD_STATUS_OK_MORE_RETRY 0x00000004 +#define EAGLE_TXD_STATUS_MULTICAST_TX 0x00000008 +#define EAGLE_TXD_STATUS_BROADCAST_TX 0x00000010 +#define EAGLE_TXD_STATUS_FAILED_LINK_ERROR 0x00000020 +#define EAGLE_TXD_STATUS_FAILED_EXCEED_LIMIT 0x00000040 +#define EAGLE_TXD_STATUS_FAILED_AGING 0x00000080 +#define EAGLE_TXD_STATUS_FW_OWNED 0x80000000 + +#define EAGLE_TXD_XMITCTRL_USE_RATEINFO 0x1 +#define EAGLE_TXD_XMITCTRL_DISABLE_AMPDU 0x2 +#define EAGLE_TXD_XMITCTRL_ENABLE_AMPDU 0x4 +#define EAGLE_TXD_XMITCTRL_USE_MC_RATE 0x8 /* Use multicast data rate */ + +#define NBR_BYTES_FW_RX_PREPEND_LEN 2 +#define NBR_BYTES_FW_TX_PREPEND_LEN 2 + +/* Misc +*/ +#define WL_SEC_SLEEP(num_secs) mdelay(num_secs * 1000) +#define WL_MSEC_SLEEP(num_milli_secs) mdelay(num_milli_secs) + +#define ENDIAN_SWAP32(_val) (cpu_to_le32(_val)) +#define ENDIAN_SWAP16(_val) (cpu_to_le16(_val)) + +#define DECLARE_LOCK(l) spinlock_t l +#define SPIN_LOCK_INIT(l) spin_lock_init(l) +#define SPIN_LOCK(l) spin_lock(l) +#define SPIN_UNLOCK(l) spin_unlock(l) +#define SPIN_LOCK_IRQSAVE(l, f) spin_lock_irqsave(l, f) +#define SPIN_UNLOCK_IRQRESTORE(l, f) spin_unlock_irqrestore(l, f) + +/* vif and station +*/ +#define MAX_WEP_KEY_LEN 13 +#define NUM_WEP_KEYS 4 +#define MWL_MAX_TID 8 +#define MWL_VIF(_vif) ((struct mwl_vif *)&((_vif)->drv_priv)) +#define IEEE80211_KEY_CONF(_u8) ((struct ieee80211_key_conf *)(_u8)) +#define MWL_STA(_sta) ((struct mwl_sta *)&((_sta)->drv_priv)) + +/* TYPE DEFINITION +*/ + +enum { + + AP_MODE_11AC = 0x10, /* generic 11ac indication mode */ + AP_MODE_2_4GHZ_11AC_MIXED = 0x17, + +}; + +enum { + + AMPDU_NO_STREAM, + AMPDU_STREAM_NEW, + AMPDU_STREAM_IN_PROGRESS, + AMPDU_STREAM_ACTIVE, + +}; + +enum { + + IEEE_TYPE_MANAGEMENT = 0, + IEEE_TYPE_CONTROL, + IEEE_TYPE_DATA + +}; + +struct mwl_tx_pwr_tbl { + + u8 channel; + u8 setcap; + u16 tx_power[SYSADPT_TX_POWER_LEVEL_TOTAL]; + u8 cdd; /* 0: off, 1: on */ + u16 txantenna2; + +}; + +struct mwl_hw_data { + + u32 fw_release_num; /* MajNbr:MinNbr:SubMin:PatchLevel */ + u8 hw_version; /* plain number indicating version */ + u8 host_interface; /* plain number of interface */ + u16 max_num_tx_desc; /* max number of TX descriptors */ + u16 max_num_mc_addr; /* max number multicast addresses */ + u16 num_antennas; /* number antennas used */ + u16 region_code; /* region (eg. 0x10 for USA FCC) */ + unsigned char mac_addr[ETH_ALEN]; /* well known -> AA:BB:CC:DD:EE:FF */ + +}; + +struct mwl_rate_info { + + u32 format:1; /* 0 = Legacy format, 1 = Hi-throughput format */ + u32 short_gi:1; /* 0 = Use standard guard interval,1 = Use short guard interval */ + u32 bandwidth:1; /* 0 = Use 20 MHz channel,1 = Use 40 MHz channel */ + u32 rate_id_mcs:7; /* = RateID[3:0]; Legacy format,= MCS[5:0]; HT format */ + u32 adv_coding:1; /* AdvCoding 0 = No AdvCoding,1 = LDPC,2 = RS,3 = Reserved */ + u32 ant_select:2; /* Bitmap to select one of the transmit antennae */ + u32 act_sub_chan:2; /* Active subchannel for 40 MHz mode 00:lower, 01= upper, 10= both on lower and upper */ + u32 preamble_type:1; /* Preambletype 0= Long, 1= Short; */ + u32 pid:4; /* Power ID */ + u32 ant2:1; /* bit 2 of antenna selection field */ + u32 ant3:1; + u32 bf:1; /* 0: beam forming off; 1: beam forming on */ + u32 gf:1; /* 0: green field off; 1, green field on */ + u32 count:4; + u32 rsvd2:3; + u32 drop:1; + +} __packed; + +struct mwl_tx_desc { + + u8 data_rate; + u8 tx_priority; + u16 qos_ctrl; + u32 pkt_ptr; + u16 pkt_len; + u8 dest_addr[ETH_ALEN]; + u32 pphys_next; + u32 sap_pkt_info; + struct mwl_rate_info rate_info; + u8 type; + u8 xmit_control; /* bit 0: use rateinfo, bit 1: disable ampdu */ + u16 reserved; + u32 tcpack_sn; + u32 tcpack_src_dst; + struct sk_buff *psk_buff; + struct mwl_tx_desc *pnext; + u32 soft_stat; + u32 ack_wcb_addr; + u8 *sta_info; + u32 status; + +} __packed; + +struct mwl_hw_rssi_info { + + u32 rssi_a:8; + u32 rssi_b:8; + u32 rssi_c:8; + u32 rssi_d:8; + +} __packed; + +struct mwl_hw_noise_floor_info { + + u32 noise_floor_a:8; + u32 noise_floor_b:8; + u32 noise_floor_c:8; + u32 noise_floor_d:8; + +} __packed; + +struct mwl_rx_desc { + + u16 pkt_len; /* total length of received data */ + u8 sq2; /* unused at the moment */ + u8 rate; /* received data rate */ + u32 pphys_buff_data; /* physical address of payload data */ + u32 pphys_next; /* physical address of next RX desc */ + u16 qos_ctrl; /* received QosCtrl field variable */ + u16 ht_sig2; /* like name states */ + struct mwl_hw_rssi_info hw_rssi_info; + struct mwl_hw_noise_floor_info hw_noise_floor_info; + u8 noise_floor; + u8 reserved[3]; + u8 rssi; /* received signal strengt indication */ + u8 status; /* status field containing USED bit */ + u8 channel; /* channel this pkt was received on */ + u8 rx_control; /* the control element of the desc */ + /* above are 32bits aligned and is same as FW, RxControl put at end for sync */ + struct sk_buff *psk_buff; /* associated sk_buff for Linux */ + void *pbuff_data; /* virtual address of payload data */ + struct mwl_rx_desc *pnext; /* virtual address of next RX desc */ + +} __packed; + +struct mwl_desc_data { + + dma_addr_t pphys_tx_ring; /* ptr to first TX desc (phys.) */ + struct mwl_tx_desc *ptx_ring; /* ptr to first TX desc (virt.) */ + struct mwl_tx_desc *pnext_tx_desc; /* next TX desc that can be used */ + struct mwl_tx_desc *pstale_tx_desc;/* the staled TX descriptor */ + dma_addr_t pphys_rx_ring; /* ptr to first RX desc (phys.) */ + struct mwl_rx_desc *prx_ring; /* ptr to first RX desc (virt.) */ + struct mwl_rx_desc *pnext_rx_desc; /* next RX desc that can be used */ + unsigned int wcb_base; /* FW base offset for registers */ + unsigned int rx_desc_write; /* FW descriptor write position */ + unsigned int rx_desc_read; /* FW descriptor read position */ + unsigned int rx_buf_size; /* length of the RX buffers */ + +} __packed; + +struct mwl_locks { + + DECLARE_LOCK(xmit_lock); /* used to protect TX actions */ + DECLARE_LOCK(fwcmd_lock); /* used to protect FW commands */ + DECLARE_LOCK(stream_lock); /* used to protect stream */ + +}; + +struct mwl_ampdu_stream { + + struct ieee80211_sta *sta; + u8 tid; + u8 state; + u8 idx; + +}; + +struct mwl_priv { + + struct ieee80211_hw *hw; + + const struct firmware *fw_ucode; + + struct mwl_tx_pwr_tbl tx_pwr_tbl[SYSADPT_MAX_NUM_CHANNELS]; + u32 cdd; /* 0: off, 1: on */ + u16 txantenna2; + u8 powinited; + u16 max_tx_pow[SYSADPT_TX_POWER_LEVEL_TOTAL]; /* max tx power (dBm) */ + u16 target_powers[SYSADPT_TX_POWER_LEVEL_TOTAL]; /* target powers (dBm) */ + u8 cal_tbl[200]; + + struct pci_dev *pdev; + void *iobase0; /* MEM Base Address Register 0 */ + void *iobase1; /* MEM Base Address Register 1 */ + u32 next_bar_num; + + unsigned short *pcmd_buf; /* pointer to CmdBuf (virtual) */ + dma_addr_t pphys_cmd_buf; /* pointer to CmdBuf (physical) */ + bool in_send_cmd; + + int irq; + + struct mwl_hw_data hw_data; /* Adapter HW specific info */ + + struct mwl_desc_data desc_data[SYSADPT_NUM_OF_DESC_DATA]; /* various descriptor data */ + struct sk_buff_head txq[SYSADPT_NUM_OF_DESC_DATA]; + struct sk_buff_head delay_freeq; + int fw_desc_cnt[SYSADPT_NUM_OF_DESC_DATA]; /* number of descriptors owned by fw at any one time */ + + struct mwl_locks locks; /* various spinlocks */ + + struct tasklet_struct tx_task; + struct tasklet_struct rx_task; + int txq_limit; + bool is_tx_schedule; + int recv_limit; + bool is_rx_schedule; + s8 noise; /* Most recently reported noise in dBm */ + + struct ieee80211_supported_band band_24; + struct ieee80211_channel channels_24[14]; + struct ieee80211_rate rates_24[13]; + struct ieee80211_supported_band band_50; + struct ieee80211_channel channels_50[24]; + struct ieee80211_rate rates_50[8]; + + u32 ap_macids_supported; + u32 sta_macids_supported; + u32 macids_used; + struct list_head vif_list; /* List of interfaces. */ + u32 running_bsses; /* bitmap of running BSSes */ + + bool radio_on; + bool radio_short_preamble; + + bool wmm_enabled; + struct ieee80211_tx_queue_params wmm_params[SYSADPT_TX_WMM_QUEUES]; + + /* Ampdu stream information */ + u8 num_ampdu_queues; + struct mwl_ampdu_stream ampdu[SYSADPT_TX_AMPDU_QUEUES]; + struct work_struct watchdog_ba_handle; + +}; + +struct beacon_info { + + bool valid; + u16 cap_info; + u8 bss_basic_rate_set[SYSADPT_MAX_DATA_RATES_G]; + u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G]; + u8 ie_wmm_len; /* Keep WMM IE */ + u8 *ie_wmm_ptr; + u8 ie_rsn_len; /* Keep WPA IE */ + u8 *ie_rsn_ptr; + u8 ie_rsn48_len; /* Keep WPA2 IE */ + u8 *ie_rsn48_ptr; + u8 ie_ht_len; /* Keep HT IE */ + u8 *ie_ht_ptr; + u8 ie_list_ht[148]; + u8 ie_vht_len; /* Keep VHT IE */ + u8 *ie_vht_ptr; + u8 ie_list_vht[24]; +}; + +struct mwl_vif { + + struct list_head list; + struct ieee80211_vif *vif; + + int macid; /* Firmware macid for this vif. */ + + u16 seqno; /* Non AMPDU sequence number assigned by driver. */ + + struct { /* Saved WEP keys */ + u8 enabled; + u8 key[sizeof(struct ieee80211_key_conf) + MAX_WEP_KEY_LEN]; + } wep_key_conf[NUM_WEP_KEYS]; + + u8 bssid[ETH_ALEN]; /* BSSID */ + + bool is_hw_crypto_enabled; /* A flag to indicate is HW crypto is enabled for this bssid */ + + struct beacon_info beacon_info; + + u16 iv16; + u32 iv32; + s8 keyidx; + +}; + +struct mwl_tx_info { + + u32 start_time; + u32 pkts; + +}; + +struct mwl_sta { + + u8 is_ampdu_allowed; + struct mwl_tx_info tx_stats[MWL_MAX_TID]; + + u16 iv16; + u32 iv32; + +}; + +/* DMA header used by firmware and hardware. +*/ +struct mwl_dma_data { + + u16 fwlen; + struct ieee80211_hdr wh; + char data[0]; + +} __packed; + +/* Transmission information to transmit a socket buffer. + */ +struct mwl_tx_ctrl { + + u8 tx_priority; + u16 qos_ctrl; + u8 type; + u8 xmit_control; + u8 *sta_info; + bool ccmp; + +} __packed; + +#endif /* _mwl_dev_h_ */ diff --git a/mwl_fwcmd.c b/mwl_fwcmd.c new file mode 100644 index 0000000..d549c76 --- /dev/null +++ b/mwl_fwcmd.c @@ -0,0 +1,3551 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file implements frimware host command related functions. +* +*/ + +#include "mwl_sysadpt.h" +#include "mwl_dev.h" +#include "mwl_debug.h" +#include "mwl_fwcmd.h" + +/* CONSTANTS AND MACROS +*/ + +#define MAX_WAIT_FW_COMPLETE_ITERATIONS 10000 + +/* + * 16 bit host command code + */ +#define HOSTCMD_CMD_GET_HW_SPEC 0x0003 +#define HOSTCMD_CMD_SET_HW_SPEC 0x0004 +#define HOSTCMD_CMD_802_11_GET_STAT 0x0014 +#define HOSTCMD_CMD_802_11_RADIO_CONTROL 0x001c +#define HOSTCMD_CMD_802_11_TX_POWER 0x001f +#define HOSTCMD_CMD_802_11_RF_ANTENNA 0x0020 +#define HOSTCMD_CMD_BROADCAST_SSID_ENABLE 0x0050 /* per-vif */ +#define HOSTCMD_CMD_SET_RF_CHANNEL 0x010a +#define HOSTCMD_CMD_802_11_RTS_THSD 0x0113 +#define HOSTCMD_CMD_SET_EDCA_PARAMS 0x0115 +#define HOSTCMD_CMD_SET_WMM_MODE 0x0123 +#define HOSTCMD_CMD_SET_FIXED_RATE 0x0126 +#define HOSTCMD_CMD_SET_IES 0x0127 +#define HOSTCMD_CMD_SET_RATE_ADAPT_MODE 0x0203 +#define HOSTCMD_CMD_GET_WATCHDOG_BITMAP 0x0205 +#define HOSTCMD_CMD_BSS_START 0x1100 /* per-vif */ +#define HOSTCMD_CMD_AP_BEACON 0x1101 /* per-vif */ +#define HOSTCMD_CMD_SET_NEW_STN 0x1111 /* per-vif */ +#define HOSTCMD_CMD_SET_APMODE 0x1114 +#define HOSTCMD_CMD_UPDATE_ENCRYPTION 0x1122 /* per-vif */ +#define HOSTCMD_CMD_BASTREAM 0x1125 +#define HOSTCMD_CMD_DWDS_ENABLE 0x1144 +#define HOSTCMD_CMD_FW_FLUSH_TIMER 0x1148 +#define HOSTCMD_CMD_SET_CDD 0x1150 + +/* + * Define general result code for each command + */ +#define HOSTCMD_RESULT_OK 0x0000 /* OK */ +#define HOSTCMD_RESULT_ERROR 0x0001 /* Genenral error */ +#define HOSTCMD_RESULT_NOT_SUPPORT 0x0002 /* Command is not valid */ +#define HOSTCMD_RESULT_PENDING 0x0003 /* Command is pending (will be processed) */ +#define HOSTCMD_RESULT_BUSY 0x0004 /* System is busy (command ignored) */ +#define HOSTCMD_RESULT_PARTIAL_DATA 0x0005 /* Data buffer is not big enough */ + +/* + * Define channel related constants + */ +#define FREQ_BAND_2DOT4GHZ 0x1 +#define FREQ_BAND_4DOT9GHZ 0x2 +#define FREQ_BAND_5GHZ 0x4 +#define FREQ_BAND_5DOT2GHZ 0x8 +#define CH_AUTO_WIDTH 0 +#define CH_10_MHz_WIDTH 0x1 +#define CH_20_MHz_WIDTH 0x2 +#define CH_40_MHz_WIDTH 0x4 +#define CH_80_MHz_WIDTH 0x5 +#define EXT_CH_ABOVE_CTRL_CH 0x1 +#define EXT_CH_AUTO 0x2 +#define EXT_CH_BELOW_CTRL_CH 0x3 +#define NO_EXT_CHANNEL 0x0 + +#define ACT_PRIMARY_CHAN_0 0 /* active primary 1st 20MHz channel */ +#define ACT_PRIMARY_CHAN_1 1 /* active primary 2nd 20MHz channel */ +#define ACT_PRIMARY_CHAN_2 2 /* active primary 3rd 20MHz channel */ +#define ACT_PRIMARY_CHAN_3 3 /* active primary 4th 20MHz channel */ +#define ACT_PRIMARY_CHAN_4 4 /* active primary 5th 20MHz channel */ +#define ACT_PRIMARY_CHAN_5 5 /* active primary 6th 20MHz channel */ +#define ACT_PRIMARY_CHAN_6 6 /* active primary 7th 20MHz channel */ +#define ACT_PRIMARY_CHAN_7 7 /* active primary 8th 20MHz channel */ + +/* + * Define rate related constants + */ +#define HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002 + +/* + * Define station related constants + */ +#define HOSTCMD_ACT_STA_ACTION_ADD 0 +#define HOSTCMD_ACT_STA_ACTION_REMOVE 2 + +/* + * Define key related constants + */ +#define MAX_ENCR_KEY_LENGTH 16 +#define MIC_KEY_LENGTH 8 + +#define KEY_TYPE_ID_WEP 0x00 /* Key type is WEP */ +#define KEY_TYPE_ID_TKIP 0x01 /* Key type is TKIP */ +#define KEY_TYPE_ID_AES 0x02 /* Key type is AES-CCMP */ + +#define ENCR_KEY_FLAG_TXGROUPKEY 0x00000004 /* Group key for TX */ +#define ENCR_KEY_FLAG_PAIRWISE 0x00000008 /* pairwise */ +#define ENCR_KEY_FLAG_TSC_VALID 0x00000040 /* Sequence counters are valid */ +#define ENCR_KEY_FLAG_WEP_TXKEY 0x01000000 /* Tx key for WEP */ +#define ENCR_KEY_FLAG_MICKEY_VALID 0x02000000 /* Tx/Rx MIC keys are valid */ + +/* + * Define block ack related constants + */ +#define BASTREAM_FLAG_IMMEDIATE_TYPE 1 +#define BASTREAM_FLAG_DIRECTION_UPSTREAM 0 + +/* + * Define general purpose action + */ +#define HOSTCMD_ACT_GEN_SET 0x0001 +#define HOSTCMD_ACT_GEN_SET_LIST 0x0002 +#define HOSTCMD_ACT_GEN_GET_LIST 0x0003 + +/* Misc +*/ +#define MWL_SPIN_LOCK(X) SPIN_LOCK_IRQSAVE(X, flags) +#define MWL_SPIN_UNLOCK(X) SPIN_UNLOCK_IRQRESTORE(X, flags) + +#define MAX_ENCR_KEY_LENGTH 16 +#define MIC_KEY_LENGTH 8 + +/* TYPE DEFINITION +*/ + +enum { + + WL_DISABLE = 0, + WL_ENABLE = 1, + WL_DISABLE_VMAC = 0x80, + +}; + +enum { + + WL_GET = 0, + WL_SET = 1, + WL_RESET = 2, + +}; + +enum { + + WL_LONG_PREAMBLE = 1, + WL_SHORT_PREAMBLE = 3, + WL_AUTO_PREAMBLE = 5, + +}; + +enum encr_action_type { + + /* request to enable/disable HW encryption */ + ENCR_ACTION_ENABLE_HW_ENCR, + /* request to set encryption key */ + ENCR_ACTION_TYPE_SET_KEY, + /* request to remove one or more keys */ + ENCR_ACTION_TYPE_REMOVE_KEY, + ENCR_ACTION_TYPE_SET_GROUP_KEY, + +}; + +enum ba_action_type { + + BA_CREATE_STREAM, + BA_UPDATE_STREAM, + BA_DESTROY_STREAM, + BA_FLUSH_STREAM, + BA_CHECK_STREAM, + +}; + +/* + * General host command header + */ + +struct hostcmd_header { + + u16 cmd; + u16 len; + u8 seq_num; + u8 macid; + u16 result; + +} __packed; + +/* + * HOSTCMD_CMD_GET_HW_SPEC + */ + +struct hostcmd_cmd_get_hw_spec { + + struct hostcmd_header cmd_hdr; + u8 version; /* version of the HW */ + u8 host_if; /* host interface */ + u16 num_wcb; /* Max. number of WCB FW can handle */ + u16 num_mcast_addr; /* MaxNbr of MC addresses FW can handle */ + u8 permanent_addr[ETH_ALEN]; /* MAC address programmed in HW */ + u16 region_code; + u16 num_antenna; /* Number of antenna used */ + u32 fw_release_num; /* 4 byte of FW release number */ + u32 wcb_base0; + u32 rxpd_wr_ptr; + u32 rxpd_rd_ptr; + u32 fw_awake_cookie; + u32 wcb_base[SYSADPT_TOTAL_TX_QUEUES - 1]; + +} __packed; + +/* + * HOSTCMD_CMD_SET_HW_SPEC + */ + +struct hostcmd_cmd_set_hw_spec { + + struct hostcmd_header cmd_hdr; + u8 version; /* HW revision */ + u8 host_if; /* Host interface */ + u16 num_mcast_addr; /* Max. number of Multicast address FW can handle */ + u8 permanent_addr[ETH_ALEN]; /* MAC address */ + u16 region_code; /* Region Code */ + u32 fw_release_num; /* 4 byte of FW release number, example 0x1234=1.2.3.4 */ + u32 fw_awake_cookie; /* Firmware awake cookie - used to ensure that the device is not in sleep mode */ + u32 device_caps; /* Device capabilities (see above) */ + u32 rxpd_wr_ptr; /* Rx shared memory queue */ + u32 num_tx_queues; /* Actual number of TX queues in WcbBase array */ + u32 wcb_base[SYSADPT_NUM_OF_DESC_DATA]; /* TX WCB Rings */ + u32 max_amsdu_size:2; /* Max AMSDU size (00 - AMSDU Disabled, 01 - 4K, 10 - 8K, 11 - not defined) */ + u32 implicit_ampdu_ba:1; /* Indicates supported AMPDU type (1 = implicit, 0 = explicit (default)) */ + u32 disablembss:1; /* indicates mbss features disable in FW */ + u32 host_form_beacon:1; + u32 host_form_probe_response:1; + u32 host_power_save:1; + u32 host_encr_decr_mgt:1; + u32 host_intra_bss_offload:1; + u32 host_iv_offload:1; + u32 host_encr_decr_frame:1; + u32 reserved: 21; /* Reserved */ + u32 tx_wcb_num_per_queue; + u32 total_rx_wcb; + +} __packed; + +/* + * HOSTCMD_CMD_802_11_GET_STAT + */ + +struct hostcmd_cmd_802_11_get_stat { + + struct hostcmd_header cmd_hdr; + u32 tx_retry_successes; + u32 tx_multiple_retry_successes; + u32 tx_failures; + u32 rts_successes; + u32 rts_failures; + u32 ack_failures; + u32 rx_duplicate_frames; + u32 rx_fcs_errors; + u32 tx_watchdog_timeouts; + u32 rx_overflows; + u32 rx_frag_errors; + u32 rx_mem_errors; + u32 pointer_errors; + u32 tx_underflows; + u32 tx_done; + u32 tx_done_buf_try_put; + u32 tx_done_buf_put; + u32 wait_for_tx_buf; /* Put size of requested buffer in here */ + u32 tx_attempts; + u32 tx_successes; + u32 tx_fragments; + u32 tx_multicasts; + u32 rx_non_ctl_pkts; + u32 rx_multicasts; + u32 rx_undecryptable_frames; + u32 rx_icv_errors; + u32 rx_excluded_frames; + u32 rx_weak_iv_count; + u32 rx_unicasts; + u32 rx_bytes; + u32 rx_errors; + u32 rx_rts_count; + u32 tx_cts_count; + +} __packed; + +/* + * HOSTCMD_CMD_802_11_RADIO_CONTROL + */ + +struct hostcmd_cmd_802_11_radio_control { + + struct hostcmd_header cmd_hdr; + u16 action; + u16 control; /* @bit0: 1/0,on/off, @bit1: 1/0, long/short @bit2: 1/0,auto/fix */ + u16 radio_on; + +} __packed; + +/* + * HOSTCMD_CMD_802_11_TX_POWER + */ + +struct hostcmd_cmd_802_11_tx_power { + + struct hostcmd_header cmd_hdr; + u16 action; + u16 band; + u16 ch; + u16 bw; + u16 sub_ch; + u16 power_level_list[SYSADPT_TX_POWER_LEVEL_TOTAL]; + +} __packed; + +/* + * HOSTCMD_CMD_802_11_RF_ANTENNA + */ + +struct hostcmd_cmd_802_11_rf_antenna { + + struct hostcmd_header cmd_hdr; + u16 action; + u16 antenna_mode; /* Number of antennas or 0xffff(diversity) */ + +} __packed; + +/* + * HOSTCMD_CMD_BROADCAST_SSID_ENABLE + */ + +struct hostcmd_cmd_broadcast_ssid_enable { + + struct hostcmd_header cmd_hdr; + u32 enable; + +} __packed; + +/* + * HOSTCMD_CMD_SET_RF_CHANNEL + */ + +struct chnl_flags_11ac { + + u32 freq_band:6; /* bit0=1: 2.4GHz,bit1=1: 4.9GHz,bit2=1: 5GHz,bit3=1: 5.2GHz, */ + u32 chnl_width:5; /* bit6=1:10MHz, bit7=1:20MHz, bit8=1:40MHz */ + u32 act_primary:3; /* 000: 1st 20MHz chan, 001:2nd 20MHz chan, 011:3rd 20MHz chan, 100:4th 20MHz chan */ + u32 reserved:18; + +} __packed; + +struct hostcmd_cmd_set_rf_channel { + + struct hostcmd_header cmd_hdr; + u16 action; + u8 curr_chnl; + struct chnl_flags_11ac chnl_flags; + +} __packed; + +/* + * HOSTCMD_CMD_802_11_RTS_THSD + */ + +struct hostcmd_cmd_802_11_rts_thsd { + + struct hostcmd_header cmd_hdr; + u16 action; + u16 threshold; + +} __packed; + +/* + * HOSTCMD_CMD_SET_EDCA_PARAMS + */ + +struct hostcmd_cmd_set_edca_params { + + struct hostcmd_header cmd_hdr; + u16 action; /* 0 = get all, 0x1 =set CWMin/Max, 0x2 = set TXOP , 0x4 =set AIFSN */ + u16 txop; /* in unit of 32 us */ + u32 cw_max; /* 0~15 */ + u32 cw_min; /* 0~15 */ + u8 aifsn; + u8 txq_num; /* Tx Queue number. */ + +} __packed; + +/* + * HOSTCMD_CMD_SET_WMM_MODE + */ + +struct hostcmd_cmd_set_wmm_mode { + + struct hostcmd_header cmd_hdr; + u16 action; /* 0->unset, 1->set */ + +} __packed; + +/* + * HOSTCMD_CMD_SET_FIXED_RATE + */ + +struct fix_rate_flag { + /* lower rate after the retry count */ + u32 fix_rate_type; /* 0: legacy, 1: HT */ + u32 retry_count_valid; /* 0: retry count is not valid, 1: use retry count specified */ + +} __packed; + +struct fix_rate_entry { + + struct fix_rate_flag fix_rate_type_flags; + u32 fixed_rate; /* depending on the flags above, this can be either a legacy rate(not index) or an MCS code. */ + u32 retry_count; + +} __packed; + +struct hostcmd_cmd_set_fixed_rate { + + struct hostcmd_header cmd_hdr; + u32 action; /* HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002 */ + u32 allow_rate_drop; /* use fixed rate specified but firmware can drop to */ + u32 entry_count; + struct fix_rate_entry fixed_rate_table[4]; + u8 multicast_rate; + u8 multi_rate_tx_type; + u8 management_rate; + +} __packed; + +/* + * HOSTCMD_CMD_SET_IES + */ + +struct hostcmd_cmd_set_ies { + + struct hostcmd_header cmd_hdr; + u16 action; /* 0->unset, 1->set */ + u16 ie_list_len_ht; + u16 ie_list_len_vht; + u16 ie_list_len_proprietary; + /*Buffer size same as Generic_Beacon*/ + u8 ie_list_ht[148]; + u8 ie_list_vht[24]; + u8 ie_list_proprietary[112]; + +} __packed; + +/* + * HOSTCMD_CMD_SET_RATE_ADAPT_MODE + */ + +struct hostcmd_cmd_set_rate_adapt_mode { + + struct hostcmd_header cmd_hdr; + u16 action; + u16 rate_adapt_mode; /* 0:Indoor, 1:Outdoor */ + +} __packed; + +/* + * HOSTCMD_CMD_GET_WATCHDOG_BITMAP + */ + +struct hostcmd_cmd_get_watchdog_bitmap { + + struct hostcmd_header cmd_hdr; + u8 watchdog_bitmap; /* for SW/BA */ + +} __packed; + +/* + * HOSTCMD_CMD_BSS_START + */ + +struct hostcmd_cmd_bss_start { + + struct hostcmd_header cmd_hdr; + u32 enable; /* FALSE: Disable or TRUE: Enable */ + +} __packed; + +/* + * HOSTCMD_CMD_AP_BEACON + */ + +struct cf_params { + + u8 elem_id; + u8 len; + u8 cfp_cnt; + u8 cfp_period; + u16 cfp_max_duration; + u16 cfp_duration_remaining; + +} __packed; + +struct ibss_params { + + u8 elem_id; + u8 len; + u16 atim_window; + +} __packed; + +union ss_params { + + struct cf_params cf_param_set; + struct ibss_params ibss_param_set; + +} __packed; + +struct fh_params { + + u8 elem_id; + u8 len; + u16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; + +} __packed; + +struct ds_params { + + u8 elem_id; + u8 len; + u8 current_chnl; + +} __packed; + +union phy_params { + + struct fh_params fh_param_set; + struct ds_params ds_param_set; + +} __packed; + +struct rsn_ie { + + u8 elem_id; + u8 len; + u8 oui_type[4]; /* 00:50:f2:01 */ + u8 ver[2]; + u8 grp_key_cipher[4]; + u8 pws_key_cnt[2]; + u8 pws_key_cipher_list[4]; + u8 auth_key_cnt[2]; + u8 auth_key_list[4]; + +} __packed; + +struct rsn48_ie { + + u8 elem_id; + u8 len; + u8 ver[2]; + u8 grp_key_cipher[4]; + u8 pws_key_cnt[2]; + u8 pws_key_cipher_list[4]; + u8 auth_key_cnt[2]; + u8 auth_key_list[4]; + u8 rsn_cap[2]; + +} __packed; + +struct aci_aifsn_field { + + u8 aifsn:4; + u8 acm:1; + u8 aci:2; + u8 rsvd:1; + +} __packed; + +struct ecw_min_max_field { + + u8 ecw_min:4; + u8 ecw_max:4; + +} __packed; + +struct ac_param_rcd { + + struct aci_aifsn_field aci_aifsn; + struct ecw_min_max_field ecw_min_max; + u16 txop_lim; + +} __packed; + +struct wmm_param_elem { + + u8 elem_id; + u8 len; + u8 oui[3]; + u8 type; + u8 sub_type; + u8 version; + u8 rsvd; + struct ac_param_rcd ac_be; + struct ac_param_rcd ac_bk; + struct ac_param_rcd ac_vi; + struct ac_param_rcd ac_vo; + +} __packed; + +struct channel_info { + + u8 first_channel_num; + u8 num_channels; + u8 max_tx_pwr_level; + +} __packed; + +struct country { + + u8 elem_id; + u8 len; + u8 country_str[3]; + struct channel_info channel_info[40]; + +} __packetd; + +struct start_cmd { + + u8 sta_mac_addr[ETH_ALEN]; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 bss_type; + u16 bcn_period; + u8 dtim_period; + union ss_params ss_param_set; + union phy_params phy_param_set; + u16 probe_delay; + u16 cap_info; + u8 bss_basic_rate_set[SYSADPT_MAX_DATA_RATES_G]; + u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G]; + struct rsn_ie rsn_ie; + struct rsn48_ie rsn48_ie; + struct wmm_param_elem wmm_param; + struct country country; + u32 ap_rf_type; /* 0->B, 1->G, 2->Mixed, 3->A, 4->11J */ + +} __packed; + +struct hostcmd_cmd_ap_beacon { + + struct hostcmd_header cmd_hdr; + struct start_cmd start_cmd; + +} __packed; + +/* + * HOSTCMD_CMD_SET_NEW_STN + */ + +struct cap_info { + + u16 ess:1; + u16 ibss:1; + u16 cf_pollable:1; + u16 cf_poll_rqst:1; + u16 privacy:1; + u16 short_preamble:1; + u16 pbcc:1; + u16 chan_agility:1; + u16 spectrum_mgmt:1; + u16 qoS:1; + u16 short_slot_time:1; + u16 apsd:1; + u16 rsrvd1:1; + u16 dsss_ofdm:1; + u16 block_ack:1; + u16 rsrvd2:1; + +} __packed; + +struct add_ht_chnl { + + u8 ext_chnl_offset:2; + u8 sta_chnl_width:1; + u8 rifs_mode:1; + u8 psmp_stas_only:1; + u8 srvc_intvl_gran:3; + +} __packed; + +struct add_ht_op_mode { + + u16 op_mode:2; + u16 non_gf_sta_present:1; + u16 rrans_burst_limit:1; + u16 non_ht_sta_present:1; + u16 rsrv:11; + +} __packed; + +struct add_ht_stbc { + + u16 bsc_stbc:7; + u16 dual_stbc_proc:1; + u16 scd_bcn:1; + u16 lsig_txop_proc_full_sup:1; + u16 pco_active:1; + u16 pco_phase:1; + u16 rsrv:4; + +} __packed; + +struct add_ht_info { + + u8 control_chnl; + struct add_ht_chnl add_chnl; + struct add_ht_op_mode op_mode; + struct add_ht_stbc stbc; + +} __packed; + +struct peer_info { + + u32 legacy_rate_bitmap; + u8 ht_rates[4]; + struct cap_info cap_info; + u16 ht_cap_info; + u8 mac_ht_param_info; + u8 mrvl_sta; + struct add_ht_info add_ht_info; + u32 tx_bf_capabilities; /* EXBF_SUPPORT */ + u32 vht_max_rx_mcs; + u32 vht_cap; + u8 vht_rx_channel_width; /* 0:20Mhz, 1:40Mhz, 2:80Mhz, 3:160 or 80+80Mhz */ + +} __packed; + +struct hostcmd_cmd_set_new_stn { + + struct hostcmd_header cmd_hdr; + u16 aid; + u8 mac_addr[ETH_ALEN]; + u16 stn_id; + u16 action; + u16 reserved; + struct peer_info peer_info; + /* UAPSD_SUPPORT */ + u8 qos_info; + u8 is_qos_sta; + u32 fw_sta_ptr; + +} __packed; + +/* + * HOSTCMD_CMD_SET_APMODE + */ + +struct hostcmd_cmd_set_apmode { + + struct hostcmd_header cmd_hdr; + u8 apmode; + +} __packed; + +/* + * HOSTCMD_CMD_UPDATE_ENCRYPTION + */ + +struct hostcmd_cmd_update_encryptoin { + + struct hostcmd_header cmd_hdr; + /* Action type - see encr_action_type */ + u32 action_type; /* encr_action_type */ + /* size of the data buffer attached. */ + u32 data_length; + u8 mac_addr[ETH_ALEN]; + u8 action_data[1]; + +} __packed; + +struct wep_type_key { + + /* WEP key material (max 128bit) */ + u8 key_material[MAX_ENCR_KEY_LENGTH]; + +} __packed; + +struct encr_tkip_seqcnt { + + u16 low; + u32 high; + +} __packed; + +struct tkip_type_key { + + /* TKIP Key material. Key type (group or pairwise key) is determined by flags */ + /* in KEY_PARAM_SET structure. */ + u8 key_material[MAX_ENCR_KEY_LENGTH]; + /* MIC keys */ + u8 tkip_tx_mic_key[MIC_KEY_LENGTH]; + u8 tkip_rx_mic_key[MIC_KEY_LENGTH]; + struct encr_tkip_seqcnt tkip_rsc; + struct encr_tkip_seqcnt tkip_tsc; + +} __packed; + +struct aes_type_key { + + /* AES Key material */ + u8 key_material[MAX_ENCR_KEY_LENGTH]; + +} __packed; + +union key_type { + + struct wep_type_key wep_key; + struct tkip_type_key tkip_key; + struct aes_type_key aes_key; + +} __packed; + +struct key_param_set { + + /* Total length of this structure (Key is variable size array) */ + u16 length; + /* Key type - WEP, TKIP or AES-CCMP. */ + /* See definitions above */ + u16 key_type_id; + /* key flags (ENCR_KEY_FLAG_XXX_ */ + u32 key_info; + /* For WEP only - actual key index */ + u32 key_index; + /* Size of the key */ + u16 key_len; + /* Key material (variable size array) */ + union key_type key; + u8 mac_addr[ETH_ALEN]; + +} __packed; + +struct hostcmd_cmd_set_key { + + struct hostcmd_header cmd_hdr; + /* Action type - see encr_action_type */ + u32 action_type; /* encr_action_type */ + /* size of the data buffer attached. */ + u32 data_length; + /* data buffer - maps to one KEY_PARAM_SET structure */ + struct key_param_set key_param; + +} __packed; + +/* + * HOSTCMD_CMD_BASTREAM + */ + +struct ba_stream_flags { + + u32 ba_type:1; + u32 ba_direction:3; + u32 reserved:24; + +} __packed; + +struct ba_context { + + u32 context; + +} __packed; + +/* parameters for block ack creation */ +struct create_ba_params { + /* BA Creation flags - see above */ + struct ba_stream_flags flags; + /* idle threshold */ + u32 idle_thrs; + /* block ack transmit threshold (after how many pkts should we send BAR?) */ + u32 bar_thrs; + /* receiver window size */ + u32 window_size; + /* MAC Address of the BA partner */ + u8 peer_mac_addr[ETH_ALEN]; + /* Dialog Token */ + u8 dialog_token; + /* TID for the traffic stream in this BA */ + u8 tid; + /* shared memory queue ID (not sure if this is required) */ + u8 queue_id; + u8 param_info; + /* returned by firmware - firmware context pointer. */ + /* this context pointer will be passed to firmware for all future commands. */ + struct ba_context fw_ba_context; + u8 reset_seq_no; /** 0 or 1**/ + u16 current_seq; + u8 sta_src_mac_addr[ETH_ALEN]; /* This is for virtual station in Sta proxy mode for V6FW */ + +} __packed; + +/* new transmit sequence number information */ +struct ba_update_seq_num { + + /* BA flags - see above */ + struct ba_stream_flags flags; + /* returned by firmware in the create ba stream response */ + struct ba_context fw_ba_context; + /* new sequence number for this block ack stream */ + u16 ba_seq_num; + +} __packed; + +struct ba_stream_context { + + /* BA Stream flags */ + struct ba_stream_flags flags; + /* returned by firmware in the create ba stream response */ + struct ba_context fw_ba_context; + +} __packed; + +union ba_info { + + /* information required to create BA Stream... */ + struct create_ba_params create_params; + /* update starting/new sequence number etc. */ + struct ba_update_seq_num updt_seq_num; + /* destroy an existing stream... */ + struct ba_stream_context destroy_params; + /* destroy an existing stream... */ + struct ba_stream_context flush_params; + +} __packed; + +struct hostcmd_cmd_bastream { + + struct hostcmd_header cmd_hdr; + u32 action_type; + union ba_info ba_info; + +} __packed; + +/* + * HOSTCMD_CMD_DWDS_ENABLE + */ + +struct hostcmd_cmd_dwds_enable { + + struct hostcmd_header cmd_hdr; + u32 enable; /* 0 -- Disable. or 1 -- Enable. */ + +} __packed; + +/* + * HOSTCMD_CMD_FW_FLUSH_TIMER + */ + +struct hostcmd_cmd_fw_flush_timer { + + struct hostcmd_header cmd_hdr; + u32 value; /* 0 -- Disable. > 0 -- holds time value in usecs. */ + +} __packed; + +/* + * HOSTCMD_CMD_SET_CDD + */ + +struct hostcmd_cmd_set_cdd { + + struct hostcmd_header cmd_hdr; + u32 enable; + +} __packed; + +/* PRIVATE FUNCTION DECLARATION +*/ + +static bool mwl_fwcmd_chk_adapter(struct mwl_priv *priv); +static int mwl_fwcmd_exec_cmd(struct mwl_priv *priv, unsigned short cmd); +static void mwl_fwcmd_send_cmd(struct mwl_priv *priv); +static int mwl_fwcmd_wait_complete(struct mwl_priv *priv, unsigned short cmd); + +static int mwl_fwcmd_802_11_radio_control(struct mwl_priv *priv, bool enable, bool force); +static int mwl_fwcmd_get_tx_powers(struct mwl_priv *priv, u16 *powlist, u16 ch, + u16 band, u16 width, u16 sub_ch); +static int mwl_fwcmd_set_tx_powers(struct mwl_priv *priv, u16 txpow[], u8 action, + u16 ch, u16 band, u16 width, u16 sub_ch); +static u8 mwl_fwcmd_get_80m_pri_chnl_offset(u8 channel); +static void mwl_fwcmd_parse_beacon(struct mwl_priv *priv, + struct mwl_vif *vif, u8 *beacon, int len); +static int mwl_fwcmd_set_ies(struct mwl_priv *priv, struct mwl_vif *mwl_vif); +static int mwl_fwcmd_set_ap_beacon(struct mwl_priv *priv, struct mwl_vif *mwl_vif, + struct ieee80211_bss_conf *bss_conf); +static int mwl_fwcmd_encryption_set_cmd_info(struct hostcmd_cmd_set_key *cmd, + u8 *addr, struct ieee80211_key_conf *key); + +#ifdef MWL_DEBUG +static char *mwl_fwcmd_get_cmd_string(unsigned short cmd); +#endif + +/* PUBLIC FUNCTION DEFINITION +*/ + +void mwl_fwcmd_reset(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + if (mwl_fwcmd_chk_adapter(priv)) { + + writel(ISR_RESET, priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); + } + + WLDBG_EXIT(DBG_LEVEL_2); +} + +void mwl_fwcmd_int_enable(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + if (mwl_fwcmd_chk_adapter(priv)) { + + writel(0x00, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + + writel((MACREG_A2HRIC_BIT_MASK), + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + } + + WLDBG_EXIT(DBG_LEVEL_2); +} + +void mwl_fwcmd_int_disable(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + if (mwl_fwcmd_chk_adapter(priv)) { + + writel(0x00, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + } + + WLDBG_EXIT(DBG_LEVEL_2); +} + +int mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_get_hw_spec *pcmd; + unsigned long flags; + int i; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_get_hw_spec *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + WLDBG_PRINT("pcmd = %x", (unsigned int)pcmd); + memset(pcmd, 0x00, sizeof(*pcmd)); + memset(&pcmd->permanent_addr[0], 0xff, ETH_ALEN); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_GET_HW_SPEC); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->fw_awake_cookie = ENDIAN_SWAP32(priv->pphys_cmd_buf + 2048); + + WLDBG_DUMP_DATA(DBG_LEVEL_2, (void *)pcmd, sizeof(*pcmd)); + + while (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_HW_SPEC)) { + + WLDBG_PRINT("failed execution"); + WL_MSEC_SLEEP(1000); + WLDBG_PRINT("repeat command = %x", (unsigned int)pcmd); + } + + memcpy(&priv->hw_data.mac_addr[0], pcmd->permanent_addr, ETH_ALEN); + priv->desc_data[0].wcb_base = ENDIAN_SWAP32(pcmd->wcb_base0) & 0x0000ffff; +#if SYSADPT_NUM_OF_DESC_DATA > 3 + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + priv->desc_data[i].wcb_base = ENDIAN_SWAP32(pcmd->wcb_base[i - 1]) & 0x0000ffff; +#endif + priv->desc_data[0].rx_desc_read = ENDIAN_SWAP32(pcmd->rxpd_rd_ptr) & 0x0000ffff; + priv->desc_data[0].rx_desc_write = ENDIAN_SWAP32(pcmd->rxpd_wr_ptr) & 0x0000ffff; + priv->hw_data.region_code = ENDIAN_SWAP16(pcmd->region_code) & 0x00ff; + priv->hw_data.fw_release_num = ENDIAN_SWAP32(pcmd->fw_release_num); + priv->hw_data.max_num_tx_desc = ENDIAN_SWAP16(pcmd->num_wcb); + priv->hw_data.max_num_mc_addr = ENDIAN_SWAP16(pcmd->num_mcast_addr); + priv->hw_data.num_antennas = ENDIAN_SWAP16(pcmd->num_antenna); + priv->hw_data.hw_version = pcmd->version; + priv->hw_data.host_interface = pcmd->host_if; + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + + WLDBG_EXIT_INFO(DBG_LEVEL_2, + "region code is %i (0x%x), HW version is %i (0x%x)", + priv->hw_data.region_code, priv->hw_data.region_code, + priv->hw_data.hw_version, priv->hw_data.hw_version); + + return 0; +} + +int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_hw_spec *pcmd; + unsigned long flags; + int i; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + /* Info for debugging + */ + WLDBG_PRINT("%s ...", __FUNCTION__); + WLDBG_PRINT(" -->pPhysTxRing[0] = %x", priv->desc_data[0].pphys_tx_ring); + WLDBG_PRINT(" -->pPhysTxRing[1] = %x", priv->desc_data[1].pphys_tx_ring); + WLDBG_PRINT(" -->pPhysTxRing[2] = %x", priv->desc_data[2].pphys_tx_ring); + WLDBG_PRINT(" -->pPhysTxRing[3] = %x", priv->desc_data[3].pphys_tx_ring); + WLDBG_PRINT(" -->pPhysRxRing = %x", priv->desc_data[0].pphys_rx_ring); + WLDBG_PRINT(" -->numtxq %d wcbperq %d totalrxwcb %d", + SYSADPT_NUM_OF_DESC_DATA, SYSADPT_MAX_NUM_TX_DESC, SYSADPT_MAX_NUM_RX_DESC); + + pcmd = (struct hostcmd_cmd_set_hw_spec *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_HW_SPEC); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->wcb_base[0] = ENDIAN_SWAP32(priv->desc_data[0].pphys_tx_ring); +#if SYSADPT_NUM_OF_DESC_DATA > 3 + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + pcmd->wcb_base[i] = ENDIAN_SWAP32(priv->desc_data[i].pphys_tx_ring); +#endif + pcmd->tx_wcb_num_per_queue = ENDIAN_SWAP32(SYSADPT_MAX_NUM_TX_DESC); + pcmd->num_tx_queues = ENDIAN_SWAP32(SYSADPT_NUM_OF_DESC_DATA); + pcmd->total_rx_wcb = ENDIAN_SWAP32(SYSADPT_MAX_NUM_RX_DESC); + pcmd->rxpd_wr_ptr = ENDIAN_SWAP32(priv->desc_data[0].pphys_rx_ring); + pcmd->disablembss = 0; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_HW_SPEC)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_get_stat(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_802_11_get_stat *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_802_11_get_stat *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_802_11_GET_STAT); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_GET_STAT)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + stats->dot11ACKFailureCount = + ENDIAN_SWAP32(pcmd->ack_failures); + stats->dot11RTSFailureCount = + ENDIAN_SWAP32(pcmd->rts_failures); + stats->dot11FCSErrorCount = + ENDIAN_SWAP32(pcmd->rx_fcs_errors); + stats->dot11RTSSuccessCount = + ENDIAN_SWAP32(pcmd->rts_successes); + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int rc; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + rc = mwl_fwcmd_802_11_radio_control(priv, true, false); + + WLDBG_EXIT(DBG_LEVEL_2); + + return rc; +} + +int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int rc; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + rc = mwl_fwcmd_802_11_radio_control(priv, false, false); + + WLDBG_EXIT(DBG_LEVEL_2); + + return rc; +} + +int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw, bool short_preamble) +{ + struct mwl_priv *priv; + int rc; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + priv->radio_short_preamble = short_preamble; + rc = mwl_fwcmd_802_11_radio_control(priv, true, true); + + WLDBG_EXIT(DBG_LEVEL_2); + + return rc; +} + +int mwl_fwcmd_max_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u8 fraction) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + struct mwl_priv *priv; + int reduce_val = 0; + u16 band = 0, width = 0, sub_ch = 0; + u16 maxtxpow[SYSADPT_TX_POWER_LEVEL_TOTAL]; + int i, tmp; + int rc; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + switch (fraction) { + + case 0: + reduce_val = 0; /* Max */ + break; + case 1: + reduce_val = 2; /* 75% -1.25db */ + break; + case 2: + reduce_val = 3; /* 50% -3db */ + break; + case 3: + reduce_val = 6; /* 25% -6db */ + break; + default: + reduce_val = 0xff; /* larger than case 3, pCmd->MaxPowerLevel is min */ + break; + } + + if (channel->band == IEEE80211_BAND_2GHZ) + band = FREQ_BAND_2DOT4GHZ; + else if (channel->band == IEEE80211_BAND_5GHZ) + band = FREQ_BAND_5GHZ; + + if ((conf->chandef.width == NL80211_CHAN_WIDTH_20_NOHT) || + (conf->chandef.width == NL80211_CHAN_WIDTH_20)) { + + width = CH_20_MHz_WIDTH; + sub_ch = NO_EXT_CHANNEL; + + } else if (conf->chandef.width == NL80211_CHAN_WIDTH_40) { + + width = CH_40_MHz_WIDTH; + + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + + } else if (conf->chandef.width == NL80211_CHAN_WIDTH_80) { + + width = CH_80_MHz_WIDTH; + + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + } + + if ((priv->powinited & 2) == 0) { + + mwl_fwcmd_get_tx_powers(priv, priv->max_tx_pow, + channel->hw_value, band, width, sub_ch); + + priv->powinited |= 2; + } + + if ((priv->powinited & 1) == 0) { + + mwl_fwcmd_get_tx_powers(priv, priv->target_powers, + channel->hw_value, band, width, sub_ch); + + priv->powinited |= 1; + } + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) { + + if (priv->target_powers[i] > priv->max_tx_pow[i]) + tmp = priv->max_tx_pow[i]; + else + tmp = priv->target_powers[i]; + + maxtxpow[i] = ((tmp - reduce_val) > 0) ? (tmp - reduce_val) : 0; + } + + rc = mwl_fwcmd_set_tx_powers(priv, maxtxpow, HOSTCMD_ACT_GEN_SET, + channel->hw_value, band, width, sub_ch); + + WLDBG_EXIT_INFO(DBG_LEVEL_2, "return code: %d", rc); + + return rc; +} + +int mwl_fwcmd_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u8 fraction) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + struct mwl_priv *priv; + int reduce_val = 0; + u16 band = 0, width = 0, sub_ch = 0; + u16 txpow[SYSADPT_TX_POWER_LEVEL_TOTAL]; + int index, found = 0; + int i, tmp; + int rc; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + switch (fraction) { + + case 0: + reduce_val = 0; /* Max */ + break; + case 1: + reduce_val = 2; /* 75% -1.25db */ + break; + case 2: + reduce_val = 3; /* 50% -3db */ + break; + case 3: + reduce_val = 6; /* 25% -6db */ + break; + default: + reduce_val = 0xff; /* larger than case 3, pCmd->MaxPowerLevel is min */ + break; + } + + if (channel->band == IEEE80211_BAND_2GHZ) + band = FREQ_BAND_2DOT4GHZ; + else if (channel->band == IEEE80211_BAND_5GHZ) + band = FREQ_BAND_5GHZ; + + if ((conf->chandef.width == NL80211_CHAN_WIDTH_20_NOHT) || + (conf->chandef.width == NL80211_CHAN_WIDTH_20)) { + + width = CH_20_MHz_WIDTH; + sub_ch = NO_EXT_CHANNEL; + + } else if (conf->chandef.width == NL80211_CHAN_WIDTH_40) { + + width = CH_40_MHz_WIDTH; + + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + + } else if (conf->chandef.width == NL80211_CHAN_WIDTH_80) { + + width = CH_80_MHz_WIDTH; + + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + } + + /* search tx power table if exist + */ + for (index = 0; index < SYSADPT_MAX_NUM_CHANNELS; index++) { + + /* do nothing if table is not loaded + */ + if (priv->tx_pwr_tbl[index].channel == 0) + break; + + if (priv->tx_pwr_tbl[index].channel == channel->hw_value) { + + priv->cdd = priv->tx_pwr_tbl[index].cdd; + priv->txantenna2 = priv->tx_pwr_tbl[index].txantenna2; + + if (priv->tx_pwr_tbl[index].setcap) + priv->powinited = 0x01; + else + priv->powinited = 0x02; + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) { + + if (priv->tx_pwr_tbl[index].setcap) + priv->max_tx_pow[i] = priv->tx_pwr_tbl[index].tx_power[i]; + else + priv->target_powers[i] = priv->tx_pwr_tbl[index].tx_power[i]; + } + + found = 1; + break; + } + } + + if ((priv->powinited & 2) == 0) { + + mwl_fwcmd_get_tx_powers(priv, priv->max_tx_pow, + channel->hw_value, band, width, sub_ch); + + priv->powinited |= 2; + } + + if ((priv->powinited & 1) == 0) { + + mwl_fwcmd_get_tx_powers(priv, priv->target_powers, + channel->hw_value, band, width, sub_ch); + + priv->powinited |= 1; + } + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) { + + if (found) { + + if ((priv->tx_pwr_tbl[index].setcap) + && (priv->tx_pwr_tbl[index].tx_power[i] > priv->max_tx_pow[i])) + tmp = priv->max_tx_pow[i]; + else + tmp = priv->tx_pwr_tbl[index].tx_power[i]; + + } else { + + if (priv->target_powers[i] > priv->max_tx_pow[i]) + tmp = priv->max_tx_pow[i]; + else + tmp = priv->target_powers[i]; + } + + txpow[i] = ((tmp - reduce_val) > 0) ? (tmp - reduce_val) : 0; + } + + rc = mwl_fwcmd_set_tx_powers(priv, txpow, HOSTCMD_ACT_GEN_SET_LIST, + channel->hw_value, band, width, sub_ch); + + WLDBG_EXIT_INFO(DBG_LEVEL_2, "return code: %d", rc); + + return rc; +} + +int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int dir, int antenna) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_802_11_rf_antenna *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_802_11_rf_antenna *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_802_11_RF_ANTENNA); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + + pcmd->action = ENDIAN_SWAP16(dir); + + if (dir == WL_ANTENNATYPE_RX) { + + u8 rx_antenna = 4; /* if auto, set 4 rx antennas in SC2 */ + + if (antenna != 0) + pcmd->antenna_mode = ENDIAN_SWAP16(antenna); + else + pcmd->antenna_mode = ENDIAN_SWAP16(rx_antenna); + + } else { + + u8 tx_antenna = 0xf; /* if auto, set 4 tx antennas in SC2 */ + + if (antenna != 0) + pcmd->antenna_mode = ENDIAN_SWAP16(antenna); + else + pcmd->antenna_mode = ENDIAN_SWAP16(tx_antenna); + + } + + WLDBG_DUMP_DATA(DBG_LEVEL_2, (void *)pcmd, sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RF_ANTENNA)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_broadcast_ssid_enable *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + pcmd = (struct hostcmd_cmd_broadcast_ssid_enable *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_BROADCAST_SSID_ENABLE); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->enable = ENDIAN_SWAP32(enable); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BROADCAST_SSID_ENABLE)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw, + struct ieee80211_conf *conf) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + struct mwl_priv *priv; + struct hostcmd_cmd_set_rf_channel *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_set_rf_channel *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_RF_CHANNEL); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->action = ENDIAN_SWAP16(WL_SET); + pcmd->curr_chnl = channel->hw_value; + + if (channel->band == IEEE80211_BAND_2GHZ) + pcmd->chnl_flags.freq_band = FREQ_BAND_2DOT4GHZ; + else if (channel->band == IEEE80211_BAND_5GHZ) + pcmd->chnl_flags.freq_band = FREQ_BAND_5GHZ; + + if ((conf->chandef.width == NL80211_CHAN_WIDTH_20_NOHT) || + (conf->chandef.width == NL80211_CHAN_WIDTH_20)) { + + pcmd->chnl_flags.chnl_width = CH_20_MHz_WIDTH; + pcmd->chnl_flags.act_primary = ACT_PRIMARY_CHAN_0; + + } else if (conf->chandef.width == NL80211_CHAN_WIDTH_40) { + + pcmd->chnl_flags.chnl_width = CH_40_MHz_WIDTH; + + if (conf->chandef.center_freq1 > channel->center_freq) + pcmd->chnl_flags.act_primary = ACT_PRIMARY_CHAN_0; + else + pcmd->chnl_flags.act_primary = ACT_PRIMARY_CHAN_1; + + } else if (conf->chandef.width == NL80211_CHAN_WIDTH_80) { + + pcmd->chnl_flags.chnl_width = CH_80_MHz_WIDTH; + pcmd->chnl_flags.act_primary = + mwl_fwcmd_get_80m_pri_chnl_offset(pcmd->curr_chnl); + + } + + WLDBG_DUMP_DATA(DBG_LEVEL_2, (void *)pcmd, sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_RF_CHANNEL)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw, int threshold) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_802_11_rts_thsd *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_802_11_rts_thsd *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_802_11_RTS_THSD); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->action = ENDIAN_SWAP16(WL_SET); + pcmd->threshold = ENDIAN_SWAP16(threshold); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RTS_THSD)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, u8 index, + u16 cw_min, u16 cw_max, u8 aifs, u16 txop) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_edca_params *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_set_edca_params *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_EDCA_PARAMS); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + + pcmd->action = ENDIAN_SWAP16(0xffff);; + pcmd->txop = ENDIAN_SWAP16(txop); + pcmd->cw_max = ENDIAN_SWAP32(ilog2(cw_max + 1)); + pcmd->cw_min = ENDIAN_SWAP32(ilog2(cw_min + 1)); + pcmd->aifsn = aifs; + pcmd->txq_num = index; + + /* The array index defined in qos.h has a reversed bk and be. + * The HW queue was not used this way; the qos code needs to be changed or + * checked + */ + if (index == 0) + pcmd->txq_num = 1; + else if (index == 1) + pcmd->txq_num = 0; + + WLDBG_DUMP_DATA(DBG_LEVEL_2, (void *)pcmd, sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_EDCA_PARAMS)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, bool enable) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_wmm_mode *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_set_wmm_mode *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_WMM_MODE); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->action = ENDIAN_SWAP16(enable ? WL_ENABLE : WL_DISABLE); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WMM_MODE)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw, int mcast, int mgmt) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_fixed_rate *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_set_fixed_rate *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_FIXED_RATE); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + + pcmd->action = ENDIAN_SWAP32(HOSTCMD_ACT_NOT_USE_FIXED_RATE); + pcmd->multicast_rate = mcast; + pcmd->management_rate = mgmt; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_FIXED_RATE)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw, u16 mode) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_rate_adapt_mode *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_set_rate_adapt_mode *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_RATE_ADAPT_MODE); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->action = ENDIAN_SWAP16(WL_SET); + pcmd->rate_adapt_mode = ENDIAN_SWAP16(mode); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_RATE_ADAPT_MODE)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw, u8 *bitmap) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_get_watchdog_bitmap *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_get_watchdog_bitmap *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_GET_WATCHDOG_BITMAP); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_WATCHDOG_BITMAP)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + *bitmap = pcmd->watchdog_bitmap; + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_bss_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bss_start *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + if (enable && (priv->running_bsses & (1 << mwl_vif->macid))) + return 0; + + if (!enable && !(priv->running_bsses & (1 << mwl_vif->macid))) + return 0; + + pcmd = (struct hostcmd_cmd_bss_start *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_BSS_START); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if (enable) { + + pcmd->enable = ENDIAN_SWAP32(WL_ENABLE); + + } else { + + if (mwl_vif->macid == 0) + pcmd->enable = ENDIAN_SWAP32(WL_DISABLE); + else + pcmd->enable = ENDIAN_SWAP32(WL_DISABLE_VMAC); + + } + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BSS_START)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + if (enable) + priv->running_bsses |= (1 << mwl_vif->macid); + else + priv->running_bsses &= ~(1 << mwl_vif->macid); + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *beacon, int len) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + mwl_fwcmd_parse_beacon(priv, mwl_vif, beacon, len); + + if (mwl_fwcmd_set_ies(priv, mwl_vif)) + goto err; + + if (mwl_fwcmd_set_ap_beacon(priv, mwl_vif, &vif->bss_conf)) + goto err; + + mwl_vif->beacon_info.valid = false; + + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; + +err: + + mwl_vif->beacon_info.valid = false; + + WLDBG_EXIT_INFO(DBG_LEVEL_2, "set beacon failed"); + + return -EIO; +} + +int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_new_stn *pcmd; + unsigned long flags; + u32 rates; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + BUG_ON(!sta); + + pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = ENDIAN_SWAP16(HOSTCMD_ACT_STA_ACTION_ADD); + pcmd->aid = ENDIAN_SWAP16(sta->aid); + memcpy(pcmd->mac_addr, sta->addr, ETH_ALEN); + pcmd->stn_id = ENDIAN_SWAP16(sta->aid); + + if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) + rates = sta->supp_rates[IEEE80211_BAND_2GHZ]; + else + rates = sta->supp_rates[IEEE80211_BAND_5GHZ] << 5; + pcmd->peer_info.legacy_rate_bitmap = ENDIAN_SWAP32(rates); + + if (sta->ht_cap.ht_supported) { + + pcmd->peer_info.ht_rates[0] = sta->ht_cap.mcs.rx_mask[0]; + pcmd->peer_info.ht_rates[1] = sta->ht_cap.mcs.rx_mask[1]; + pcmd->peer_info.ht_rates[2] = sta->ht_cap.mcs.rx_mask[2]; + pcmd->peer_info.ht_rates[3] = sta->ht_cap.mcs.rx_mask[3]; + pcmd->peer_info.ht_cap_info = ENDIAN_SWAP16(sta->ht_cap.cap); + pcmd->peer_info.mac_ht_param_info = (sta->ht_cap.ampdu_factor & 3) | + ((sta->ht_cap.ampdu_density & 7) << 2); + } + + if (sta->vht_cap.vht_supported) { + + pcmd->peer_info.vht_max_rx_mcs = ENDIAN_SWAP32(*((u32 *)&sta->vht_cap.vht_mcs.rx_mcs_map)); + pcmd->peer_info.vht_cap = ENDIAN_SWAP32(sta->vht_cap.cap); + pcmd->peer_info.vht_rx_channel_width = sta->bandwidth; + } + + pcmd->is_qos_sta = sta->wme; + pcmd->qos_info = ((sta->uapsd_queues << 4) | (sta->max_sp << 1)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_new_stn *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = ENDIAN_SWAP16(HOSTCMD_ACT_STA_ACTION_ADD); + memcpy(pcmd->mac_addr, vif->addr, ETH_ALEN); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_new_stn *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = ENDIAN_SWAP16(HOSTCMD_ACT_STA_ACTION_REMOVE); + memcpy(pcmd->mac_addr, addr, ETH_ALEN); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_apmode *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_set_apmode *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_APMODE); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->apmode = apmode; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_APMODE)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, u8 encr_type) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_update_encryptoin *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + pcmd = (struct hostcmd_cmd_update_encryptoin *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action_type = ENDIAN_SWAP32(ENCR_ACTION_ENABLE_HW_ENCR); + memcpy(pcmd->mac_addr, addr, ETH_ALEN); + pcmd->action_data[0] = encr_type; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_key *pcmd; + unsigned long flags; + int rc; + int keymlen; + u32 action; + u8 idx; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, addr, key); + if (rc) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "encryption not support"); + return rc; + } + + idx = key->keyidx; + + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + action = ENCR_ACTION_TYPE_SET_KEY; + else + action = ENCR_ACTION_TYPE_SET_GROUP_KEY; + + switch (key->cipher) { + + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + + if (!mwl_vif->wep_key_conf[idx].enabled) { + + memcpy(mwl_vif->wep_key_conf[idx].key, key, + sizeof(*key) + key->keylen); + mwl_vif->wep_key_conf[idx].enabled = 1; + } + + keymlen = key->keylen; + action = ENCR_ACTION_TYPE_SET_KEY; + break; + + case WLAN_CIPHER_SUITE_TKIP: + + keymlen = MAX_ENCR_KEY_LENGTH + 2 * MIC_KEY_LENGTH; + break; + + case WLAN_CIPHER_SUITE_CCMP: + + keymlen = key->keylen; + break; + + default: + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "encryption not support"); + return -ENOTSUPP; + } + + memcpy((void *)&pcmd->key_param.key, key->key, keymlen); + pcmd->action_type = ENDIAN_SWAP32(action); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_key *pcmd; + unsigned long flags; + int rc; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, addr, key); + if (rc) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "encryption not support"); + return rc; + } + + pcmd->action_type = ENDIAN_SWAP32(ENCR_ACTION_TYPE_REMOVE_KEY); + + if (key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) + mwl_vif->wep_key_conf[key->keyidx].enabled = 0; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_check_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bastream *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!stream); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_BASTREAM); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->cmd_hdr.result = ENDIAN_SWAP16(0xffff); + + pcmd->action_type = ENDIAN_SWAP32(BA_CHECK_STREAM); + memcpy(&pcmd->ba_info.create_params.peer_mac_addr[0], + stream->sta->addr, ETH_ALEN); + pcmd->ba_info.create_params.tid = stream->tid; + pcmd->ba_info.create_params.flags.ba_type = + BASTREAM_FLAG_IMMEDIATE_TYPE; + pcmd->ba_info.create_params.flags.ba_direction = + BASTREAM_FLAG_DIRECTION_UPSTREAM; + pcmd->ba_info.create_params.queue_id = stream->idx; + + WLDBG_DUMP_DATA(DBG_LEVEL_2, (void *)pcmd, sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "result error"); + return -EINVAL; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_create_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + u8 buf_size, struct ieee80211_vif *vif) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bastream *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!stream); + + BUG_ON(!vif); + mwl_vif = MWL_VIF(vif); + BUG_ON(!mwl_vif); + + pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_BASTREAM); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->cmd_hdr.result = ENDIAN_SWAP16(0xffff); + + pcmd->action_type = ENDIAN_SWAP32(BA_CREATE_STREAM); + pcmd->ba_info.create_params.bar_thrs = ENDIAN_SWAP32(buf_size); + pcmd->ba_info.create_params.window_size = ENDIAN_SWAP32(buf_size); + memcpy(&pcmd->ba_info.create_params.peer_mac_addr[0], + stream->sta->addr, ETH_ALEN); + pcmd->ba_info.create_params.tid = stream->tid; + pcmd->ba_info.create_params.flags.ba_type = + BASTREAM_FLAG_IMMEDIATE_TYPE; + pcmd->ba_info.create_params.flags.ba_direction = + BASTREAM_FLAG_DIRECTION_UPSTREAM; + pcmd->ba_info.create_params.queue_id = stream->idx; + pcmd->ba_info.create_params.param_info = + (stream->sta->ht_cap.ampdu_factor & + IEEE80211_HT_AMPDU_PARM_FACTOR) | + ((stream->sta->ht_cap.ampdu_density << 2) & + IEEE80211_HT_AMPDU_PARM_DENSITY); + pcmd->ba_info.create_params.reset_seq_no = 1; + pcmd->ba_info.create_params.current_seq = ENDIAN_SWAP16(0); + + WLDBG_DUMP_DATA(DBG_LEVEL_2, (void *)pcmd, sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "result error"); + return -EINVAL; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw, + u8 idx) +{ + + struct mwl_priv *priv; + struct hostcmd_cmd_bastream *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_BASTREAM); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + + pcmd->action_type = ENDIAN_SWAP32(BA_DESTROY_STREAM); + pcmd->ba_info.destroy_params.flags.ba_type = + BASTREAM_FLAG_IMMEDIATE_TYPE; + pcmd->ba_info.destroy_params.flags.ba_direction = + BASTREAM_FLAG_DIRECTION_UPSTREAM; + pcmd->ba_info.destroy_params.fw_ba_context.context = ENDIAN_SWAP32(idx); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +/* caller must hold priv->locks.stream_lock when calling the stream functions +*/ +struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 tid) +{ + struct mwl_priv *priv; + struct mwl_ampdu_stream *stream; + int i; + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!sta); + + for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) { + + stream = &priv->ampdu[i]; + + if (stream->state == AMPDU_NO_STREAM) { + + stream->sta = sta; + stream->state = AMPDU_STREAM_NEW; + stream->tid = tid; + stream->idx = i; + return stream; + } + } + + return NULL; +} + +int mwl_fwcmd_start_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream) +{ + BUG_ON(!hw); + BUG_ON(!stream); + + /* if the stream has already been started, don't start it again + */ + if (stream->state != AMPDU_STREAM_NEW) + return 0; + + return (ieee80211_start_tx_ba_session(stream->sta, stream->tid, 0)); +} + +void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream) +{ + BUG_ON(!hw); + BUG_ON(!stream); + + memset(stream, 0, sizeof(*stream)); +} + +struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw, + u8 *addr, u8 tid) +{ + struct mwl_priv *priv; + struct mwl_ampdu_stream *stream; + int i; + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) { + + stream = &priv->ampdu[i]; + + if (stream->state == AMPDU_NO_STREAM) + continue; + + if (!memcmp(stream->sta->addr, addr, ETH_ALEN) && + stream->tid == tid) + return stream; + + } + + return NULL; +} + +bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid) +{ + struct mwl_sta *sta_info; + struct mwl_tx_info *tx_stats; + + BUG_ON(!sta); + sta_info = MWL_STA(sta); + BUG_ON(!sta_info); + + BUG_ON(tid >= SYSADPT_MAX_TID); + + tx_stats = &sta_info->tx_stats[tid]; + + return (sta_info->is_ampdu_allowed && + tx_stats->pkts > SYSADPT_AMPDU_PACKET_THRESHOLD); +} + +int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_dwds_enable *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_dwds_enable *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_DWDS_ENABLE); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->enable = ENDIAN_SWAP32(enable); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_DWDS_ENABLE)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_fw_flush_timer *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_fw_flush_timer *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_FW_FLUSH_TIMER); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->value = ENDIAN_SWAP32(value); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_FW_FLUSH_TIMER)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_cdd *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_set_cdd *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_CDD); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->enable = ENDIAN_SWAP32(priv->cdd); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_CDD)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +/* PRIVATE FUNCTION DEFINITION +*/ + +static bool mwl_fwcmd_chk_adapter(struct mwl_priv *priv) +{ + u32 regval; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!priv); + + regval = readl(priv->iobase1 + MACREG_REG_INT_CODE); + + if (regval == 0xffffffff) { + + WLDBG_ERROR(DBG_LEVEL_2, "adapter is not existed"); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "adapter is not existed"); + + return false; + } + + WLDBG_EXIT(DBG_LEVEL_2); + + return true; +} + +static int mwl_fwcmd_exec_cmd(struct mwl_priv *priv, unsigned short cmd) +{ + bool busy = false; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!priv); + + if (!mwl_fwcmd_chk_adapter(priv)) { + + WLDBG_ERROR(DBG_LEVEL_2, "no adapter existed"); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "no adapter plugged in"); + priv->in_send_cmd = false; + return -EIO; + } + + if (!priv->in_send_cmd) { + + priv->in_send_cmd = true; + + mwl_fwcmd_send_cmd(priv); + + if (mwl_fwcmd_wait_complete(priv, 0x8000 | cmd)) { + + WLDBG_ERROR(DBG_LEVEL_2, "timeout"); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "timeout"); + priv->in_send_cmd = false; + return -EIO; + } + + } else { + + WLDBG_WARNING(DBG_LEVEL_2, "previous command is still running"); + busy = true; + } + + WLDBG_EXIT(DBG_LEVEL_2); + if (!busy) + priv->in_send_cmd = false; + + return 0; +} + +static void mwl_fwcmd_send_cmd(struct mwl_priv *priv) +{ + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!priv); + + writel(priv->pphys_cmd_buf, priv->iobase1 + MACREG_REG_GEN_PTR); + writel(MACREG_H2ARIC_BIT_DOOR_BELL, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); + + WLDBG_EXIT(DBG_LEVEL_2); +} + +static int mwl_fwcmd_wait_complete(struct mwl_priv *priv, unsigned short cmd) +{ + unsigned int curr_iteration = MAX_WAIT_FW_COMPLETE_ITERATIONS; + + volatile unsigned short int_code = 0; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!priv); + + do { + + int_code = ENDIAN_SWAP16(priv->pcmd_buf[0]); + WL_MSEC_SLEEP(1); + + } while ((int_code != cmd) && (--curr_iteration)); + + if (curr_iteration == 0) + { + WLDBG_ERROR(DBG_LEVEL_2, "cmd 0x%04x=%s timed out", + cmd, mwl_fwcmd_get_cmd_string(cmd)); + + WLDBG_EXIT_INFO(DBG_LEVEL_2, "timeout"); + + return -EIO; + } + + WL_MSEC_SLEEP(3); + + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +static int mwl_fwcmd_802_11_radio_control(struct mwl_priv *priv, bool enable, bool force) +{ + struct hostcmd_cmd_802_11_radio_control *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!priv); + + if (enable == priv->radio_on && !force) + return 0; + + pcmd = (struct hostcmd_cmd_802_11_radio_control *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_802_11_RADIO_CONTROL); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->action = ENDIAN_SWAP16(WL_SET); + pcmd->control = ENDIAN_SWAP16(priv->radio_short_preamble ? + WL_AUTO_PREAMBLE : WL_LONG_PREAMBLE); + pcmd->radio_on = ENDIAN_SWAP16(enable ? WL_ENABLE : WL_DISABLE); + + WLDBG_DUMP_DATA(DBG_LEVEL_2, (void *)pcmd, sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RADIO_CONTROL)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + priv->radio_on = enable; + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +static int mwl_fwcmd_get_tx_powers(struct mwl_priv *priv, u16 *powlist, u16 ch, + u16 band, u16 width, u16 sub_ch) +{ + struct hostcmd_cmd_802_11_tx_power *pcmd; + unsigned long flags; + int i; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_802_11_TX_POWER); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->action = ENDIAN_SWAP16(HOSTCMD_ACT_GEN_GET_LIST); + pcmd->ch = ENDIAN_SWAP16(ch); + pcmd->bw = ENDIAN_SWAP16(width); + pcmd->band = ENDIAN_SWAP16(band); + pcmd->sub_ch = ENDIAN_SWAP16(sub_ch); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_TX_POWER)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) + powlist[i] = (u8)ENDIAN_SWAP16(pcmd->power_level_list[i]); + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +static int mwl_fwcmd_set_tx_powers(struct mwl_priv *priv, u16 txpow[], u8 action, + u16 ch, u16 band, u16 width, u16 sub_ch) +{ + struct hostcmd_cmd_802_11_tx_power *pcmd; + unsigned long flags; + int i; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!priv); + + pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_802_11_TX_POWER); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->action = ENDIAN_SWAP16(action); + pcmd->ch = ENDIAN_SWAP16(ch); + pcmd->bw = ENDIAN_SWAP16(width); + pcmd->band = ENDIAN_SWAP16(band); + pcmd->sub_ch = ENDIAN_SWAP16(sub_ch); + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) + pcmd->power_level_list[i] = ENDIAN_SWAP16(txpow[i]); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_TX_POWER)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; +} + +static u8 mwl_fwcmd_get_80m_pri_chnl_offset(u8 channel) +{ + u8 act_primary = ACT_PRIMARY_CHAN_0; + + switch (channel) { + + case 36: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 40: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 44: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 48: + act_primary = ACT_PRIMARY_CHAN_3; + break; + + case 52: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 56: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 60: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 64: + act_primary = ACT_PRIMARY_CHAN_3; + break; + + case 100: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 104: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 108: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 112: + act_primary = ACT_PRIMARY_CHAN_3; + break; + + case 116: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 120: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 124: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 128: + act_primary = ACT_PRIMARY_CHAN_3; + break; + + case 132: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 136: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 140: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 144: + act_primary = ACT_PRIMARY_CHAN_3; + break; + + case 149: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 153: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 157: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 161: + act_primary = ACT_PRIMARY_CHAN_3; + break; + } + + return act_primary; +} + +static void mwl_fwcmd_parse_beacon(struct mwl_priv *priv, + struct mwl_vif *vif, u8 *beacon, int len) +{ + struct ieee80211_mgmt *mgmt; + struct beacon_info *beacon_info; + int baselen; + u8 *pos; + size_t left; + bool elem_parse_failed; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!vif); + BUG_ON(!beacon); + + mgmt = (struct ieee80211_mgmt *)beacon; + + baselen = (u8 *)mgmt->u.beacon.variable - (u8 *)mgmt; + if (baselen > len) + return; + + beacon_info = &vif->beacon_info; + memset(beacon_info, 0, sizeof(struct beacon_info)); + beacon_info->valid = false; + beacon_info->ie_ht_ptr = &beacon_info->ie_list_ht[0]; + beacon_info->ie_vht_ptr = &beacon_info->ie_list_vht[0]; + + beacon_info->cap_info = mgmt->u.beacon.capab_info; + + pos = (u8 *)mgmt->u.beacon.variable; + left = len - baselen; + + elem_parse_failed = false; + + while (left >= 2) { + + u8 id, elen; + + id = *pos++; + elen = *pos++; + left -= 2; + + if (elen > left) { + + elem_parse_failed = true; + break; + } + + switch (id) { + + case WLAN_EID_SUPP_RATES: + case WLAN_EID_EXT_SUPP_RATES: + { + int idx, basic_idx, oprate_idx; + u8 rate; + + for (basic_idx = 0; basic_idx < SYSADPT_MAX_DATA_RATES_G; basic_idx++) { + + if (beacon_info->bss_basic_rate_set[basic_idx] == 0) + break; + } + + for (oprate_idx = 0; oprate_idx < SYSADPT_MAX_DATA_RATES_G; oprate_idx++) { + + if (beacon_info->op_rate_set[oprate_idx] == 0) + break; + } + + for (idx = 0; idx < elen; idx++) { + + rate = pos[idx]; + + if ((rate & 0x80) != 0) { + + if (basic_idx < SYSADPT_MAX_DATA_RATES_G) + beacon_info->bss_basic_rate_set[basic_idx++] = rate & 0x7f; + else { + elem_parse_failed = true; + break; + } + } + + if (oprate_idx < SYSADPT_MAX_DATA_RATES_G) + beacon_info->op_rate_set[oprate_idx++] = rate & 0x7f; + else { + elem_parse_failed = true; + break; + } + } + } + break; + + case WLAN_EID_RSN: + beacon_info->ie_rsn48_len = (elen + 2); + beacon_info->ie_rsn48_ptr = (pos - 2); + break; + + case WLAN_EID_HT_CAPABILITY: + case WLAN_EID_HT_OPERATION: + case WLAN_EID_OVERLAP_BSS_SCAN_PARAM: + case WLAN_EID_EXT_CAPABILITY: + beacon_info->ie_ht_len += (elen + 2); + if (beacon_info->ie_ht_len > sizeof(beacon_info->ie_list_ht)) { + elem_parse_failed = true; + break; + } else { + *beacon_info->ie_ht_ptr++ = id; + *beacon_info->ie_ht_ptr++ = elen; + memcpy(beacon_info->ie_ht_ptr, pos, elen); + beacon_info->ie_ht_ptr += elen; + } + break; + + case WLAN_EID_VHT_CAPABILITY: + case WLAN_EID_VHT_OPERATION: + case WLAN_EID_OPMODE_NOTIF: + beacon_info->ie_vht_len += (elen + 2); + if (beacon_info->ie_vht_len > sizeof(beacon_info->ie_list_vht)) { + elem_parse_failed = true; + break; + } else { + *beacon_info->ie_vht_ptr++ = id; + *beacon_info->ie_vht_ptr++ = elen; + memcpy(beacon_info->ie_vht_ptr, pos, elen); + beacon_info->ie_vht_ptr += elen; + } + break; + + case WLAN_EID_VENDOR_SPECIFIC: + if ((pos[0] == 0x00) && (pos[1] == 0x50) && (pos[2] == 0xf2)) { + + if (pos[3] == 0x01) { + beacon_info->ie_rsn_len = (elen + 2); + beacon_info->ie_rsn_ptr = (pos - 2); + } + + if (pos[3] == 0x02) { + beacon_info->ie_wmm_len = (elen + 2); + beacon_info->ie_wmm_ptr = (pos - 2); + } + } + break; + + default: + break; + + } + + left -= elen; + pos += elen; + } + + if (elem_parse_failed == false) { + + beacon_info->ie_ht_ptr = &beacon_info->ie_list_ht[0]; + beacon_info->ie_vht_ptr = &beacon_info->ie_list_vht[0]; + beacon_info->valid = true; + + WLDBG_INFO(DBG_LEVEL_2, "wmm:%d, rsn:%d, rsn48:%d, ht:%d, vht:%d", + beacon_info->ie_wmm_len, beacon_info->ie_rsn_len, beacon_info->ie_rsn48_len, + beacon_info->ie_ht_len, beacon_info->ie_vht_len); + + WLDBG_DUMP_DATA(DBG_LEVEL_2, beacon_info->bss_basic_rate_set, + SYSADPT_MAX_DATA_RATES_G); + + WLDBG_DUMP_DATA(DBG_LEVEL_2, beacon_info->op_rate_set, + SYSADPT_MAX_DATA_RATES_G); + } + + WLDBG_EXIT_INFO(DBG_LEVEL_2, "parse valid:%d", beacon_info->valid); + + return; +} + +static int mwl_fwcmd_set_ies(struct mwl_priv *priv, struct mwl_vif *mwl_vif) +{ + struct hostcmd_cmd_set_ies *pcmd; + unsigned long flags; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!priv); + BUG_ON(!mwl_vif); + + if (!mwl_vif->beacon_info.valid) + return -EINVAL; + + if (mwl_vif->beacon_info.ie_ht_len > sizeof(pcmd->ie_list_ht)) + goto einval; + + if (mwl_vif->beacon_info.ie_vht_len > sizeof(pcmd->ie_list_vht)) + goto einval; + + pcmd = (struct hostcmd_cmd_set_ies *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_SET_IES); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = ENDIAN_SWAP16(HOSTCMD_ACT_GEN_SET); + + pcmd->ie_list_len_ht = mwl_vif->beacon_info.ie_ht_len; + memcpy(pcmd->ie_list_ht, mwl_vif->beacon_info.ie_ht_ptr, + pcmd->ie_list_len_ht); + + pcmd->ie_list_len_vht = mwl_vif->beacon_info.ie_vht_len; + memcpy(pcmd->ie_list_vht, mwl_vif->beacon_info.ie_vht_ptr, + pcmd->ie_list_len_vht); + + WLDBG_DUMP_DATA(DBG_LEVEL_2, (void *)pcmd, sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_IES)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; + +einval: + + WLDBG_EXIT_INFO(DBG_LEVEL_2, "length of IE is too long"); + + return -EINVAL; +} + +static int mwl_fwcmd_set_ap_beacon(struct mwl_priv *priv, struct mwl_vif *mwl_vif, + struct ieee80211_bss_conf *bss_conf) +{ + struct hostcmd_cmd_ap_beacon *pcmd; + unsigned long flags; + struct ds_params *phy_ds_param_set; + + WLDBG_ENTER(DBG_LEVEL_2); + + BUG_ON(!priv); + BUG_ON(!mwl_vif); + BUG_ON(!bss_conf); + + if (!mwl_vif->beacon_info.valid) + return -EINVAL; + + /* wmm structure of start command is defined less one byte, due to following + * field country is not used, add byte one to bypass the check. + */ + if (mwl_vif->beacon_info.ie_wmm_len > (sizeof(pcmd->start_cmd.wmm_param) + 1)) + goto ielenerr; + + if (mwl_vif->beacon_info.ie_rsn_len > sizeof(pcmd->start_cmd.rsn_ie)) + goto ielenerr; + + if (mwl_vif->beacon_info.ie_rsn48_len > sizeof(pcmd->start_cmd.rsn48_ie)) + goto ielenerr; + + pcmd = (struct hostcmd_cmd_ap_beacon *)&priv->pcmd_buf[0]; + + MWL_SPIN_LOCK(&priv->locks.fwcmd_lock); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_AP_BEACON); + pcmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + memcpy(pcmd->start_cmd.sta_mac_addr, mwl_vif->bssid, ETH_ALEN); + memcpy(pcmd->start_cmd.ssid, bss_conf->ssid, bss_conf->ssid_len); + pcmd->start_cmd.bss_type = 1; + pcmd->start_cmd.bcn_period = ENDIAN_SWAP16(bss_conf->beacon_int); + pcmd->start_cmd.dtim_period = bss_conf->dtim_period; /* 8bit */ + + phy_ds_param_set = &pcmd->start_cmd.phy_param_set.ds_param_set; + phy_ds_param_set->elem_id = WLAN_EID_DS_PARAMS; + phy_ds_param_set->len = sizeof(phy_ds_param_set->current_chnl); + phy_ds_param_set->current_chnl = bss_conf->chandef.chan->hw_value; + + pcmd->start_cmd.probe_delay = ENDIAN_SWAP16(10); + pcmd->start_cmd.cap_info = ENDIAN_SWAP16(mwl_vif->beacon_info.cap_info); + + memcpy(&pcmd->start_cmd.wmm_param, mwl_vif->beacon_info.ie_wmm_ptr, + mwl_vif->beacon_info.ie_wmm_len); + + memcpy(&pcmd->start_cmd.rsn_ie, mwl_vif->beacon_info.ie_rsn_ptr, + mwl_vif->beacon_info.ie_rsn_len); + + memcpy(&pcmd->start_cmd.rsn48_ie, mwl_vif->beacon_info.ie_rsn48_ptr, + mwl_vif->beacon_info.ie_rsn48_len); + + memcpy(pcmd->start_cmd.bss_basic_rate_set, mwl_vif->beacon_info.bss_basic_rate_set, + SYSADPT_MAX_DATA_RATES_G); + + memcpy(pcmd->start_cmd.op_rate_set, mwl_vif->beacon_info.op_rate_set, + SYSADPT_MAX_DATA_RATES_G); + + WLDBG_DUMP_DATA(DBG_LEVEL_2, (void *)pcmd, sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_AP_BEACON)) { + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_2, "failed execution"); + return -EIO; + } + + MWL_SPIN_UNLOCK(&priv->locks.fwcmd_lock); + WLDBG_EXIT(DBG_LEVEL_2); + + return 0; + +ielenerr: + + WLDBG_EXIT_INFO(DBG_LEVEL_2, "length of IE is too long"); + + return -EINVAL; +} + +static int mwl_fwcmd_encryption_set_cmd_info(struct hostcmd_cmd_set_key *cmd, + u8 *addr, struct ieee80211_key_conf *key) +{ + cmd->cmd_hdr.cmd = ENDIAN_SWAP16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + cmd->cmd_hdr.len = ENDIAN_SWAP16(sizeof(*cmd)); + cmd->key_param.length = ENDIAN_SWAP16(sizeof(*cmd) - + offsetof(struct hostcmd_cmd_set_key, key_param)); + cmd->key_param.key_index = ENDIAN_SWAP32(key->keyidx); + cmd->key_param.key_len = ENDIAN_SWAP16(key->keylen); + memcpy(cmd->key_param.mac_addr, addr, ETH_ALEN); + + switch (key->cipher) { + + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + + cmd->key_param.key_type_id = ENDIAN_SWAP16(KEY_TYPE_ID_WEP); + if (key->keyidx == 0) + cmd->key_param.key_info = ENDIAN_SWAP32(ENCR_KEY_FLAG_WEP_TXKEY); + + break; + + case WLAN_CIPHER_SUITE_TKIP: + + cmd->key_param.key_type_id = ENDIAN_SWAP16(KEY_TYPE_ID_TKIP); + cmd->key_param.key_info = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + ? ENDIAN_SWAP32(ENCR_KEY_FLAG_PAIRWISE) + : ENDIAN_SWAP32(ENCR_KEY_FLAG_TXGROUPKEY); + cmd->key_param.key_info |= ENDIAN_SWAP32(ENCR_KEY_FLAG_MICKEY_VALID + | ENCR_KEY_FLAG_TSC_VALID); + break; + + case WLAN_CIPHER_SUITE_CCMP: + + cmd->key_param.key_type_id = ENDIAN_SWAP16(KEY_TYPE_ID_AES); + cmd->key_param.key_info = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + ? ENDIAN_SWAP32(ENCR_KEY_FLAG_PAIRWISE) + : ENDIAN_SWAP32(ENCR_KEY_FLAG_TXGROUPKEY); + break; + + default: + + return -ENOTSUPP; + } + + return 0; +} + +#ifdef MWL_DEBUG +static char *mwl_fwcmd_get_cmd_string(unsigned short cmd) +{ + int max_entries = 0; + int curr_cmd = 0; + + static const struct { + + u16 cmd; + char *cmd_string; + + } cmds[] = { + + { HOSTCMD_CMD_GET_HW_SPEC, "GetHwSpecifications" }, + { HOSTCMD_CMD_SET_HW_SPEC, "SetHwSepcifications" }, + { HOSTCMD_CMD_802_11_GET_STAT, "80211GetStat" }, + { HOSTCMD_CMD_802_11_RADIO_CONTROL, "80211RadioControl" }, + { HOSTCMD_CMD_802_11_TX_POWER, "80211TxPower" }, + { HOSTCMD_CMD_802_11_RF_ANTENNA, "80211RfAntenna" }, + { HOSTCMD_CMD_BROADCAST_SSID_ENABLE, "broadcast_ssid_enable" }, + { HOSTCMD_CMD_SET_RF_CHANNEL, "SetRfChannel" }, + { HOSTCMD_CMD_802_11_RTS_THSD, "80211RtsThreshold" }, + { HOSTCMD_CMD_SET_EDCA_PARAMS, "SetEDCAParams" }, + { HOSTCMD_CMD_SET_WMM_MODE, "SetWMMMode" }, + { HOSTCMD_CMD_SET_FIXED_RATE, "SetFixedRate" }, + { HOSTCMD_CMD_SET_IES, "SetInformationElements" }, + { HOSTCMD_CMD_SET_RATE_ADAPT_MODE, "SetRateAdaptationMode" }, + { HOSTCMD_CMD_GET_WATCHDOG_BITMAP, "GetWatchdogBitMap" }, + { HOSTCMD_CMD_BSS_START, "BssStart" }, + { HOSTCMD_CMD_AP_BEACON, "SetApBeacon" }, + { HOSTCMD_CMD_SET_NEW_STN, "SetNewStation" }, + { HOSTCMD_CMD_SET_APMODE, "SetApMode" }, + { HOSTCMD_CMD_UPDATE_ENCRYPTION, "UpdateEncryption" }, + { HOSTCMD_CMD_BASTREAM, "BAStream" }, + { HOSTCMD_CMD_DWDS_ENABLE, "DwdsEnable" }, + { HOSTCMD_CMD_FW_FLUSH_TIMER, "FwFlushTimer" }, + { HOSTCMD_CMD_SET_CDD, "SetCDD" }, + }; + + WLDBG_ENTER(DBG_LEVEL_2); + + max_entries = sizeof(cmds) / sizeof(cmds[0]); + + for (curr_cmd = 0; curr_cmd < max_entries; curr_cmd++) { + + if ((cmd & 0x7fff) == cmds[curr_cmd].cmd) { + + WLDBG_EXIT(DBG_LEVEL_2); + + return cmds[curr_cmd].cmd_string; + } + } + + WLDBG_EXIT_INFO(DBG_LEVEL_2, "unknown"); + + return "unknown"; +} +#endif diff --git a/mwl_fwcmd.h b/mwl_fwcmd.h new file mode 100644 index 0000000..fed69ba --- /dev/null +++ b/mwl_fwcmd.h @@ -0,0 +1,178 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file defines firmware host command related functions. +* +*/ + +#ifndef _mwl_fwcmd_h_ +#define _mwl_fwcmd_h_ + +/* CONSTANTS AND MACROS +*/ + +/* + * Define OpMode for SoftAP/Station mode + * + * The following mode signature has to be written to PCI scratch register#0 + * right after successfully downloading the last block of firmware and + * before waiting for firmware ready signature + */ + +#define HOSTCMD_STA_MODE 0x5A +#define HOSTCMD_SOFTAP_MODE 0xA5 + +#define HOSTCMD_STA_FWRDY_SIGNATURE 0xF0F1F2F4 +#define HOSTCMD_SOFTAP_FWRDY_SIGNATURE 0xF1F2F4A5 + +/* TYPE DEFINITION +*/ + +enum { + + WL_ANTENNATYPE_RX = 1, + WL_ANTENNATYPE_TX = 2, + +}; + +enum encr_type { + + ENCR_TYPE_WEP = 0, + ENCR_TYPE_DISABLE = 1, + ENCR_TYPE_TKIP = 4, + ENCR_TYPE_AES = 6, + ENCR_TYPE_MIX = 7, + +}; + +/* PUBLIC FUNCTION DECLARATION +*/ + +void mwl_fwcmd_reset(struct ieee80211_hw *hw); + +void mwl_fwcmd_int_enable(struct ieee80211_hw *hw); + +void mwl_fwcmd_int_disable(struct ieee80211_hw *hw); + +int mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw); + +int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw); + +int mwl_fwcmd_get_stat(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats); + +int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw); + +int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw); + +int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw, + bool short_preamble); + +int mwl_fwcmd_max_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u8 fraction); + +int mwl_fwcmd_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u8 fraction); + +int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int dir, int antenna); + +int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable); + +int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw, + struct ieee80211_conf *conf); + +int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw, + int threshold); + +int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, + u8 index, u16 cw_min, u16 cw_max, u8 aifs, u16 txop); + +int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, + bool enable); + +int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw, + int mcast, int mgmt); + +int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw, + u16 mode); + +int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw, + u8 *bitmap); + +int mwl_fwcmd_bss_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable); + +int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *beacon, int len); + +int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + +int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); + +int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr); + +int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode); + +int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, u8 encr_type); + +int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key); + +int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key); + +int mwl_fwcmd_check_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + struct ieee80211_vif *vif); + +int mwl_fwcmd_create_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + u8 buf_size, struct ieee80211_vif *vif); + +int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw, + u8 idx); + +struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 tid); + +int mwl_fwcmd_start_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream); + +void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream); + +struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw, + u8 *addr, u8 tid); + +bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid); + +int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable); + +int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value); + +int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw); + +#endif /* _mwl_fwcmd_h_ */ diff --git a/mwl_fwdl.c b/mwl_fwdl.c new file mode 100644 index 0000000..5f9ef91 --- /dev/null +++ b/mwl_fwdl.c @@ -0,0 +1,219 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file implements frimware downloa related functions. +* +*/ + +#include + +#include "mwl_sysadpt.h" +#include "mwl_dev.h" +#include "mwl_debug.h" +#include "mwl_fwcmd.h" +#include "mwl_fwdl.h" + +/* CONSTANTS AND MACROS +*/ + +#define FW_DOWNLOAD_BLOCK_SIZE 256 +#define FW_CHECK_MSECS 1 + +#define FW_MAX_NUM_CHECKS 0xffff + +/* PRIVATE FUNCTION DECLARATION +*/ + +static void mwl_fwdl_trig_pcicmd(struct mwl_priv *priv); +static void mwl_fwdl_trig_pcicmd_bootcode(struct mwl_priv *priv); + +/* PUBLIC FUNCTION DEFINITION +*/ + +int mwl_fwdl_download_firmware(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + const struct firmware *fw; + u32 curr_iteration = 0; + u32 size_fw_downloaded = 0; + u32 int_code = 0; + u32 len = 0; + + WLDBG_ENTER(DBG_LEVEL_1); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + fw = priv->fw_ucode; + BUG_ON(!fw); + + mwl_fwcmd_reset(hw); + + /* FW before jumping to boot rom, it will enable PCIe transaction retry, wait for boot code to stop it. + */ + WL_MSEC_SLEEP(FW_CHECK_MSECS); + + writel(MACREG_A2HRIC_BIT_MASK, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CLEAR_SEL); + writel(0x00, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + writel(0x00, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + writel(MACREG_A2HRIC_BIT_MASK, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + /* this routine interacts with SC2 bootrom to download firmware binary + * to the device. After DMA'd to SC2, the firmware could be deflated to reside + * on its respective blocks such as ITCM, DTCM, SQRAM, + * (or even DDR, AFTER DDR is init'd before fw download + */ + WLDBG_PRINT("fw download start 88"); + + /* Disable PFU before FWDL + */ + writel(0x100, priv->iobase1 + 0xE0E4); + + /* make sure SCRATCH2 C40 is clear, in case we are too quick + */ + while (readl(priv->iobase1 + 0xc40) == 0); + + while (size_fw_downloaded < fw->size) { + + len = readl(priv->iobase1 + 0xc40); + + if (!len) + break; + + /* this copies the next chunk of fw binary to be delivered + */ + memcpy((char *)&priv->pcmd_buf[0], (fw->data + size_fw_downloaded), len); + + /* this function writes pdata to c10, then write 2 to c18 + */ + mwl_fwdl_trig_pcicmd_bootcode(priv); + + curr_iteration = FW_MAX_NUM_CHECKS; /* this is arbitrary per your platform; we use 0xffff */ + + /* NOTE: the following back to back checks on C1C is time sensitive, hence + * may need to be tweaked dependent on host processor. Time for SC2 to go from + * the write of event 2 to C1C == 2 is ~1300 nSec. Hence the checkings on host + * has to consider how efficient your code can be to meet this timing, or you + * can alternatively tweak this routines to fit your platform + */ + do { + + int_code = readl(priv->iobase1 + 0xc1c); + if (int_code != 0) + break; + curr_iteration--; + + } while (curr_iteration); + + do { + + int_code = readl(priv->iobase1 + 0xc1c); + if ((int_code & MACREG_H2ARIC_BIT_DOOR_BELL) != MACREG_H2ARIC_BIT_DOOR_BELL) + break; + curr_iteration--; + + } while (curr_iteration); + + if (curr_iteration == 0) { + + /* This limited loop check allows you to exit gracefully without locking up + * your entire system just because fw download failed + */ + WLDBG_PRINT("Exhausted curr_iteration during fw download"); + goto err_download; + } + + size_fw_downloaded += len; + } + + WLDBG_PRINT("FwSize = %d downloaded Size = %d curr_iteration %d", + (int)fw->size, size_fw_downloaded, curr_iteration); + + /* Now firware is downloaded successfully, so this part is to check + * whether fw can properly execute to an extent that write back signature + * to indicate its readiness to the host. NOTE: if your downloaded fw crashes, + * this signature checking will fail. This part is similar as SC1 + */ + writew(0x00, &priv->pcmd_buf[1]); + mwl_fwdl_trig_pcicmd(priv); + curr_iteration = FW_MAX_NUM_CHECKS; + do { + + curr_iteration--; + writel(HOSTCMD_SOFTAP_MODE, priv->iobase1 + MACREG_REG_GEN_PTR); + WL_MSEC_SLEEP(FW_CHECK_MSECS); + int_code = readl(priv->iobase1 + MACREG_REG_INT_CODE); + if (!(curr_iteration % 0xff)) + WLDBG_PRINT("%x;", int_code); + + } while ((curr_iteration) && (int_code != HOSTCMD_SOFTAP_FWRDY_SIGNATURE)); + + if (curr_iteration == 0) { + + WLDBG_PRINT("Exhausted curr_iteration waiting for fw signature; firmware seems failed to operate"); + goto err_download; + } + + WLDBG_PRINT("complete"); + writel(0x00, priv->iobase1 + MACREG_REG_INT_CODE); + + WLDBG_EXIT(DBG_LEVEL_1); + + return 0; + +err_download: + + mwl_fwcmd_reset(hw); + + return -EIO; +} + +/* PRIVATE FUNCTION DEFINITION +*/ + +static void mwl_fwdl_trig_pcicmd(struct mwl_priv *priv) +{ + WLDBG_ENTER(DBG_LEVEL_1); + + BUG_ON(!priv); + + writel(priv->pphys_cmd_buf, priv->iobase1 + MACREG_REG_GEN_PTR); + + writel(0x00, priv->iobase1 + MACREG_REG_INT_CODE); + + writel(MACREG_H2ARIC_BIT_DOOR_BELL, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); + + WLDBG_EXIT(DBG_LEVEL_1); +} + +static void mwl_fwdl_trig_pcicmd_bootcode(struct mwl_priv *priv) +{ + WLDBG_ENTER(DBG_LEVEL_1); + + BUG_ON(!priv); + + writel(priv->pphys_cmd_buf, priv->iobase1 + MACREG_REG_GEN_PTR); + + writel(0x00, priv->iobase1 + MACREG_REG_INT_CODE); + + writel(MACREG_H2ARIC_BIT_DOOR_BELL, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); + + WLDBG_EXIT(DBG_LEVEL_1); +} diff --git a/mwl_fwdl.h b/mwl_fwdl.h new file mode 100644 index 0000000..c9efe24 --- /dev/null +++ b/mwl_fwdl.h @@ -0,0 +1,32 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file defines firmware download related functions. +* +*/ + +#ifndef _mwl_fwdl_h_ +#define _mwl_fwdl_h_ + +#include + +/* PUBLIC FUNCTION DECLARATION +*/ +int mwl_fwdl_download_firmware(struct ieee80211_hw *hw); + +#endif /* _mwl_fwdl_h_ */ diff --git a/mwl_mac80211.c b/mwl_mac80211.c new file mode 100644 index 0000000..5f14bdc --- /dev/null +++ b/mwl_mac80211.c @@ -0,0 +1,850 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file implements mac80211 related functions. +* +*/ + +#include "mwl_sysadpt.h" +#include "mwl_dev.h" +#include "mwl_debug.h" +#include "mwl_fwcmd.h" +#include "mwl_tx.h" +#include "mwl_mac80211.h" + +/* CONSTANTS AND MACROS +*/ + +#define MWL_DRV_NAME KBUILD_MODNAME + +#define MAX_AMPDU_ATTEMPTS 5 + +/* PRIVATE FUNCTION DECLARATION +*/ + +static void mwl_mac80211_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb); +static int mwl_mac80211_start(struct ieee80211_hw *hw); +static void mwl_mac80211_stop(struct ieee80211_hw *hw); +static int mwl_mac80211_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); +static void mwl_mac80211_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); +static int mwl_mac80211_config(struct ieee80211_hw *hw, + u32 changed); +static void mwl_mac80211_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed); +static void mwl_mac80211_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast); +static int mwl_mac80211_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd_param, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key); +static int mwl_mac80211_set_rts_threshold(struct ieee80211_hw *hw, + u32 value); +static int mwl_mac80211_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +static int mwl_mac80211_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +static int mwl_mac80211_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u16 queue, + const struct ieee80211_tx_queue_params *params); +static int mwl_mac80211_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats); +static int mwl_mac80211_get_survey(struct ieee80211_hw *hw, + int idx, + struct survey_info *survey); +static int mwl_mac80211_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, + u16 tid, u16 *ssn, u8 buf_size); + +static void mwl_mac80211_remove_vif(struct mwl_priv *priv, + struct mwl_vif *vif); + +/* PRIVATE VARIABLES +*/ + +static struct ieee80211_ops mwl_mac80211_ops = { + .tx = mwl_mac80211_tx, + .start = mwl_mac80211_start, + .stop = mwl_mac80211_stop, + .add_interface = mwl_mac80211_add_interface, + .remove_interface = mwl_mac80211_remove_interface, + .config = mwl_mac80211_config, + .bss_info_changed = mwl_mac80211_bss_info_changed, + .configure_filter = mwl_mac80211_configure_filter, + .set_key = mwl_mac80211_set_key, + .set_rts_threshold = mwl_mac80211_set_rts_threshold, + .sta_add = mwl_mac80211_sta_add, + .sta_remove = mwl_mac80211_sta_remove, + .conf_tx = mwl_mac80211_conf_tx, + .get_stats = mwl_mac80211_get_stats, + .get_survey = mwl_mac80211_get_survey, + .ampdu_action = mwl_mac80211_ampdu_action, +}; + +static irqreturn_t (*mwl_mac80211_isr)(int irq, void *dev_id) = NULL; + +static const struct ieee80211_rate mwl_rates_24[] = { + { .bitrate = 10, .hw_value = 2, }, + { .bitrate = 20, .hw_value = 4, }, + { .bitrate = 55, .hw_value = 11, }, + { .bitrate = 110, .hw_value = 22, }, + { .bitrate = 220, .hw_value = 44, }, + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +static const struct ieee80211_rate mwl_rates_50[] = { + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +/* PUBLIC FUNCTION DEFINITION +*/ + +struct ieee80211_ops *mwl_mac80211_get_ops(void) +{ + return &mwl_mac80211_ops; +} + +void mwl_mac80211_set_isr(irqreturn_t (*isr)(int irq, void *dev_id)) +{ + WLDBG_ENTER(DBG_LEVEL_5); + + mwl_mac80211_isr = isr; + + WLDBG_EXIT(DBG_LEVEL_5); +} + +/* PRIVATE FUNCTION DEFINITION +*/ + +static void mwl_mac80211_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct mwl_priv *priv; + int index; + + WLDBG_ENTER(DBG_LEVEL_5); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!skb); + + if (!priv->radio_on) { + + WLDBG_EXIT_INFO(DBG_LEVEL_5, + "dropped TX frame since radio disabled"); + dev_kfree_skb_any(skb); + return; + } + + index = skb_get_queue_mapping(skb); + + mwl_tx_xmit(hw, index, control->sta, skb); + + WLDBG_EXIT(DBG_LEVEL_5); +} + +static int mwl_mac80211_start(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int rc; + + WLDBG_ENTER(DBG_LEVEL_5); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + rc = request_irq(priv->pdev->irq, mwl_mac80211_isr, + IRQF_SHARED, MWL_DRV_NAME, hw); + if (rc) { + priv->irq = -1; + WLDBG_ERROR(DBG_LEVEL_5, "fail to register IRQ handler"); + return rc; + } + priv->irq = priv->pdev->irq; + + /* Enable TX reclaim and RX tasklets. + */ + tasklet_enable(&priv->tx_task); + tasklet_enable(&priv->rx_task); + + /* Enable interrupts + */ + mwl_fwcmd_int_enable(hw); + + rc = mwl_fwcmd_radio_enable(hw); + + if (!rc) + rc = mwl_fwcmd_set_rate_adapt_mode(hw, 0); + + if (!rc) + rc = mwl_fwcmd_set_wmm_mode(hw, true); + + if (!rc) + rc = mwl_fwcmd_set_dwds_stamode(hw, true); + + if (!rc) + rc = mwl_fwcmd_set_fw_flush_timer(hw, 0); + + if (rc) { + + mwl_fwcmd_int_disable(hw); + free_irq(priv->pdev->irq, hw); + priv->irq = -1; + tasklet_disable(&priv->tx_task); + tasklet_disable(&priv->rx_task); + + } else { + + ieee80211_wake_queues(hw); + + } + + WLDBG_EXIT(DBG_LEVEL_5); + + return rc; +} + +static void mwl_mac80211_stop(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + WLDBG_ENTER(DBG_LEVEL_5); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + mwl_fwcmd_radio_disable(hw); + + ieee80211_stop_queues(hw); + + /* Disable interrupts + */ + mwl_fwcmd_int_disable(hw); + + if (priv->irq != -1) { + free_irq(priv->pdev->irq, hw); + priv->irq = -1; + } + + cancel_work_sync(&priv->watchdog_ba_handle); + + /* Disable TX reclaim and RX tasklets. + */ + tasklet_disable(&priv->tx_task); + tasklet_disable(&priv->rx_task); + + /* Return all skbs to mac80211 + */ + mwl_tx_done((unsigned long)hw); + + WLDBG_EXIT(DBG_LEVEL_5); +} + +static int mwl_mac80211_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + u32 macids_supported; + int macid; + + WLDBG_ENTER(DBG_LEVEL_5); + + macids_supported = priv->ap_macids_supported; + + macid = ffs(macids_supported & ~priv->macids_used); + + if (!macid--) { + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "no macid can be allocated"); + return -EBUSY; + } + + /* Setup driver private area. + */ + mwl_vif = MWL_VIF(vif); + memset(mwl_vif, 0, sizeof(*mwl_vif)); + mwl_vif->vif = vif; + mwl_vif->macid = macid; + mwl_vif->seqno = 0; + memcpy(mwl_vif->bssid, vif->addr, ETH_ALEN); + mwl_vif->is_hw_crypto_enabled = false; + mwl_vif->beacon_info.valid = false; + mwl_vif->iv16 = 1; + mwl_vif->iv32 = 0; + + mwl_fwcmd_set_new_stn_add_self(hw, vif); + + priv->macids_used |= 1 << mwl_vif->macid; + list_add_tail(&mwl_vif->list, &priv->vif_list); + + WLDBG_EXIT(DBG_LEVEL_5); + + return 0; +} + +static void mwl_mac80211_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif = MWL_VIF(vif); + + WLDBG_ENTER(DBG_LEVEL_5); + + mwl_fwcmd_set_new_stn_del(hw, vif, vif->addr); + + mwl_mac80211_remove_vif(priv, mwl_vif); + + WLDBG_EXIT(DBG_LEVEL_5); +} + +static int mwl_mac80211_config(struct ieee80211_hw *hw, + u32 changed) +{ + struct ieee80211_conf *conf = &hw->conf; + int rc; + + WLDBG_ENTER(DBG_LEVEL_5); + + WLDBG_INFO(DBG_LEVEL_5, "change: 0x%x", changed); + + if (conf->flags & IEEE80211_CONF_IDLE) + rc = mwl_fwcmd_radio_disable(hw); + else + rc = mwl_fwcmd_radio_enable(hw); + + if (rc) + goto out; + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + + if (conf->chandef.chan->band == IEEE80211_BAND_2GHZ) + mwl_fwcmd_set_apmode(hw, AP_MODE_2_4GHZ_11AC_MIXED); + else if (conf->chandef.chan->band == IEEE80211_BAND_5GHZ) + mwl_fwcmd_set_apmode(hw, AP_MODE_11AC); + + rc = mwl_fwcmd_set_rf_channel(hw, conf); + + if (rc) + goto out; + + rc = mwl_fwcmd_max_tx_power(hw, conf, 0); + + if (rc) + goto out; + + rc = mwl_fwcmd_tx_power(hw, conf, 0); + + if (rc) + goto out; + + rc = mwl_fwcmd_set_cdd(hw); + } + +out: + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc); + + return rc; +} + +static void mwl_mac80211_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + WLDBG_ENTER(DBG_LEVEL_5); + + WLDBG_INFO(DBG_LEVEL_5, "change: 0x%x", changed); + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + + mwl_fwcmd_set_radio_preamble(hw, + vif->bss_conf.use_short_preamble); + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + + int idx; + int rate; + + /* + * Use lowest supported basic rate for multicasts + * and management frames (such as probe responses -- + * beacons will always go out at 1 Mb/s). + */ + idx = ffs(vif->bss_conf.basic_rates); + if (idx) + idx--; + + if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) + rate = mwl_rates_24[idx].hw_value; + else + rate = mwl_rates_50[idx].hw_value; + + mwl_fwcmd_use_fixed_rate(hw, rate, rate); + } + + if (changed & (BSS_CHANGED_BEACON_INT | BSS_CHANGED_BEACON)) { + + struct sk_buff *skb; + + skb = ieee80211_beacon_get(hw, vif); + + if (skb != NULL) { + + mwl_fwcmd_set_beacon(hw, vif, skb->data, skb->len); + dev_kfree_skb_any(skb); + } + + if ((info->ssid[0] != '\0') && (info->ssid_len != 0)) + mwl_fwcmd_broadcast_ssid_enable(hw, vif, true); + else + mwl_fwcmd_broadcast_ssid_enable(hw, vif, false); + } + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + + mwl_fwcmd_bss_start(hw, vif, info->enable_beacon); + } + + WLDBG_EXIT(DBG_LEVEL_5); +} + +static void mwl_mac80211_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + WLDBG_ENTER(DBG_LEVEL_5); + + /* + * AP firmware doesn't allow fine-grained control over + * the receive filter. + */ + *total_flags &= FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC; + + WLDBG_EXIT(DBG_LEVEL_5); +} + +static int mwl_mac80211_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd_param, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + int rc = 0; + u8 encr_type; + u8 *addr; + struct mwl_vif *mwl_vif = MWL_VIF(vif); + struct mwl_sta *sta_info = MWL_STA(sta); + + WLDBG_ENTER(DBG_LEVEL_5); + + BUG_ON(!mwl_vif); + BUG_ON(!sta_info); + + if (vif->type == NL80211_IFTYPE_STATION) { + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "station mode is not supported"); + return -EOPNOTSUPP; + } + + if (sta == NULL) + addr = vif->addr; + else + addr = sta->addr; + + if (cmd_param == SET_KEY) { + + rc = mwl_fwcmd_encryption_set_key(hw, vif, addr, key); + + if (rc) + goto out; + + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40) + || (key->cipher == WLAN_CIPHER_SUITE_WEP104)) { + + encr_type = ENCR_TYPE_WEP; + + } else if (key->cipher == WLAN_CIPHER_SUITE_CCMP) { + + encr_type = ENCR_TYPE_AES; + + if ((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) == 0) + mwl_vif->keyidx = key->keyidx; + + } else if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + + encr_type = ENCR_TYPE_TKIP; + + } else { + + encr_type = ENCR_TYPE_DISABLE; + + } + + rc = mwl_fwcmd_update_encryption_enable(hw, vif, addr, + encr_type); + + if (rc) + goto out; + + mwl_vif->is_hw_crypto_enabled = true; + + } else { + + rc = mwl_fwcmd_encryption_remove_key(hw, vif, addr, key); + + if (rc) + goto out; + + } + +out: + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc); + + return rc; +} + +static int mwl_mac80211_set_rts_threshold(struct ieee80211_hw *hw, + u32 value) +{ + int rc; + + WLDBG_ENTER(DBG_LEVEL_5); + + rc = mwl_fwcmd_set_rts_threshold(hw, value); + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc); + + return rc; +} + +static int mwl_mac80211_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl_vif *mwl_vif = MWL_VIF(vif); + struct mwl_sta *sta_info = MWL_STA(sta); + struct ieee80211_key_conf *key; + int rc; + int i; + + WLDBG_ENTER(DBG_LEVEL_5); + + BUG_ON(!mwl_vif); + BUG_ON(!sta_info); + + memset(sta_info, 0, sizeof(*sta_info)); + sta_info->iv16 = 1; + sta_info->iv32 = 0; + if (sta->ht_cap.ht_supported) + sta_info->is_ampdu_allowed = true; + + rc = mwl_fwcmd_set_new_stn_add(hw, vif, sta); + + for (i = 0; i < NUM_WEP_KEYS; i++) { + + key = IEEE80211_KEY_CONF(mwl_vif->wep_key_conf[i].key); + + if (mwl_vif->wep_key_conf[i].enabled) + mwl_mac80211_set_key(hw, SET_KEY, vif, sta, key); + } + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc); + + return rc; +} + +static int mwl_mac80211_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + int rc; + + WLDBG_ENTER(DBG_LEVEL_5); + + rc = mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr); + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc); + + return rc; +} + +static int mwl_mac80211_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct mwl_priv *priv = hw->priv; + int rc = 0; + + WLDBG_ENTER(DBG_LEVEL_5); + + BUG_ON(queue > SYSADPT_TX_WMM_QUEUES - 1); + + memcpy(&priv->wmm_params[queue], params, sizeof(*params)); + + if (!priv->wmm_enabled) { + + rc = mwl_fwcmd_set_wmm_mode(hw, true); + priv->wmm_enabled = true; + } + + if (!rc) { + + int q = SYSADPT_TX_WMM_QUEUES - 1 - queue; + + rc = mwl_fwcmd_set_edca_params(hw, q, + params->cw_min, params->cw_max, + params->aifs, params->txop); + } + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc); + + return rc; +} + +static int mwl_mac80211_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + int rc; + + WLDBG_ENTER(DBG_LEVEL_5); + + rc = mwl_fwcmd_get_stat(hw, stats); + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "return code: %d", rc); + + return rc; +} + +static int mwl_mac80211_get_survey(struct ieee80211_hw *hw, + int idx, + struct survey_info *survey) +{ + struct mwl_priv *priv = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + + WLDBG_ENTER(DBG_LEVEL_5); + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->chandef.chan; + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = priv->noise; + + WLDBG_EXIT(DBG_LEVEL_5); + + return 0; +} + +static int mwl_mac80211_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, + u16 tid, u16 *ssn, u8 buf_size) +{ + int i, rc = 0; + struct mwl_priv *priv = hw->priv; + struct mwl_ampdu_stream *stream; + u8 *addr = sta->addr, idx; + struct mwl_sta *sta_info = MWL_STA(sta); + + WLDBG_ENTER(DBG_LEVEL_5); + + if (!(hw->flags & IEEE80211_HW_AMPDU_AGGREGATION)) { + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "no HW AMPDU"); + return -ENOTSUPP; + } + + SPIN_LOCK(&priv->locks.stream_lock); + + stream = mwl_fwcmd_lookup_stream(hw, addr, tid); + + switch (action) { + + case IEEE80211_AMPDU_RX_START: + case IEEE80211_AMPDU_RX_STOP: + break; + + case IEEE80211_AMPDU_TX_START: + /* By the time we get here the hw queues may contain outgoing + * packets for this RA/TID that are not part of this BA + * session. The hw will assign sequence numbers to these + * packets as they go out. So if we query the hw for its next + * sequence number and use that for the SSN here, it may end up + * being wrong, which will lead to sequence number mismatch at + * the recipient. To avoid this, we reset the sequence number + * to O for the first MPDU in this BA stream. + */ + *ssn = 0; + + if (stream == NULL) { + + /* This means that somebody outside this driver called + * ieee80211_start_tx_ba_session. This is unexpected + * because we do our own rate control. Just warn and + * move on. + */ + stream = mwl_fwcmd_add_stream(hw, sta, tid); + } + + if (stream == NULL) { + + WLDBG_EXIT_INFO(DBG_LEVEL_5, "no stream found"); + rc = -EBUSY; + break; + } + + stream->state = AMPDU_STREAM_IN_PROGRESS; + + /* Release the lock before we do the time consuming stuff + */ + SPIN_UNLOCK(&priv->locks.stream_lock); + + for (i = 0; i < MAX_AMPDU_ATTEMPTS; i++) { + + /* Check if link is still valid + */ + if (!sta_info->is_ampdu_allowed) { + + SPIN_LOCK(&priv->locks.stream_lock); + mwl_fwcmd_remove_stream(hw, stream); + SPIN_UNLOCK(&priv->locks.stream_lock); + WLDBG_EXIT_INFO(DBG_LEVEL_5, "link is no valid now"); + return -EBUSY; + } + + rc = mwl_fwcmd_check_ba(hw, stream, vif); + + if (!rc) + break; + + WL_MSEC_SLEEP(1000); + } + + SPIN_LOCK(&priv->locks.stream_lock); + + if (rc) { + + mwl_fwcmd_remove_stream(hw, stream); + WLDBG_EXIT_INFO(DBG_LEVEL_5, "error code: %d", rc); + rc = -EBUSY; + break; + } + + ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid); + break; + + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + + if (stream) { + + if (stream->state == AMPDU_STREAM_ACTIVE) { + + idx = stream->idx; + SPIN_UNLOCK(&priv->locks.stream_lock); + mwl_fwcmd_destroy_ba(hw, idx); + SPIN_LOCK(&priv->locks.stream_lock); + } + + mwl_fwcmd_remove_stream(hw, stream); + } + + ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid); + break; + + case IEEE80211_AMPDU_TX_OPERATIONAL: + + BUG_ON(stream == NULL); + BUG_ON(stream->state != AMPDU_STREAM_IN_PROGRESS); + SPIN_UNLOCK(&priv->locks.stream_lock); + rc = mwl_fwcmd_create_ba(hw, stream, buf_size, vif); + SPIN_LOCK(&priv->locks.stream_lock); + + if (!rc) + stream->state = AMPDU_STREAM_ACTIVE; + else { + + idx = stream->idx; + SPIN_UNLOCK(&priv->locks.stream_lock); + mwl_fwcmd_destroy_ba(hw, idx); + SPIN_LOCK(&priv->locks.stream_lock); + mwl_fwcmd_remove_stream(hw, stream); + } + break; + + default: + rc = -ENOTSUPP; + + } + + SPIN_UNLOCK(&priv->locks.stream_lock); + + WLDBG_EXIT(DBG_LEVEL_5); + + return rc; +} + +static void mwl_mac80211_remove_vif(struct mwl_priv *priv, struct mwl_vif *vif) +{ + if (!priv->macids_used) + return; + + priv->macids_used &= ~(1 << vif->macid); + list_del(&vif->list); +} diff --git a/mwl_mac80211.h b/mwl_mac80211.h new file mode 100644 index 0000000..1432e02 --- /dev/null +++ b/mwl_mac80211.h @@ -0,0 +1,35 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file defines mac80211 related functions. +* +*/ + +#ifndef _mwl_mac80211_h_ +#define _mwl_mac80211_h_ + +#include +#include + +/* PUBLIC FUNCTION DECLARATION +*/ + +struct ieee80211_ops *mwl_mac80211_get_ops(void); +void mwl_mac80211_set_isr(irqreturn_t (*isr)(int irq, void *dev_id)); + +#endif /* _mwl_mac80211_h_ */ diff --git a/mwl_main.c b/mwl_main.c new file mode 100644 index 0000000..c0b70df --- /dev/null +++ b/mwl_main.c @@ -0,0 +1,1070 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file implements main functions of this module. +* +*/ + +#include +#include + +#include "mwl_sysadpt.h" +#include "mwl_dev.h" +#include "mwl_debug.h" +#include "mwl_fwdl.h" +#include "mwl_fwcmd.h" +#include "mwl_tx.h" +#include "mwl_rx.h" +#include "mwl_mac80211.h" + +/* CONSTANTS AND MACROS +*/ + +#define MWL_DESC "Marvell 802.11ac Wireless Network Driver" +#define MWL_DEV_NAME "Marvell 88W8864 802.11ac Adapter" +#define MWL_DRV_NAME KBUILD_MODNAME +#define MWL_DRV_VERSION "10.2.6.1.p4" + +#define FILE_PATH_LEN 64 +#define CMD_BUF_SIZE 0x4000 + +#define INVALID_WATCHDOG 0xAA + +/* PRIVATE FUNCTION DECLARATION +*/ + +static int mwl_probe(struct pci_dev *pdev, const struct pci_device_id *id); +static void mwl_remove(struct pci_dev *pdev); +static int mwl_alloc_pci_resource(struct mwl_priv *priv); +static void mwl_free_pci_resource(struct mwl_priv *priv); +static int mwl_init_firmware(struct mwl_priv *priv, char *fw_image); +static int mwl_load_tx_pwr_tbl(struct mwl_priv *priv, char *pwr_tbl); +static void mwl_set_ht_caps(struct ieee80211_hw *hw, + struct ieee80211_supported_band *band); +static void mwl_set_vht_caps(struct ieee80211_hw *hw, + struct ieee80211_supported_band *band); +static void mwl_set_caps(struct mwl_priv *priv); +static int mwl_wl_init(struct mwl_priv *priv); +static void mwl_wl_deinit(struct mwl_priv *priv); +static void mwl_watchdog_ba_events(struct work_struct *work); +static irqreturn_t mwl_interrupt(int irq, void *dev_id); + +static int atoi(const char *num_str); +static long atohex(const char *number); +static long atohex2(const char *number); + +/* PRIVATE VARIABLES +*/ + +static char fw_image_path[FILE_PATH_LEN] = "88W8864.bin"; +static char pwr_tbl_path[FILE_PATH_LEN] = "PWR_TABLE.ini"; + +static struct pci_device_id mwl_pci_id_tbl[SYSADPT_MAX_CARDS_SUPPORT + 1] = { + + { 0x11ab, 0x2a55, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long)MWL_DEV_NAME }, + { 0, 0, 0, 0, 0, 0, 0 } + +}; + +static struct pci_driver mwl_pci_driver = { + + .name = MWL_DRV_NAME, + .id_table = mwl_pci_id_tbl, + .probe = mwl_probe, + .remove = mwl_remove + +}; + +static const struct ieee80211_channel mwl_channels_24[] = { + + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2412, .hw_value = 1, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2417, .hw_value = 2, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2422, .hw_value = 3, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2427, .hw_value = 4, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2432, .hw_value = 5, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2437, .hw_value = 6, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2442, .hw_value = 7, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2447, .hw_value = 8, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2452, .hw_value = 9, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2457, .hw_value = 10, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2462, .hw_value = 11, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2467, .hw_value = 12, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2472, .hw_value = 13, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2484, .hw_value = 14, }, + +}; + +static const struct ieee80211_rate mwl_rates_24[] = { + + { .bitrate = 10, .hw_value = 2, }, + { .bitrate = 20, .hw_value = 4, }, + { .bitrate = 55, .hw_value = 11, }, + { .bitrate = 110, .hw_value = 22, }, + { .bitrate = 220, .hw_value = 44, }, + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, + +}; + +static const struct ieee80211_channel mwl_channels_50[] = { + + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5180, .hw_value = 36, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5200, .hw_value = 40, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5220, .hw_value = 44, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5240, .hw_value = 48, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5260, .hw_value = 52, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5280, .hw_value = 56, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5300, .hw_value = 60, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5320, .hw_value = 64, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5500, .hw_value = 100, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5520, .hw_value = 104, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5540, .hw_value = 108, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5560, .hw_value = 112, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5580, .hw_value = 116, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5600, .hw_value = 120, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5620, .hw_value = 124, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5640, .hw_value = 128, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5660, .hw_value = 132, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5680, .hw_value = 136, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5700, .hw_value = 140, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5720, .hw_value = 144, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5745, .hw_value = 149, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5765, .hw_value = 153, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5785, .hw_value = 157, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5805, .hw_value = 161, }, + +}; + +static const struct ieee80211_rate mwl_rates_50[] = { + + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, + +}; + +static const struct ieee80211_iface_limit ap_if_limits[] = { + + { .max = 8, .types = BIT(NL80211_IFTYPE_AP) }, + { .max = 1, .types = BIT(NL80211_IFTYPE_STATION) }, + +}; + +static const struct ieee80211_iface_combination ap_if_comb = { + + .limits = ap_if_limits, + .n_limits = ARRAY_SIZE(ap_if_limits), + .max_interfaces = 8, + .num_different_channels = 1, + +}; + +/* PUBLIC FUNCTION DEFINITION +*/ + +module_param_string(fw_name, fw_image_path, FILE_PATH_LEN, 0); +MODULE_PARM_DESC(fw_name, "Specify where to load the F/W image"); +module_param_string(pwr_tbl, pwr_tbl_path, FILE_PATH_LEN, 0); +MODULE_PARM_DESC(pwr_tbl, "Specify where to load TX power table"); + +module_pci_driver(mwl_pci_driver); + +MODULE_DESCRIPTION(MWL_DESC); +MODULE_VERSION(MWL_DRV_VERSION); +MODULE_AUTHOR("Marvell Semiconductor, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_SUPPORTED_DEVICE(MWL_DEV_NAME); +MODULE_DEVICE_TABLE(pci, mwl_pci_id_tbl); + +/* PRIVATE FUNCTION DEFINITION +*/ + +static int mwl_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + static int printed_version; + struct ieee80211_hw *hw; + struct mwl_priv *priv; + int rc = 0; + + WLDBG_ENTER(DBG_LEVEL_0); + + if (!printed_version) { + + WLDBG_PRINT("<<%s version %s>>", MWL_DESC, MWL_DRV_VERSION); + printed_version = 1; + } + + rc = pci_enable_device(pdev); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot enable new PCI device", + MWL_DRV_NAME); + WLDBG_EXIT_INFO(DBG_LEVEL_0, "init error"); + return rc; + } + + rc = pci_set_dma_mask(pdev, 0xffffffff); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: 32-bit PCI DMA not supported", + MWL_DRV_NAME); + goto err_pci_disable_device; + } + + pci_set_master(pdev); + + hw = ieee80211_alloc_hw(sizeof(*priv), mwl_mac80211_get_ops()); + if (hw == NULL) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: ieee80211 alloc failed", + MWL_DRV_NAME); + rc = -ENOMEM; + goto err_pci_disable_device; + } + + /* set interrupt service routine to mac80211 module + */ + mwl_mac80211_set_isr(mwl_interrupt); + + SET_IEEE80211_DEV(hw, &pdev->dev); + pci_set_drvdata(pdev, hw); + + priv = hw->priv; + priv->hw = hw; + priv->pdev = pdev; + + rc = mwl_alloc_pci_resource(priv); + if (rc) + goto err_alloc_pci_resource; + + rc = mwl_init_firmware(priv, fw_image_path); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to initialize firmware", + MWL_DRV_NAME); + goto err_init_firmware; + } + + /* firmware is loaded to H/W, it can be released now + */ + release_firmware(priv->fw_ucode); + + rc = mwl_load_tx_pwr_tbl(priv, pwr_tbl_path); + if (rc) { + + WLDBG_PRINT("%s: fail to load tx power table", + MWL_DRV_NAME); + } + + rc = mwl_wl_init(priv); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to initialize wireless lan", + MWL_DRV_NAME); + goto err_wl_init; + } + + WLDBG_EXIT(DBG_LEVEL_0); + + return rc; + +err_wl_init: +err_init_firmware: + + mwl_fwcmd_reset(hw); + +err_alloc_pci_resource: + + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); + +err_pci_disable_device: + + pci_disable_device(pdev); + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "init error"); + + return rc; +} + +static void mwl_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct mwl_priv *priv; + + WLDBG_ENTER(DBG_LEVEL_0); + + if (hw == NULL) { + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "ieee80211 hw is null"); + return; + } + + priv = hw->priv; + BUG_ON(!priv); + + mwl_wl_deinit(priv); + mwl_free_pci_resource(priv); + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); + pci_disable_device(pdev); + + WLDBG_EXIT(DBG_LEVEL_0); +} + +static int mwl_alloc_pci_resource(struct mwl_priv *priv) +{ + struct pci_dev *pdev; + u32 phys_addr = 0; + u32 flags; + void *phys_addr1[2]; + void *phys_addr2[2]; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + pdev = priv->pdev; + BUG_ON(!pdev); + + phys_addr = pci_resource_start(pdev, 0); + flags = pci_resource_flags(pdev, 0); + + priv->next_bar_num = 1; /* 32-bit */ + + if (flags & 0x04) + priv->next_bar_num = 2; /* 64-bit */ + + if (!request_mem_region(phys_addr, pci_resource_len(pdev, 0), MWL_DRV_NAME)) { + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot reserve PCI memory region 0", + MWL_DRV_NAME); + goto err_reserve_mem_region_bar0; + } + + phys_addr1[0] = ioremap(phys_addr, pci_resource_len(pdev, 0)); + phys_addr1[1] = 0; + + priv->iobase0 = phys_addr1[0]; + if (!priv->iobase0) { + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot remap PCI memory region 0", + MWL_DRV_NAME); + goto err_release_mem_region_bar0; + } + + WLDBG_PRINT("priv->iobase0 = %x", (unsigned int)priv->iobase0); + + phys_addr = pci_resource_start(pdev, priv->next_bar_num); + + if (!request_mem_region(phys_addr, pci_resource_len(pdev, priv->next_bar_num), MWL_DRV_NAME)) { + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot reserve PCI memory region 1", + MWL_DRV_NAME); + goto err_iounmap_iobase0; + } + + phys_addr2[0] = ioremap(phys_addr, pci_resource_len(pdev, priv->next_bar_num)); + phys_addr2[1] = 0; + priv->iobase1 = phys_addr2[0]; + + if (!priv->iobase1) + { + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot remap PCI memory region 1", + MWL_DRV_NAME); + goto err_release_mem_region_bar1; + } + + WLDBG_PRINT("priv->iobase1 = %x", (unsigned int)priv->iobase1); + + priv->pcmd_buf = (unsigned short *) + pci_alloc_consistent(priv->pdev, CMD_BUF_SIZE, &priv->pphys_cmd_buf); + + if (priv->pcmd_buf == NULL) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot alloc memory for command buffer", + MWL_DRV_NAME); + goto err_iounmap_iobase1; + } + + WLDBG_PRINT("priv->pcmd_buf = %x priv->pphys_cmd_buf = %x", + (unsigned int)priv->pcmd_buf, (unsigned int)priv->pphys_cmd_buf); + + memset(priv->pcmd_buf, 0x00, CMD_BUF_SIZE); + + WLDBG_EXIT(DBG_LEVEL_0); + + return 0; + +err_iounmap_iobase1: + + iounmap(priv->iobase1); + +err_release_mem_region_bar1: + + release_mem_region(pci_resource_start(pdev, 1), pci_resource_len(pdev, 1)); + +err_iounmap_iobase0: + + iounmap(priv->iobase0); + +err_release_mem_region_bar0: + + release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); + +err_reserve_mem_region_bar0: + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "pci alloc fail"); + + return -EIO; + +} + +static void mwl_free_pci_resource(struct mwl_priv *priv) +{ + struct pci_dev *pdev; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + pdev = priv->pdev; + BUG_ON(!pdev); + + iounmap(priv->iobase0); + iounmap(priv->iobase1); + release_mem_region(pci_resource_start(pdev, priv->next_bar_num), pci_resource_len(pdev, priv->next_bar_num)); + release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); + pci_free_consistent(priv->pdev, CMD_BUF_SIZE, priv->pcmd_buf, priv->pphys_cmd_buf); + + WLDBG_EXIT(DBG_LEVEL_0); +} + +static int mwl_init_firmware(struct mwl_priv *priv, char *fw_name) +{ + struct pci_dev *pdev; + int rc = 0; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + pdev = priv->pdev; + BUG_ON(!pdev); + + rc = request_firmware(&priv->fw_ucode, fw_name, &priv->pdev->dev); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot load firmware image <%s>", + MWL_DRV_NAME, fw_name); + goto err_load_fw; + } + + rc = mwl_fwdl_download_firmware(priv->hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot download firmware image <%s>", + MWL_DRV_NAME, fw_name); + goto err_download_fw; + } + + WLDBG_EXIT(DBG_LEVEL_0); + + return rc; + +err_download_fw: + + release_firmware(priv->fw_ucode); + +err_load_fw: + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "firmware init fail"); + + return rc; +} + +static int mwl_load_tx_pwr_tbl(struct mwl_priv *priv, char *pwr_tbl) +{ + struct file *filp = NULL; + mm_segment_t oldfs; + char buff[120], *s; + int len, index = 0, i, value = 0; + char param[20][32]; + int rc = 0; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + + oldfs = get_fs(); + set_fs(KERNEL_DS); + + filp = filp_open(pwr_tbl, O_RDONLY, 0); + + if (!IS_ERR(filp)) { /* MUST use this one, important!!! */ + + WLDBG_PRINT("open tx power table <%s>: OK", pwr_tbl); + + /* reset the whole table */ + for (i = 0; i < SYSADPT_MAX_NUM_CHANNELS; i++) + memset(&priv->tx_pwr_tbl[i], 0, sizeof(struct mwl_tx_pwr_tbl)); + + while (1) { + + s = buff; + while ((len = vfs_read(filp, s, 0x01, &filp->f_pos)) == 1) { + + if (*s == '\n') { + /* skip blank line */ + if (s == buff) + break; + + /* parse this line and assign value to data structure */ + *s = '\0'; + /* WLDBG_PRINT("index=<%d>: <%s>", index, buff); */ + /* 8864 total param: ch + setcap + 16 txpower + CDD + tx2 = 16 */ + sscanf(buff, "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n", param[0], param[1], param[2], param[3], param[4], param[5] + , param[6], param[7], param[8], param[9], param[10], param[11], param[12], param[13], param[14], param[15], param[16], param[17] + , param[18], param[19]); + + if (strcmp(param[18], "on") == 0) + value = 1; + else if (strcmp(param[18], "off") == 0) + value = 0; + else { + WLDBG_PRINT("txpower table format error: CCD should be on|off"); + break; + } + + priv->tx_pwr_tbl[index].cdd = value; + priv->tx_pwr_tbl[index].txantenna2 = atohex2(param[19]); + priv->tx_pwr_tbl[index].channel = atoi(param[0]); + priv->tx_pwr_tbl[index].setcap = atoi(param[1]); + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) + priv->tx_pwr_tbl[index].tx_power[i] = atohex2(param[i+2]); + + index++; + break; + + } else { + + s++; + + } + } + + if (len <= 0) + break; + } + + filp_close(filp, current->files); + + } else { + + WLDBG_PRINT("open tx power table <%s>: FAIL", pwr_tbl); + rc = -EIO; + + } + + set_fs(oldfs); + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "result: %d", rc); + + return rc; +} + +static void mwl_set_ht_caps(struct ieee80211_hw *hw, + struct ieee80211_supported_band *band) +{ + band->ht_cap.ht_supported = 1; + + band->ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING; + band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + band->ht_cap.cap |= IEEE80211_HT_CAP_SM_PS; + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; + + hw->flags |= IEEE80211_HW_AMPDU_AGGREGATION; + band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; + + band->ht_cap.mcs.rx_mask[0] = 0xff; + band->ht_cap.mcs.rx_mask[1] = 0xff; + band->ht_cap.mcs.rx_mask[2] = 0xff; + band->ht_cap.mcs.rx_mask[4] = 0x01; + + band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; +} + +static void mwl_set_vht_caps(struct ieee80211_hw *hw, + struct ieee80211_supported_band *band) +{ + band->vht_cap.vht_supported = 1; + + band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN; + band->vht_cap.cap |= IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; + + band->vht_cap.vht_mcs.rx_mcs_map = 0xffea; + band->vht_cap.vht_mcs.tx_mcs_map = 0xffea; +} + +static void mwl_set_caps(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + hw = priv->hw; + BUG_ON(!hw); + + /* set up band information for 2.4G + */ + BUILD_BUG_ON(sizeof(priv->channels_24) != sizeof(mwl_channels_24)); + memcpy(priv->channels_24, mwl_channels_24, sizeof(mwl_channels_24)); + + BUILD_BUG_ON(sizeof(priv->rates_24) != sizeof(mwl_rates_24)); + memcpy(priv->rates_24, mwl_rates_24, sizeof(mwl_rates_24)); + + priv->band_24.band = IEEE80211_BAND_2GHZ; + priv->band_24.channels = priv->channels_24; + priv->band_24.n_channels = ARRAY_SIZE(mwl_channels_24); + priv->band_24.bitrates = priv->rates_24; + priv->band_24.n_bitrates = ARRAY_SIZE(mwl_rates_24); + + mwl_set_ht_caps(hw, &priv->band_24); + mwl_set_vht_caps(hw, &priv->band_24); + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band_24; + + /* set up band information for 5G + */ + BUILD_BUG_ON(sizeof(priv->channels_50) != sizeof(mwl_channels_50)); + memcpy(priv->channels_50, mwl_channels_50, sizeof(mwl_channels_50)); + + BUILD_BUG_ON(sizeof(priv->rates_50) != sizeof(mwl_rates_50)); + memcpy(priv->rates_50, mwl_rates_50, sizeof(mwl_rates_50)); + + priv->band_50.band = IEEE80211_BAND_5GHZ; + priv->band_50.channels = priv->channels_50; + priv->band_50.n_channels = ARRAY_SIZE(mwl_channels_50); + priv->band_50.bitrates = priv->rates_50; + priv->band_50.n_bitrates = ARRAY_SIZE(mwl_rates_50); + + mwl_set_ht_caps(hw, &priv->band_50); + mwl_set_vht_caps(hw, &priv->band_50); + + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &priv->band_50; + + WLDBG_EXIT(DBG_LEVEL_0); +} + +static int mwl_wl_init(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw; + int rc; + int i; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + hw = priv->hw; + BUG_ON(!hw); + + /* + * Extra headroom is the size of the required DMA header + * minus the size of the smallest 802.11 frame (CTS frame). + */ + hw->extra_tx_headroom = + sizeof(struct mwl_dma_data) - sizeof(struct ieee80211_cts); + hw->queues = SYSADPT_TX_WMM_QUEUES; + + /* Set rssi values to dBm + */ + hw->flags |= IEEE80211_HW_SIGNAL_DBM | IEEE80211_HW_HAS_RATE_CONTROL; + + /* + * Ask mac80211 to not to trigger PS mode + * based on PM bit of incoming frames. + */ + hw->flags |= IEEE80211_HW_AP_LINK_PS; + + hw->vif_data_size = sizeof(struct mwl_vif); + hw->sta_data_size = sizeof(struct mwl_sta); + + priv->ap_macids_supported = 0x000000ff; + priv->sta_macids_supported = 0x00000100; + priv->macids_used = 0; + INIT_LIST_HEAD(&priv->vif_list); + + /* Set default radio state, preamble and wmm + */ + priv->radio_on = false; + priv->radio_short_preamble = false; + priv->wmm_enabled = false; + + priv->powinited = 0; + + /* Handle watchdog ba events + */ + INIT_WORK(&priv->watchdog_ba_handle, mwl_watchdog_ba_events); + + tasklet_init(&priv->tx_task, (void *)mwl_tx_done, (unsigned long)hw); + tasklet_disable(&priv->tx_task); + tasklet_init(&priv->rx_task, (void *)mwl_rx_recv, (unsigned long)hw); + tasklet_disable(&priv->rx_task); + priv->txq_limit = SYSADPT_TX_QUEUE_LIMIT; + priv->is_tx_schedule = false; + priv->recv_limit = SYSADPT_RECEIVE_LIMIT; + priv->is_rx_schedule = false; + + SPIN_LOCK_INIT(&priv->locks.xmit_lock); + SPIN_LOCK_INIT(&priv->locks.fwcmd_lock); + SPIN_LOCK_INIT(&priv->locks.stream_lock); + + rc = mwl_tx_init(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to initialize TX", + MWL_DRV_NAME); + goto err_mwl_tx_init; + } + + rc = mwl_rx_init(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to initialize RX", + MWL_DRV_NAME); + goto err_mwl_rx_init; + } + + rc = mwl_fwcmd_get_hw_specs(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to get HW specifications", + MWL_DRV_NAME); + goto err_get_hw_specs; + } + + SET_IEEE80211_PERM_ADDR(hw, priv->hw_data.mac_addr); + + writel(priv->desc_data[0].pphys_tx_ring, + priv->iobase0 + priv->desc_data[0].wcb_base); +#if SYSADPT_NUM_OF_DESC_DATA > 3 + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + writel(priv->desc_data[i].pphys_tx_ring, + priv->iobase0 + priv->desc_data[i].wcb_base); +#endif + writel(priv->desc_data[0].pphys_rx_ring, + priv->iobase0 + priv->desc_data[0].rx_desc_read); + writel(priv->desc_data[0].pphys_rx_ring, + priv->iobase0 + priv->desc_data[0].rx_desc_write); + + rc = mwl_fwcmd_set_hw_specs(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to set HW specifications", + MWL_DRV_NAME); + goto err_set_hw_specs; + } + + mwl_fwcmd_radio_disable(hw); + + mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_RX, 0); + + mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_TX, 0); + + hw->wiphy->interface_modes = 0; + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP); + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_STATION); + hw->wiphy->iface_combinations = &ap_if_comb; + hw->wiphy->n_iface_combinations = 1; + + mwl_set_caps(priv); + + rc = ieee80211_register_hw(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to register device", + MWL_DRV_NAME); + goto err_register_hw; + } + + WLDBG_EXIT(DBG_LEVEL_0); + + return rc; + +err_register_hw: +err_set_hw_specs: +err_get_hw_specs: + + mwl_rx_deinit(hw); + +err_mwl_rx_init: + + mwl_tx_deinit(hw); + +err_mwl_tx_init: + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "init fail"); + + return rc; +} + +static void mwl_wl_deinit(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + hw = priv->hw; + BUG_ON(!hw); + + ieee80211_unregister_hw(hw); + mwl_rx_deinit(hw); + mwl_tx_deinit(hw); + tasklet_kill(&priv->rx_task); + tasklet_kill(&priv->tx_task); + mwl_fwcmd_reset(hw); + + WLDBG_EXIT(DBG_LEVEL_0); +} + +static void mwl_watchdog_ba_events(struct work_struct *work) +{ + int rc; + u8 bitmap = 0, stream_index; + struct mwl_ampdu_stream *streams; + struct mwl_priv *priv = + container_of(work, struct mwl_priv, watchdog_ba_handle); + struct ieee80211_hw *hw = priv->hw; + u32 status; + + rc = mwl_fwcmd_get_watchdog_bitmap(priv->hw, &bitmap); + + if (rc) + goto done; + + SPIN_LOCK(&priv->locks.stream_lock); + + /* the bitmap is the hw queue number. Map it to the ampdu queue. + */ + if (bitmap != INVALID_WATCHDOG) { + + if (bitmap == SYSADPT_TX_AMPDU_QUEUES) + stream_index = 0; + else if (bitmap > SYSADPT_TX_AMPDU_QUEUES) + stream_index = bitmap - SYSADPT_TX_AMPDU_QUEUES; + else + stream_index = bitmap + 3; /** queue 0 is stream 3*/ + + if (bitmap != 0xFF) { + + /* Check if the stream is in use before disabling it + */ + streams = &priv->ampdu[stream_index]; + + if (streams->state == AMPDU_STREAM_ACTIVE) { + + ieee80211_stop_tx_ba_session(streams->sta, + streams->tid); + SPIN_UNLOCK(&priv->locks.stream_lock); + mwl_fwcmd_destroy_ba(hw, stream_index); + SPIN_LOCK(&priv->locks.stream_lock); + } + + } else { + + for (stream_index = 0; stream_index < SYSADPT_TX_AMPDU_QUEUES; stream_index++) { + + streams = &priv->ampdu[stream_index]; + + if (streams->state == AMPDU_STREAM_ACTIVE) { + + ieee80211_stop_tx_ba_session(streams->sta, + streams->tid); + SPIN_UNLOCK(&priv->locks.stream_lock); + mwl_fwcmd_destroy_ba(hw, stream_index); + SPIN_LOCK(&priv->locks.stream_lock); + } + } + } + } + + SPIN_UNLOCK(&priv->locks.stream_lock); + +done: + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel(status | MACREG_A2HRIC_BA_WATCHDOG, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + return; +} + +static irqreturn_t mwl_interrupt(int irq, void *dev_id) +{ + struct ieee80211_hw *hw = dev_id; + struct mwl_priv *priv; + unsigned int int_status, clr_status; + u32 status; + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + int_status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + + if (int_status == 0x00000000) + return IRQ_NONE; + + if (int_status == 0xffffffff) { + + WLDBG_INFO(DBG_LEVEL_0, "card plugged out???"); + + } else { + + clr_status = int_status; + + if (int_status & MACREG_A2HRIC_BIT_TX_DONE) { + + int_status &= ~MACREG_A2HRIC_BIT_TX_DONE; + + if (priv->is_tx_schedule == false) { + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel((status & ~MACREG_A2HRIC_BIT_TX_DONE), + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + tasklet_schedule(&priv->tx_task); + priv->is_tx_schedule = true; + } + } + + if (int_status & MACREG_A2HRIC_BIT_RX_RDY) { + + int_status &= ~MACREG_A2HRIC_BIT_RX_RDY; + + if (priv->is_rx_schedule == false) { + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel((status & ~MACREG_A2HRIC_BIT_RX_RDY), + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + tasklet_schedule(&priv->rx_task); + priv->is_rx_schedule = true; + } + } + + if (int_status & MACREG_A2HRIC_BA_WATCHDOG) { + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel((status & ~MACREG_A2HRIC_BA_WATCHDOG), + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + int_status &= ~MACREG_A2HRIC_BA_WATCHDOG; + ieee80211_queue_work(hw, &priv->watchdog_ba_handle); + } + + writel(~clr_status, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + } + + return IRQ_HANDLED; +} + +static int atoi(const char *num_str) +{ + int val = 0; + + for (;; num_str++) { + + switch (*num_str) { + + case '0'...'9': + val = 10*val+(*num_str-'0'); + break; + + default: + return val; + } + } +} + +static long atohex(const char *number) +{ + long n = 0; + + if (*number == '0' && (*(number + 1) == 'x' || *(number + 1) == 'X')) + number += 2; + + while (*number <= ' ' && *number > 0) + ++number; + + while ((*number >= '0' && *number <= '9') || + (*number >= 'A' && *number <= 'F') || + (*number >= 'a' && *number <= 'f')) { + + if (*number >= '0' && *number <= '9') { + + n = (n * 0x10) + ((*number++) - '0'); + + } else if (*number >= 'A' && *number <= 'F') { + + n = (n * 0x10) + ((*number++) - 'A' + 10); + + } else { + + n = (n * 0x10) + ((*number++) - 'a' + 10); + + } + } + + return n; +} + +static long atohex2(const char *number) +{ + long n = 0; + + while (*number <= ' ' && *number > 0) + ++number; + + if (*number == 0) + return n; + + if (*number == '0' && (*(number + 1) == 'x' || *(number + 1) == 'X')) + n = atohex(number+2); + else + n = atoi(number); + + return n; +} diff --git a/mwl_rx.c b/mwl_rx.c new file mode 100644 index 0000000..90cea3a --- /dev/null +++ b/mwl_rx.c @@ -0,0 +1,528 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file implements receive related functions. +* +*/ + +#include "mwl_sysadpt.h" +#include "mwl_dev.h" +#include "mwl_debug.h" +#include "mwl_rx.h" + +/* CONSTANTS AND MACROS +*/ + +#define MAX_NUM_RX_RING_BYTES SYSADPT_MAX_NUM_RX_DESC * sizeof(struct mwl_rx_desc) + +#define FIRST_RXD priv->desc_data[0].prx_ring[0] +#define CURR_RXD priv->desc_data[0].prx_ring[curr_desc] +#define NEXT_RXD priv->desc_data[0].prx_ring[curr_desc + 1] +#define LAST_RXD priv->desc_data[0].prx_ring[SYSADPT_MAX_NUM_RX_DESC - 1] + +#define DECRYPT_ERR_MASK 0x80 +#define GENERAL_DECRYPT_ERR 0xFF +#define TKIP_DECRYPT_MIC_ERR 0x02 +#define WEP_DECRYPT_ICV_ERR 0x04 +#define TKIP_DECRYPT_ICV_ERR 0x08 + +#define W836X_RSSI_OFFSET 8 + +/* PRIVATE FUNCTION DECLARATION +*/ + +static int mwl_rx_ring_alloc(struct mwl_priv *priv); +static int mwl_rx_ring_init(struct mwl_priv *priv); +static void mwl_rx_ring_cleanup(struct mwl_priv *priv); +static void mwl_rx_ring_free(struct mwl_priv *priv); +static inline void mwl_rx_prepare_status(struct mwl_rx_desc *pdesc, + struct ieee80211_rx_status *status); +static inline struct mwl_vif *mwl_rx_find_vif_bss(struct list_head *vif_list, u8 *bssid); +static inline void mwl_rx_remove_dma_header(struct sk_buff *skb, u16 qos); +static int mwl_rx_refill(struct mwl_priv *priv, struct mwl_rx_desc *pdesc); + +/* PUBLIC FUNCTION DEFINITION +*/ + +int mwl_rx_init(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int rc; + + WLDBG_ENTER(DBG_LEVEL_4); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + rc = mwl_rx_ring_alloc(priv); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_4, "allocating RX ring failed"); + + } else { + + rc = mwl_rx_ring_init(priv); + if (rc) { + + mwl_rx_ring_free(priv); + WLDBG_ERROR(DBG_LEVEL_4, "initializing RX ring failed"); + } + } + + WLDBG_EXIT(DBG_LEVEL_4); + + return rc; +} + +void mwl_rx_deinit(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + WLDBG_ENTER(DBG_LEVEL_4); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + mwl_rx_ring_cleanup(priv); + mwl_rx_ring_free(priv); + + WLDBG_EXIT(DBG_LEVEL_4); +} + +void mwl_rx_recv(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv; + struct mwl_rx_desc *curr_desc; + int work_done = 0; + struct sk_buff *prx_skb = NULL; + int pkt_len; + struct ieee80211_rx_status status; + struct mwl_vif *mwl_vif = NULL; + struct ieee80211_hdr *wh; + u32 status_mask; + + WLDBG_ENTER(DBG_LEVEL_4); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + curr_desc = priv->desc_data[0].pnext_rx_desc; + + if (curr_desc == NULL) { + + status_mask = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel(status_mask | MACREG_A2HRIC_BIT_RX_RDY, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + priv->is_rx_schedule = false; + + WLDBG_EXIT_INFO(DBG_LEVEL_4, "busy or no receiving packets"); + return; + } + + while ((curr_desc->rx_control == EAGLE_RXD_CTRL_DMA_OWN) + && (work_done < priv->recv_limit)) { + + prx_skb = curr_desc->psk_buff; + if (prx_skb == NULL) + goto out; + pci_unmap_single(priv->pdev, + ENDIAN_SWAP32(curr_desc->pphys_buff_data), + priv->desc_data[0].rx_buf_size, + PCI_DMA_FROMDEVICE); + pkt_len = curr_desc->pkt_len; + + if (skb_tailroom(prx_skb) < pkt_len) { + + WLDBG_INFO(DBG_LEVEL_4, "Not enough tail room =%x pkt_len=%x, curr_desc=%x, curr_desc_data=%x", + skb_tailroom(prx_skb), pkt_len, curr_desc, curr_desc->pbuff_data); + dev_kfree_skb_any(prx_skb); + goto out; + } + + mwl_rx_prepare_status(curr_desc, &status); + + priv->noise = -curr_desc->noise_floor; + + wh = &((struct mwl_dma_data *)prx_skb->data)->wh; + + if (ieee80211_has_protected(wh->frame_control)) { + + /* Check if hw crypto has been enabled for + * this bss. If yes, set the status flags + * accordingly + */ + mwl_vif = mwl_rx_find_vif_bss(&priv->vif_list, + wh->addr1); + + if (mwl_vif != NULL && + mwl_vif->is_hw_crypto_enabled) { + /* + * When MMIC ERROR is encountered + * by the firmware, payload is + * dropped and only 32 bytes of + * mwl8k Firmware header is sent + * to the host. + * + * We need to add four bytes of + * key information. In it + * MAC80211 expects keyidx set to + * 0 for triggering Counter + * Measure of MMIC failure. + */ + if (status.flag & RX_FLAG_MMIC_ERROR) { + + struct mwl_dma_data *tr; + + tr = (struct mwl_dma_data *)prx_skb->data; + memset((void *)&(tr->data), 0, 4); + pkt_len += 4; + } + + if (!ieee80211_is_auth(wh->frame_control)) + + status.flag |= RX_FLAG_IV_STRIPPED | + RX_FLAG_DECRYPTED | + RX_FLAG_MMIC_STRIPPED; + } + } + + skb_put(prx_skb, pkt_len); + mwl_rx_remove_dma_header(prx_skb, curr_desc->qos_ctrl); + memcpy(IEEE80211_SKB_RXCB(prx_skb), &status, sizeof(status)); + ieee80211_rx(hw, prx_skb); + + mwl_rx_refill(priv, curr_desc); + +out: + curr_desc->rx_control = EAGLE_RXD_CTRL_DRIVER_OWN; + curr_desc->qos_ctrl = 0; + curr_desc = curr_desc->pnext; + work_done++; + } + + priv->desc_data[0].pnext_rx_desc = curr_desc; + + status_mask = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel(status_mask | MACREG_A2HRIC_BIT_RX_RDY, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + priv->is_rx_schedule = false; + + WLDBG_EXIT(DBG_LEVEL_4); +} + +/* PRIVATE FUNCTION DEFINITION +*/ + +static int mwl_rx_ring_alloc(struct mwl_priv *priv) +{ + WLDBG_ENTER_INFO(DBG_LEVEL_4, "allocating %i (0x%x) bytes", + MAX_NUM_RX_RING_BYTES, MAX_NUM_RX_RING_BYTES); + + BUG_ON(!priv); + + priv->desc_data[0].prx_ring = + (struct mwl_rx_desc *)pci_alloc_consistent(priv->pdev, + MAX_NUM_RX_RING_BYTES, + &priv->desc_data[0].pphys_rx_ring); + + if (priv->desc_data[0].prx_ring == NULL) { + + WLDBG_ERROR(DBG_LEVEL_4, "can not alloc mem"); + WLDBG_EXIT_INFO(DBG_LEVEL_4, "no memory"); + return -ENOMEM; + } + + memset(priv->desc_data[0].prx_ring, 0x00, MAX_NUM_RX_RING_BYTES); + + WLDBG_EXIT_INFO(DBG_LEVEL_4, "RX ring vaddr: 0x%x paddr: 0x%x", + priv->desc_data[0].prx_ring, priv->desc_data[0].pphys_rx_ring); + + return 0; +} + +static int mwl_rx_ring_init(struct mwl_priv *priv) +{ + int curr_desc; + + WLDBG_ENTER_INFO(DBG_LEVEL_4, "initializing %i descriptors", SYSADPT_MAX_NUM_RX_DESC); + + if (priv->desc_data[0].prx_ring != NULL) { + + priv->desc_data[0].rx_buf_size = SYSADPT_MAX_AGGR_SIZE; + + for (curr_desc = 0; curr_desc < SYSADPT_MAX_NUM_RX_DESC; curr_desc++) { + + CURR_RXD.psk_buff = dev_alloc_skb(priv->desc_data[0].rx_buf_size); + + if (skb_linearize(CURR_RXD.psk_buff)) { + + dev_kfree_skb_any(CURR_RXD.psk_buff); + WLDBG_ERROR(DBG_LEVEL_4, "need linearize memory"); + WLDBG_EXIT_INFO(DBG_LEVEL_4, "no suitable memory"); + return -ENOMEM; + } + + skb_reserve(CURR_RXD.psk_buff, SYSADPT_MIN_BYTES_HEADROOM); + CURR_RXD.rx_control = EAGLE_RXD_CTRL_DRIVER_OWN; + CURR_RXD.status = EAGLE_RXD_STATUS_OK; + CURR_RXD.qos_ctrl = 0x0000; + CURR_RXD.channel = 0x00; + CURR_RXD.rssi = 0x00; + CURR_RXD.sq2 = 0x00; + + if (CURR_RXD.psk_buff != NULL) { + + CURR_RXD.pkt_len = SYSADPT_MAX_AGGR_SIZE; + CURR_RXD.pbuff_data = CURR_RXD.psk_buff->data; + CURR_RXD.pphys_buff_data = + ENDIAN_SWAP32(pci_map_single(priv->pdev, + CURR_RXD.psk_buff->data, + priv->desc_data[0].rx_buf_size, + PCI_DMA_FROMDEVICE)); + CURR_RXD.pnext = &NEXT_RXD; + CURR_RXD.pphys_next = + ENDIAN_SWAP32((u32)priv->desc_data[0].pphys_rx_ring + + ((curr_desc + 1) * sizeof(struct mwl_rx_desc))); + WLDBG_INFO(DBG_LEVEL_4, + "rxdesc: %i status: 0x%x (%i) len: 0x%x (%i)", + curr_desc, EAGLE_TXD_STATUS_IDLE, EAGLE_TXD_STATUS_IDLE, + priv->desc_data[0].rx_buf_size, priv->desc_data[0].rx_buf_size); + WLDBG_INFO(DBG_LEVEL_4, + "rxdesc: %i vnext: 0x%p pnext: 0x%x", curr_desc, + CURR_RXD.pnext, ENDIAN_SWAP32(CURR_RXD.pphys_next)); + } else { + + WLDBG_ERROR(DBG_LEVEL_4, + "rxdesc %i: no skbuff available", curr_desc); + WLDBG_EXIT_INFO(DBG_LEVEL_4, "no socket buffer"); + return -ENOMEM; + } + } + LAST_RXD.pphys_next = + ENDIAN_SWAP32((u32)priv->desc_data[0].pphys_rx_ring); + LAST_RXD.pnext = &FIRST_RXD; + priv->desc_data[0].pnext_rx_desc = &FIRST_RXD; + + WLDBG_EXIT_INFO(DBG_LEVEL_4, + "last rxdesc vnext: 0x%p pnext: 0x%x vfirst 0x%x", + LAST_RXD.pnext, ENDIAN_SWAP32(LAST_RXD.pphys_next), + priv->desc_data[0].pnext_rx_desc); + + return 0; + } + + WLDBG_ERROR(DBG_LEVEL_4, "no valid RX mem"); + WLDBG_EXIT_INFO(DBG_LEVEL_4, "no valid RX mem"); + + return -ENOMEM; +} + +static void mwl_rx_ring_cleanup(struct mwl_priv *priv) +{ + int curr_desc; + + WLDBG_ENTER(DBG_LEVEL_4); + + BUG_ON(!priv); + + if (priv->desc_data[0].prx_ring != NULL) { + + for (curr_desc = 0; curr_desc < SYSADPT_MAX_NUM_RX_DESC; curr_desc++) { + + if (CURR_RXD.psk_buff != NULL) { + + if (skb_shinfo(CURR_RXD.psk_buff)->nr_frags) + skb_shinfo(CURR_RXD.psk_buff)->nr_frags = 0; + + if (skb_shinfo(CURR_RXD.psk_buff)->frag_list) + skb_shinfo(CURR_RXD.psk_buff)->frag_list = NULL; + + pci_unmap_single(priv->pdev, + ENDIAN_SWAP32(CURR_RXD.pphys_buff_data), + priv->desc_data[0].rx_buf_size, + PCI_DMA_FROMDEVICE); + + dev_kfree_skb_any(CURR_RXD.psk_buff); + + WLDBG_INFO(DBG_LEVEL_4, + "unmapped+free'd rxdesc %i vaddr: 0x%p paddr: 0x%x len: %i", + curr_desc, CURR_RXD.pbuff_data, + ENDIAN_SWAP32(CURR_RXD.pphys_buff_data), + priv->desc_data[0].rx_buf_size); + + CURR_RXD.pbuff_data = NULL; + CURR_RXD.psk_buff = NULL; + } + } + } + + WLDBG_EXIT(DBG_LEVEL_4); +} + +static void mwl_rx_ring_free(struct mwl_priv *priv) +{ + WLDBG_ENTER(DBG_LEVEL_4); + + BUG_ON(!priv); + + if (priv->desc_data[0].prx_ring != NULL) { + + mwl_rx_ring_cleanup(priv); + + pci_free_consistent(priv->pdev, + MAX_NUM_RX_RING_BYTES, + priv->desc_data[0].prx_ring, + priv->desc_data[0].pphys_rx_ring); + + priv->desc_data[0].prx_ring = NULL; + } + + priv->desc_data[0].pnext_rx_desc = NULL; + + WLDBG_EXIT(DBG_LEVEL_4); +} + +static inline void mwl_rx_prepare_status(struct mwl_rx_desc *pdesc, + struct ieee80211_rx_status *status) +{ + WLDBG_ENTER(DBG_LEVEL_4); + + BUG_ON(!pdesc); + BUG_ON(!status); + + memset(status, 0, sizeof(*status)); + + status->signal = -(pdesc->rssi + W836X_RSSI_OFFSET); + + /* TODO: rate & antenna + */ + + if (pdesc->channel > 14) + status->band = IEEE80211_BAND_5GHZ; + else + status->band = IEEE80211_BAND_2GHZ; + + status->freq = ieee80211_channel_to_frequency(pdesc->channel, + status->band); + + /* check if status has a specific error bit (bit 7)set or indicates a general decrypt error + */ + if ((pdesc->status == GENERAL_DECRYPT_ERR) || (pdesc->status & DECRYPT_ERR_MASK)) { + + /* check if status is not equal to 0xFF + * the 0xFF check is for backward compatibility + */ + if (pdesc->status != GENERAL_DECRYPT_ERR) { + + if (((pdesc->status & (~DECRYPT_ERR_MASK)) & TKIP_DECRYPT_MIC_ERR) && + !((pdesc->status & (WEP_DECRYPT_ICV_ERR | TKIP_DECRYPT_ICV_ERR)))) { + + status->flag |= RX_FLAG_MMIC_ERROR; + } + } + } + + WLDBG_EXIT(DBG_LEVEL_4); +} + +static inline struct mwl_vif *mwl_rx_find_vif_bss(struct list_head *vif_list, u8 *bssid) +{ + struct mwl_vif *mwl_vif; + + list_for_each_entry(mwl_vif, vif_list, list) { + + if (memcmp(bssid, mwl_vif->bssid, ETH_ALEN) == 0) + return mwl_vif; + } + + return NULL; +} + +static inline void mwl_rx_remove_dma_header(struct sk_buff *skb, u16 qos) +{ + struct mwl_dma_data *tr; + int hdrlen; + + tr = (struct mwl_dma_data *)skb->data; + hdrlen = ieee80211_hdrlen(tr->wh.frame_control); + + if (hdrlen != sizeof(tr->wh)) { + + if (ieee80211_is_data_qos(tr->wh.frame_control)) { + memmove(tr->data - hdrlen, &tr->wh, hdrlen - 2); + *((u16 *)(tr->data - 2)) = qos; + } else { + memmove(tr->data - hdrlen, &tr->wh, hdrlen); + } + } + + if (hdrlen != sizeof(*tr)) + skb_pull(skb, sizeof(*tr) - hdrlen); +} + +static int mwl_rx_refill(struct mwl_priv *priv, struct mwl_rx_desc *pdesc) +{ + WLDBG_ENTER(DBG_LEVEL_4); + + BUG_ON(!priv); + BUG_ON(!pdesc); + + pdesc->psk_buff = dev_alloc_skb(priv->desc_data[0].rx_buf_size); + + if (pdesc->psk_buff == NULL) + goto nomem; + + if (skb_linearize(pdesc->psk_buff)) { + + dev_kfree_skb_any(pdesc->psk_buff); + WLDBG_ERROR(DBG_LEVEL_4, "need linearize memory"); + goto nomem; + } + + skb_reserve(pdesc->psk_buff, SYSADPT_MIN_BYTES_HEADROOM); + + pdesc->status = EAGLE_RXD_STATUS_OK; + pdesc->qos_ctrl = 0x0000; + pdesc->channel = 0x00; + pdesc->rssi = 0x00; + pdesc->sq2 = 0x00; + + pdesc->pkt_len = priv->desc_data[0].rx_buf_size; + pdesc->pbuff_data = pdesc->psk_buff->data; + pdesc->pphys_buff_data = + ENDIAN_SWAP32(pci_map_single(priv->pdev, + pdesc->psk_buff->data, + priv->desc_data[0].rx_buf_size, + PCI_DMA_BIDIRECTIONAL)); + + WLDBG_EXIT(DBG_LEVEL_4); + + return 0; + +nomem: + + WLDBG_EXIT_INFO(DBG_LEVEL_4, "no memory"); + + return -ENOMEM; +} diff --git a/mwl_rx.h b/mwl_rx.h new file mode 100644 index 0000000..191280e --- /dev/null +++ b/mwl_rx.h @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file defines receive related functions. +* +*/ + +#ifndef _mwl_rx_h_ +#define _mwl_rx_h_ + +/* PUBLIC FUNCTION DECLARATION +*/ + +int mwl_rx_init(struct ieee80211_hw *hw); +void mwl_rx_deinit(struct ieee80211_hw *hw); +void mwl_rx_recv(unsigned long data); + +#endif /* _mwl_rx_h_ */ diff --git a/mwl_sysadpt.h b/mwl_sysadpt.h new file mode 100644 index 0000000..5c509e1 --- /dev/null +++ b/mwl_sysadpt.h @@ -0,0 +1,65 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file defines system adaptation related information. +* +*/ + +#ifndef _mwl_sysadpt_h_ +#define _mwl_sysadpt_h_ + +/* CONSTANTS AND MACROS +*/ + +#define SYSADPT_MAX_CARDS_SUPPORT 1 + +#define SYSADPT_MAX_NUM_CHANNELS 64 + +#define SYSADPT_MAX_DATA_RATES_G 14 + +#define SYSADPT_TX_POWER_LEVEL_TOTAL 16 + +#define SYSADPT_TX_WMM_QUEUES 4 + +#define SYSADPT_TX_AMPDU_QUEUES 4 + +#define SYSADPT_TOTAL_TX_QUEUES SYSADPT_TX_WMM_QUEUES + +#define SYSADPT_TOTAL_HW_QUEUES (SYSADPT_TX_WMM_QUEUES + SYSADPT_TX_AMPDU_QUEUES) + +#define SYSADPT_NUM_OF_DESC_DATA 4 + +#define SYSADPT_MAX_NUM_TX_DESC 256 + +#define SYSADPT_TX_QUEUE_LIMIT 1024 + +#define SYSADPT_DELAY_FREE_Q_LIMIT SYSADPT_MAX_NUM_TX_DESC + +#define SYSADPT_MAX_NUM_RX_DESC 256 + +#define SYSADPT_RECEIVE_LIMIT 64 + +#define SYSADPT_MAX_AGGR_SIZE 4096 + +#define SYSADPT_MIN_BYTES_HEADROOM 64 + +#define SYSADPT_AMPDU_PACKET_THRESHOLD 64 + +#define SYSADPT_MAX_TID 8 + +#endif /* _mwl_sysadpt_h_ */ diff --git a/mwl_tx.c b/mwl_tx.c new file mode 100644 index 0000000..4594537 --- /dev/null +++ b/mwl_tx.c @@ -0,0 +1,891 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file implements transmit related functions. +* +*/ + +#include + +#include "mwl_sysadpt.h" +#include "mwl_dev.h" +#include "mwl_debug.h" +#include "mwl_fwcmd.h" +#include "mwl_tx.h" + +/* CONSTANTS AND MACROS +*/ + +#define MAX_NUM_TX_RING_BYTES (SYSADPT_MAX_NUM_TX_DESC * sizeof(struct mwl_tx_desc)) + +#define FIRST_TXD(i) priv->desc_data[i].ptx_ring[0] +#define CURR_TXD(i) priv->desc_data[i].ptx_ring[curr_desc] +#define NEXT_TXD(i) priv->desc_data[i].ptx_ring[curr_desc + 1] +#define LAST_TXD(i) priv->desc_data[i].ptx_ring[SYSADPT_MAX_NUM_TX_DESC - 1] + +#define STALE_TXD(i) priv->desc_data[i].pstale_tx_desc + +#define EAGLE_TXD_XMITCTRL_USE_MC_RATE 0x8 /* Use multicast data rate */ + +#define MWL_QOS_ACK_POLICY_MASK 0x0060 +#define MWL_QOS_ACK_POLICY_NORMAL 0x0000 +#define MWL_QOS_ACK_POLICY_BLOCKACK 0x0060 + +#define EXT_IV 0x20 +#define INCREASE_IV(iv16, iv32) {(iv16)++; if ((iv16) == 0) (iv32)++; } + +/* PRIVATE FUNCTION DECLARATION +*/ + +static int mwl_tx_ring_alloc(struct mwl_priv *priv); +static int mwl_tx_ring_init(struct mwl_priv *priv); +static void mwl_tx_ring_cleanup(struct mwl_priv *priv); +static void mwl_tx_ring_free(struct mwl_priv *priv); +static inline void mwl_tx_add_dma_header(struct mwl_priv *priv, + struct sk_buff *skb, int head_pad, int tail_pad); +static inline void mwl_tx_encapsulate_frame(struct mwl_priv *priv, + struct sk_buff *skb, bool *ccmp); +static inline void mwl_tx_insert_ccmp_hdr(u8 *pccmp_hdr, + u8 key_id, u16 iv16, u32 iv32); +static inline int mwl_tx_tid_queue_mapping(u8 tid); +static inline void mwl_tx_count_packet(struct ieee80211_sta *sta, u8 tid); +static inline void mwl_tx_skbs(struct ieee80211_hw *hw); +/* static void mwl_tx_descriptor_dump(struct mwl_priv *priv); */ + +/* PUBLIC FUNCTION DEFINITION +*/ + +int mwl_tx_init(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int rc; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + skb_queue_head_init(&priv->delay_freeq); + + rc = mwl_tx_ring_alloc(priv); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_3, "allocating TX ring failed"); + + } else { + + rc = mwl_tx_ring_init(priv); + if (rc) { + + mwl_tx_ring_free(priv); + WLDBG_ERROR(DBG_LEVEL_3, "initializing TX ring failed"); + } + } + + WLDBG_EXIT(DBG_LEVEL_3); + + return rc; +} + +void mwl_tx_deinit(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + skb_queue_purge(&priv->delay_freeq); + + mwl_tx_ring_cleanup(priv); + mwl_tx_ring_free(priv); + + WLDBG_EXIT(DBG_LEVEL_3); +} + +void mwl_tx_xmit(struct ieee80211_hw *hw, + int index, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + struct mwl_priv *priv; + struct ieee80211_tx_info *tx_info; + struct mwl_vif *mwl_vif; + struct ieee80211_hdr *wh; + u8 xmitcontrol; + u16 qos; + int txpriority; + u8 tid = 0; + struct mwl_ampdu_stream *stream = NULL; + bool start_ba_session = false; + bool mgmtframe = false; + struct ieee80211_mgmt *mgmt; + bool eapol_frame = false; + bool ccmp = false; + struct mwl_tx_ctrl *tx_ctrl; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + BUG_ON(!skb); + + wh = (struct ieee80211_hdr *)skb->data; + + if (ieee80211_is_data_qos(wh->frame_control)) + qos = ENDIAN_SWAP16(*((u16 *)ieee80211_get_qos_ctl(wh))); + else + qos = 0; + + if (skb->protocol == cpu_to_be16(ETH_P_PAE)) { + + index = IEEE80211_AC_VO; + eapol_frame = true; + } + + if (ieee80211_is_mgmt(wh->frame_control)) { + + mgmtframe = true; + mgmt = (struct ieee80211_mgmt *)skb->data; + } + + mwl_tx_encapsulate_frame(priv, skb, &ccmp); + + wh = &((struct mwl_dma_data *)skb->data)->wh; + + tx_info = IEEE80211_SKB_CB(skb); + mwl_vif = MWL_VIF(tx_info->control.vif); + + if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + + wh->seq_ctrl &= ENDIAN_SWAP16(IEEE80211_SCTL_FRAG); + wh->seq_ctrl |= ENDIAN_SWAP16(mwl_vif->seqno); + mwl_vif->seqno += 0x10; + } + + /* Setup firmware control bit fields for each frame type. + */ + xmitcontrol = 0; + + if (mgmtframe || ieee80211_is_ctl(wh->frame_control)) { + + qos = 0; + + } else if (ieee80211_is_data(wh->frame_control)) { + + qos &= ~MWL_QOS_ACK_POLICY_MASK; + + if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) { + xmitcontrol &= 0xfb; + qos |= MWL_QOS_ACK_POLICY_BLOCKACK; + } else { + xmitcontrol |= 0x4; + qos |= MWL_QOS_ACK_POLICY_NORMAL; + } + + if (is_multicast_ether_addr(wh->addr1)) { + + xmitcontrol |= EAGLE_TXD_XMITCTRL_USE_MC_RATE; + + if (ccmp) { + + mwl_tx_insert_ccmp_hdr(((struct mwl_dma_data *)skb->data)->data, + mwl_vif->keyidx, mwl_vif->iv16, mwl_vif->iv32); + INCREASE_IV(mwl_vif->iv16, mwl_vif->iv32); + } + + } else { + + if (ccmp) { + + struct mwl_sta *sta_info = MWL_STA(sta); + + mwl_tx_insert_ccmp_hdr(((struct mwl_dma_data *)skb->data)->data, + 0, sta_info->iv16, sta_info->iv32); + INCREASE_IV(sta_info->iv16, sta_info->iv32); + } + } + } + + /* Queue ADDBA request in the respective data queue. While setting up + * the ampdu stream, mac80211 queues further packets for that + * particular ra/tid pair. However, packets piled up in the hardware + * for that ra/tid pair will still go out. ADDBA request and the + * related data packets going out from different queues asynchronously + * will cause a shift in the receiver window which might result in + * ampdu packets getting dropped at the receiver after the stream has + * been setup. + */ + if (mgmtframe) { + + if (unlikely(ieee80211_is_action(wh->frame_control) && + mgmt->u.action.category == WLAN_CATEGORY_BACK && + mgmt->u.action.u.addba_req.action_code == WLAN_ACTION_ADDBA_REQ)) { + + u16 capab = ENDIAN_SWAP16(mgmt->u.action.u.addba_req.capab); + + tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; + index = mwl_tx_tid_queue_mapping(tid); + } + } + + txpriority = SYSADPT_TX_WMM_QUEUES - index - 1; + + if (sta && sta->ht_cap.ht_supported && !eapol_frame && + ieee80211_is_data_qos(wh->frame_control)) { + + tid = qos & 0xf; + mwl_tx_count_packet(sta, tid); + + SPIN_LOCK(&priv->locks.stream_lock); + stream = mwl_fwcmd_lookup_stream(hw, sta->addr, tid); + + if (stream != NULL) { + + if (stream->state == AMPDU_STREAM_ACTIVE) { + + WARN_ON(!(qos & MWL_QOS_ACK_POLICY_BLOCKACK)); + + txpriority = (SYSADPT_TX_WMM_QUEUES + stream->idx) % + SYSADPT_TOTAL_HW_QUEUES; + + } else if (stream->state == AMPDU_STREAM_NEW) { + /* We get here if the driver sends us packets + * after we've initiated a stream, but before + * our ampdu_action routine has been called + * with IEEE80211_AMPDU_TX_START to get the SSN + * for the ADDBA request. So this packet can + * go out with no risk of sequence number + * mismatch. No special handling is required. + */ + } else { + /* Drop packets that would go out after the + * ADDBA request was sent but before the ADDBA + * response is received. If we don't do this, + * the recipient would probably receive it + * after the ADDBA request with SSN 0. This + * will cause the recipient's BA receive window + * to shift, which would cause the subsequent + * packets in the BA stream to be discarded. + * mac80211 queues our packets for us in this + * case, so this is really just a safety check. + */ + WLDBG_WARNING(DBG_LEVEL_3, + "cannot send packet while ADDBA " + "dialog is underway."); + SPIN_UNLOCK(&priv->locks.stream_lock); + dev_kfree_skb_any(skb); + return; + } + + } else { + /* Defer calling mwl8k_start_stream so that the current + * skb can go out before the ADDBA request. This + * prevents sequence number mismatch at the recepient + * as described above. + */ + if (mwl_fwcmd_ampdu_allowed(sta, tid)) { + + stream = mwl_fwcmd_add_stream(hw, sta, tid); + + if (stream != NULL) + start_ba_session = true; + } + } + + SPIN_UNLOCK(&priv->locks.stream_lock); + + } else { + + qos &= ~MWL_QOS_ACK_POLICY_MASK; + qos |= MWL_QOS_ACK_POLICY_NORMAL; + } + + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + tx_ctrl->tx_priority = txpriority; + tx_ctrl->qos_ctrl = qos; + tx_ctrl->type = (mgmtframe ? IEEE_TYPE_MANAGEMENT : IEEE_TYPE_DATA); + tx_ctrl->xmit_control = xmitcontrol; + tx_ctrl->sta_info = (u8 *)sta; + tx_ctrl->ccmp = ccmp; + + if (skb_queue_len(&priv->txq[index]) > priv->txq_limit) { + + dev_kfree_skb_any(skb); + WLDBG_INFO(DBG_LEVEL_3, "queue len > limit"); + + } else { + + skb_queue_tail(&priv->txq[index], skb); + } + + mwl_tx_skbs(hw); + + /* Initiate the ampdu session here + */ + if (start_ba_session) { + + SPIN_LOCK(&priv->locks.stream_lock); + if (mwl_fwcmd_start_stream(hw, stream)) + mwl_fwcmd_remove_stream(hw, stream); + SPIN_UNLOCK(&priv->locks.stream_lock); + } + + WLDBG_EXIT(DBG_LEVEL_3); +} + +void mwl_tx_done(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv; + unsigned long flags; + int num; + struct sk_buff *done_skb; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + SPIN_LOCK_IRQSAVE(&priv->locks.xmit_lock, flags); + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + + while (STALE_TXD(num) + && (STALE_TXD(num)->status & ENDIAN_SWAP32(EAGLE_TXD_STATUS_OK)) + && (!(STALE_TXD(num)->status & ENDIAN_SWAP32(EAGLE_TXD_STATUS_FW_OWNED)))) { + + pci_unmap_single(priv->pdev, + ENDIAN_SWAP32(STALE_TXD(num)->pkt_ptr), + STALE_TXD(num)->psk_buff->len, + PCI_DMA_TODEVICE); + done_skb = STALE_TXD(num)->psk_buff; + STALE_TXD(num)->pkt_len = 0; + STALE_TXD(num)->psk_buff = NULL; + STALE_TXD(num)->status = ENDIAN_SWAP32(EAGLE_TXD_STATUS_IDLE); + priv->fw_desc_cnt[num]--; + STALE_TXD(num) = STALE_TXD(num)->pnext; + wmb(); + + { + struct mwl_dma_data *tr; + struct ieee80211_tx_info *info; + int hdrlen; + + tr = (struct mwl_dma_data *)done_skb->data; + + if (ieee80211_is_assoc_resp(tr->wh.frame_control)) { + + /* Remove H/W dma header + */ + hdrlen = ieee80211_hdrlen(tr->wh.frame_control); + memmove(tr->data - hdrlen, &tr->wh, hdrlen); + skb_pull(done_skb, sizeof(*tr) - hdrlen); + + info = IEEE80211_SKB_CB(done_skb); + + ieee80211_tx_info_clear_status(info); + + /* Rate control is happening in the firmware. + * Ensure no tx rate is being reported. + */ + info->status.rates[0].idx = -1; + info->status.rates[0].count = 1; + + info->flags |= IEEE80211_TX_STAT_ACK; + + ieee80211_tx_status(hw, done_skb); + + } else { + + /* Due to firmware does not return real tx status, + * we should not ACK data frame, otherwise, hostap will + * think station is always existed. + */ + skb_queue_tail(&priv->delay_freeq, done_skb); + + if (skb_queue_len(&priv->delay_freeq) > SYSADPT_DELAY_FREE_Q_LIMIT) + dev_kfree_skb_any(skb_dequeue(&priv->delay_freeq)); + } + } + } + } + + SPIN_UNLOCK_IRQRESTORE(&priv->locks.xmit_lock, flags); + + if (priv->irq != -1) { + + u32 status; + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel(status | MACREG_A2HRIC_BIT_TX_DONE, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + mwl_tx_skbs(hw); + } + + priv->is_tx_schedule = false; + + WLDBG_EXIT(DBG_LEVEL_3); +} + +/* PRIVATE FUNCTION DEFINITION +*/ + +static int mwl_tx_ring_alloc(struct mwl_priv *priv) +{ + int num; + u8 *mem; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!priv); + + mem = (u8 *)pci_alloc_consistent(priv->pdev, + MAX_NUM_TX_RING_BYTES * SYSADPT_NUM_OF_DESC_DATA, + &priv->desc_data[0].pphys_tx_ring); + + if (mem == NULL) { + + WLDBG_ERROR(DBG_LEVEL_3, "can not alloc mem"); + WLDBG_EXIT_INFO(DBG_LEVEL_3, "no memory"); + return -ENOMEM; + } + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + + WLDBG_INFO(DBG_LEVEL_3, "allocating %i (0x%x) bytes", + MAX_NUM_TX_RING_BYTES, MAX_NUM_TX_RING_BYTES); + + priv->desc_data[num].ptx_ring = + (struct mwl_tx_desc *) (mem + num * MAX_NUM_TX_RING_BYTES); + + priv->desc_data[num].pphys_tx_ring = + (dma_addr_t)((u32)priv->desc_data[0].pphys_tx_ring + num * MAX_NUM_TX_RING_BYTES); + + memset(priv->desc_data[num].ptx_ring, 0x00, MAX_NUM_TX_RING_BYTES); + + WLDBG_INFO(DBG_LEVEL_3, "TX ring vaddr: 0x%x paddr: 0x%x", + priv->desc_data[num].ptx_ring, priv->desc_data[num].pphys_tx_ring); + } + + WLDBG_EXIT(DBG_LEVEL_3); + + return 0; +} + +static int mwl_tx_ring_init(struct mwl_priv *priv) +{ + int curr_desc; + int num; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!priv); + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + + skb_queue_head_init(&priv->txq[num]); + priv->fw_desc_cnt[num] = 0; + + if (priv->desc_data[num].ptx_ring != NULL) { + + WLDBG_INFO(DBG_LEVEL_3, "initializing %i descriptors", SYSADPT_MAX_NUM_TX_DESC); + + for (curr_desc = 0; curr_desc < SYSADPT_MAX_NUM_TX_DESC; curr_desc++) { + + CURR_TXD(num).status = ENDIAN_SWAP32(EAGLE_TXD_STATUS_IDLE); + CURR_TXD(num).pnext = &NEXT_TXD(num); + CURR_TXD(num).pphys_next = + ENDIAN_SWAP32((u32)priv->desc_data[num].pphys_tx_ring + + ((curr_desc + 1) * sizeof(struct mwl_tx_desc))); + WLDBG_INFO(DBG_LEVEL_3, + "txdesc: %i status: 0x%x (%i) vnext: 0x%p pnext: 0x%x", + curr_desc, EAGLE_TXD_STATUS_IDLE, EAGLE_TXD_STATUS_IDLE, + CURR_TXD(num).pnext, ENDIAN_SWAP32(CURR_TXD(num).pphys_next)); + } + LAST_TXD(num).pnext = &FIRST_TXD(num); + LAST_TXD(num).pphys_next = + ENDIAN_SWAP32((u32)priv->desc_data[num].pphys_tx_ring); + priv->desc_data[num].pstale_tx_desc = &FIRST_TXD(num); + priv->desc_data[num].pnext_tx_desc = &FIRST_TXD(num); + + WLDBG_INFO(DBG_LEVEL_3, + "last txdesc vnext: 0x%p pnext: 0x%x pstale 0x%x vfirst 0x%x", + LAST_TXD(num).pnext, ENDIAN_SWAP32(LAST_TXD(num).pphys_next), + priv->desc_data[num].pstale_tx_desc, priv->desc_data[num].pnext_tx_desc); + } else + { + WLDBG_ERROR(DBG_LEVEL_3, "no valid TX mem"); + WLDBG_EXIT_INFO(DBG_LEVEL_3, "no valid memory"); + return -ENOMEM; + } + } + + WLDBG_EXIT(DBG_LEVEL_3); + + return 0; +} + +static void mwl_tx_ring_cleanup(struct mwl_priv *priv) +{ + int cleaned_tx_desc = 0; + int curr_desc; + int num; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!priv); + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + + skb_queue_purge(&priv->txq[num]); + priv->fw_desc_cnt[num] = 0; + if (priv->desc_data[num].ptx_ring != NULL) { + + for (curr_desc = 0; curr_desc < SYSADPT_MAX_NUM_TX_DESC; curr_desc++) { + + if (CURR_TXD(num).psk_buff != NULL) { + + WLDBG_INFO(DBG_LEVEL_3, + "unmapped and free'd txdesc %i vaddr: 0x%p paddr: 0x%x", + curr_desc, CURR_TXD(num).psk_buff->data, + ENDIAN_SWAP32(CURR_TXD(num).pkt_ptr)); + pci_unmap_single(priv->pdev, + ENDIAN_SWAP32(CURR_TXD(num).pkt_ptr), + CURR_TXD(num).psk_buff->len, + PCI_DMA_TODEVICE); + dev_kfree_skb_any(CURR_TXD(num).psk_buff); + CURR_TXD(num).status = ENDIAN_SWAP32(EAGLE_TXD_STATUS_IDLE); + CURR_TXD(num).psk_buff = NULL; + CURR_TXD(num).pkt_ptr = 0; + CURR_TXD(num).pkt_len = 0; + cleaned_tx_desc++; + } + } + } + } + + WLDBG_EXIT_INFO(DBG_LEVEL_3, "cleaned %i TX descr", cleaned_tx_desc); +} + +static void mwl_tx_ring_free(struct mwl_priv *priv) +{ + int num; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!priv); + + if (priv->desc_data[0].ptx_ring != NULL) { + + pci_free_consistent(priv->pdev, + MAX_NUM_TX_RING_BYTES * SYSADPT_NUM_OF_DESC_DATA, + priv->desc_data[0].ptx_ring, + priv->desc_data[0].pphys_tx_ring); + } + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + + if (priv->desc_data[num].ptx_ring != NULL) + priv->desc_data[num].ptx_ring = NULL; + priv->desc_data[num].pstale_tx_desc = NULL; + priv->desc_data[num].pnext_tx_desc = NULL; + } + + WLDBG_EXIT(DBG_LEVEL_3); +} + +static inline void mwl_tx_add_dma_header(struct mwl_priv *priv, + struct sk_buff *skb, int head_pad, int tail_pad) +{ + struct ieee80211_hdr *wh; + int hdrlen; + int reqd_hdrlen; + struct mwl_dma_data *tr; + + /* + * Add a firmware DMA header; the firmware requires that we + * present a 2-byte payload length followed by a 4-address + * header (without QoS field), followed (optionally) by any + * WEP/ExtIV header (but only filled in for CCMP). + */ + wh = (struct ieee80211_hdr *)skb->data; + + hdrlen = ieee80211_hdrlen(wh->frame_control); + + reqd_hdrlen = sizeof(*tr) + head_pad; + + if (hdrlen != reqd_hdrlen) + skb_push(skb, reqd_hdrlen - hdrlen); + + if (ieee80211_is_data_qos(wh->frame_control)) + hdrlen -= IEEE80211_QOS_CTL_LEN; + + tr = (struct mwl_dma_data *)skb->data; + + if (wh != &tr->wh) + memmove(&tr->wh, wh, hdrlen); + + if (hdrlen != sizeof(tr->wh)) + memset(((void *)&tr->wh) + hdrlen, 0, sizeof(tr->wh) - hdrlen); + + /* + * Firmware length is the length of the fully formed "802.11 + * payload". That is, everything except for the 802.11 header. + * This includes all crypto material including the MIC. + */ + tr->fwlen = ENDIAN_SWAP16(skb->len - sizeof(*tr) + tail_pad); +} + +static inline void mwl_tx_encapsulate_frame(struct mwl_priv *priv, + struct sk_buff *skb, bool *ccmp) +{ + struct ieee80211_hdr *wh; + struct ieee80211_tx_info *tx_info; + struct ieee80211_key_conf *key_conf; + int data_pad; + int head_pad = 0; + + wh = (struct ieee80211_hdr *)skb->data; + + tx_info = IEEE80211_SKB_CB(skb); + + key_conf = NULL; + + if (ieee80211_is_data(wh->frame_control)) + key_conf = tx_info->control.hw_key; + + /* + * Make sure the packet header is in the DMA header format (4-address + * without QoS), and add head & tail padding when HW crypto is enabled. + * + * We have the following trailer padding requirements: + * - WEP: 4 trailer bytes (ICV) + * - TKIP: 12 trailer bytes (8 MIC + 4 ICV) + * - CCMP: 8 trailer bytes (MIC) + */ + data_pad = 0; + + if (key_conf != NULL) { + + head_pad = key_conf->iv_len; + + switch (key_conf->cipher) { + + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + data_pad = 4; + break; + + case WLAN_CIPHER_SUITE_TKIP: + data_pad = 12; + break; + + case WLAN_CIPHER_SUITE_CCMP: + data_pad = 8; + *ccmp = true; + break; + } + } + + mwl_tx_add_dma_header(priv, skb, head_pad, data_pad); +} + +static inline void mwl_tx_insert_ccmp_hdr(u8 *pccmp_hdr, + u8 key_id, u16 iv16, u32 iv32) +{ + *((u16 *)pccmp_hdr) = iv16; + pccmp_hdr[2] = 0; + pccmp_hdr[3] = EXT_IV | (key_id << 6); + *((u32 *)&pccmp_hdr[4]) = iv32; +} + +static inline int mwl_tx_tid_queue_mapping(u8 tid) +{ + BUG_ON(tid > 7); + + switch (tid) { + case 0: + case 3: + return IEEE80211_AC_BE; + break; + case 1: + case 2: + return IEEE80211_AC_BK; + break; + case 4: + case 5: + return IEEE80211_AC_VI; + break; + case 6: + case 7: + return IEEE80211_AC_VO; + break; + default: + return -1; + break; + } +} + +static inline void mwl_tx_count_packet(struct ieee80211_sta *sta, u8 tid) +{ + struct mwl_sta *sta_info = MWL_STA(sta); + struct mwl_tx_info *tx_stats; + + BUG_ON(!sta); + sta_info = MWL_STA(sta); + BUG_ON(!sta_info); + + BUG_ON(tid >= SYSADPT_MAX_TID); + + tx_stats = &sta_info->tx_stats[tid]; + + if (tx_stats->start_time == 0) + tx_stats->start_time = jiffies; + + /* reset the packet count after each second elapses. If the number of + * packets ever exceeds the ampdu_min_traffic threshold, we will allow + * an ampdu stream to be started. + */ + if (jiffies - tx_stats->start_time > HZ) { + + tx_stats->pkts = 0; + tx_stats->start_time = 0; + + } else + tx_stats->pkts++; +} + +static inline void mwl_tx_skbs(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + unsigned long flags; + int num = SYSADPT_NUM_OF_DESC_DATA; + struct sk_buff *tx_skb; + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + SPIN_LOCK_IRQSAVE(&priv->locks.xmit_lock, flags); + + while (num--) { + + while (skb_queue_len(&priv->txq[num]) > 0) { + + if (priv->desc_data[num].pnext_tx_desc == NULL) + break; + + /* Only queue to tx desc when Status is 0 (not when 0x1 or 0x80000000). If we queue even when Status==0x1 + * (DMA'd to fw but txdone haven't change Status to 0), mismatch of fwDescCnt with actual number of desc with Status==0 + * could happen. E.g fwDescCnt 256 instead of 255 when there is one desc with Status==0. This can cause Tx to stall + * when fwDescCnt==256 and pStaleTxDesc->Status==0. + */ + if (priv->desc_data[num].pnext_tx_desc->status != EAGLE_TXD_STATUS_IDLE) { + + /* Interrupt F/W anyway + */ + if (priv->desc_data[num].pnext_tx_desc->status & ENDIAN_SWAP32(EAGLE_TXD_STATUS_FW_OWNED)) + writel(MACREG_H2ARIC_BIT_PPA_READY, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); + + break; + + } + + tx_skb = skb_dequeue(&priv->txq[num]); + + BUG_ON(!tx_skb); + + tx_info = IEEE80211_SKB_CB(tx_skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + + priv->desc_data[num].pnext_tx_desc->tx_priority = tx_ctrl->tx_priority; + priv->desc_data[num].pnext_tx_desc->qos_ctrl = tx_ctrl->qos_ctrl; + priv->desc_data[num].pnext_tx_desc->psk_buff = tx_skb; + priv->desc_data[num].pnext_tx_desc->pkt_len = ENDIAN_SWAP16(tx_skb->len); + priv->desc_data[num].pnext_tx_desc->ack_wcb_addr = 0; + priv->desc_data[num].pnext_tx_desc->data_rate = 0; + priv->desc_data[num].pnext_tx_desc->sta_info = tx_ctrl->sta_info; + priv->desc_data[num].pnext_tx_desc->type = tx_ctrl->type; + priv->desc_data[num].pnext_tx_desc->xmit_control = tx_ctrl->xmit_control; + priv->desc_data[num].pnext_tx_desc->sap_pkt_info = 0; + priv->desc_data[num].pnext_tx_desc->pkt_ptr = + ENDIAN_SWAP32(pci_map_single(priv->pdev, tx_skb->data, + tx_skb->len, PCI_DMA_TODEVICE)); + priv->desc_data[num].pnext_tx_desc->status = ENDIAN_SWAP32(EAGLE_TXD_STATUS_FW_OWNED); + priv->desc_data[num].pnext_tx_desc = priv->desc_data[num].pnext_tx_desc->pnext; + /* make sure all the memory transactions done by cpu were completed */ + wmb(); /*Data Memory Barrier*/ + writel(MACREG_H2ARIC_BIT_PPA_READY, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); + priv->fw_desc_cnt[num]++; + } + } + + SPIN_UNLOCK_IRQRESTORE(&priv->locks.xmit_lock, flags); + + WLDBG_EXIT(DBG_LEVEL_3); +} + +#if 0 +static void mwl_tx_descriptor_dump(struct mwl_priv *priv) +{ + int curr_desc; + int num; + char *p1 = NULL; + char *p2 = NULL; + char str1[12] = " <- CURR_TXD"; + char str2[14] = " <- NEXT_TXD"; + char blank[2] = " "; + + WLDBG_ENTER(DBG_LEVEL_3); + + BUG_ON(!priv); + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + + if (priv->desc_data[num].ptx_ring != NULL) { + + for (curr_desc = 0; curr_desc < SYSADPT_MAX_NUM_TX_DESC; curr_desc++) { + + p1 = blank; + p2 = blank; + if ((u32)&CURR_TXD(num) == (u32)priv->desc_data[num].pstale_tx_desc) + p1 = str1; + if ((u32)&CURR_TXD(num) == (u32)priv->desc_data[num].pnext_tx_desc) + p2 = str2; + WLDBG_PRINT("TxDescriptor(%d.%d) Status=0x%x %s %s", num, curr_desc, CURR_TXD(num).status, p1, p2); + } + } + } + + WLDBG_EXIT(DBG_LEVEL_3); +} +#endif diff --git a/mwl_tx.h b/mwl_tx.h new file mode 100644 index 0000000..5cd3a97 --- /dev/null +++ b/mwl_tx.h @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file defines transmit related functions. +* +*/ + +#ifndef _mwl_tx_h_ +#define _mwl_tx_h_ + +/* PUBLIC FUNCTION DECLARATION +*/ + +int mwl_tx_init(struct ieee80211_hw *hw); +void mwl_tx_deinit(struct ieee80211_hw *hw); +void mwl_tx_xmit(struct ieee80211_hw *hw, + int index, + struct ieee80211_sta *sta, + struct sk_buff *skb); +void mwl_tx_done(unsigned long data); + +#endif /* _mwl_tx_h_ */ diff --git a/test/README b/test/README new file mode 100644 index 0000000..a59a2c4 --- /dev/null +++ b/test/README @@ -0,0 +1,78 @@ +1. Files for test: + + a. 88W8864.bin: F/W binary. Please put this file under directory '/lib/firmware'. + b. Mamba_FCC_v1.2_5G4TX.ini: power table for Mamba. + c. mwlwifi.ko: 88W8864 mac80211 driver kernel module. + d. hostapd.conf: configuration file used to configure hostapd. + e. openwrt-mvebu-jffs2-128k.img: OpenWRT download image (built with kernel cfg80211/mac80211, iw and hostapd). + +2. Bring up driver: + + a. iw reg set US + --> set regulatory domain (Note: cfg80211 will disable DFS channels). + b. insmod mwlwifi.ko fw_name=88W8864.bin pwr_tbl=Mamba_FCC_v1.2_5G4TX.ini + --> After module is inserted, physical interfaces phy0 and phy1 and network device wlan0 and wlan1 + will be created. You can issue "iw dev" to check them. + c. hostapd -B ./hostapd.conf + --> There are few sample configuration files under this directory. Please use them to do test. + d. brctl addif br-lan wlan1 + --> Make sure wlan1 is the interface specified in hostapd.conf. + + Note: you can run script file 'setup.sh' instead of issuing these commands one by one. + +3. Change configuration: + + After hostapd and driver is running, the way to change configuration: + 1. Change configuration file hostapd.conf + 2. Run "killall hostapd" then "hostapd -B ./hostapd.conf" to run hostapd with new configuration. + +4. The way to change channel via hostpad.conf for 80 MHz: + + a. You must check regulatory domain first to see if the channel you setting is allowable for 80 MHz. + You can use ˇ§iw reg getˇ¨ and ˇ§iw reg setˇ¨ to get and set regulatory domain. For script file ˇ§setup.shˇ¨, + it sets the regulatory domain to US. + If you use ˇ§iw reg getˇ¨ to check setting regulatory domain: + country US: DFS-UNSET + (2402 - 2472 @ 40), (30, 0) + (5170 - 5250 @ 80), (17, 0) + (5250 - 5330 @ 80), (23, 0) + (5735 - 5835 @ 80), (30, 0) + (57240 - 63720 @ 2160), (40, 0) + It allows you to use three ranges to do 80 MHz operation (36 ~ 48), (52 ~ 64) and (149 ~ 161). + If you use ˇ§iw reg set TWˇ¨ to set regulatory domain to TW: + country TW: DFS-UNSET + (2402 - 2472 @ 40), (30, 0) + (5270 - 5330 @ 40), (17, 0) + (5490 - 5590 @ 80), (30, 0) + (5650 - 5710 @ 40), (30, 0) + (5735 - 5835 @ 80), (30, 0) + It allows you set use two ranges to do 80 MHz operation (100 ~ 116) and (149 ~ 161). + + b. After regulatory is set and make sure the channel range you want to use is allowable by the + setting regulatory, you can modify hostapd.conf to run 80 Mhz as: + + channel=36 -> decide active primary channel. + ht_capab=[LDPC][HT40+][SHORT-GI-20][SHORT-GI-40] -> decide primary 40 MHz. + vht_oper_centr_freq_seg0_idx=42 -> center channel for this 80 MHz. + + Test sample: + + a. 149 to 161: + channel=149 + ht_capab=[LDPC][HT40+][SHORT-GI-20][SHORT-GI-40] + vht_oper_centr_freq_seg0_idx=155 + + channel=153 + ht_capab=[LDPC][HT40-][SHORT-GI-20][SHORT-GI-40] + vht_oper_centr_freq_seg0_idx=155 + + b. 100 to 112: + Use ˇ§iw reg set TWˇ¨ to change regulatory domain to allow to use this range for 80 Mhz. + channel=100 + ht_capab=[LDPC][HT40+][SHORT-GI-20][SHORT-GI-40] + vht_oper_centr_freq_seg0_idx=106 + + channel=104 + ht_capab=[LDPC][HT40-][SHORT-GI-20][SHORT-GI-40] + vht_oper_centr_freq_seg0_idx=106 + diff --git a/test/hostapd.conf.1.multi_bssid b/test/hostapd.conf.1.multi_bssid new file mode 100644 index 0000000..a2845b3 --- /dev/null +++ b/test/hostapd.conf.1.multi_bssid @@ -0,0 +1,20 @@ +interface=wlan0 +driver=nl80211 +ctrl_interface=/var/run/hostapd +ssid=mwlwifi_ap_test +ignore_broadcast_ssid=0 +hw_mode=g +channel=1 +auth_algs=1 +wmm_enabled=1 +ieee80211n=1 +ht_capab=[LDPC][SHORT-GI-20][SHORT-GI-40] +ieee80211ac=1 +vht_capab=[RXLDPC][SHORT-GI-80][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE][MAX-A-MPDU-LEN-EXP7][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] +bss=wlan0_0 +ssid=mwlwifi_ap_tset2 +wpa=2 +wpa_passphrase=12345678 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=CCMP + diff --git a/test/hostapd.conf.1.open b/test/hostapd.conf.1.open new file mode 100644 index 0000000..562b111 --- /dev/null +++ b/test/hostapd.conf.1.open @@ -0,0 +1,14 @@ +interface=wlan0 +driver=nl80211 +ctrl_interface=/var/run/hostapd +ssid=mwlwifi_ap_test +ignore_broadcast_ssid=0 +hw_mode=g +channel=1 +auth_algs=1 +wmm_enabled=1 +ieee80211n=1 +ht_capab=[LDPC][SHORT-GI-20][SHORT-GI-40] +ieee80211ac=1 +vht_capab=[RXLDPC][SHORT-GI-80][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE][MAX-A-MPDU-LEN-EXP7][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] + diff --git a/test/hostapd.conf.36.multi_bssid b/test/hostapd.conf.36.multi_bssid new file mode 100644 index 0000000..da0a5fe --- /dev/null +++ b/test/hostapd.conf.36.multi_bssid @@ -0,0 +1,20 @@ +interface=wlan1 +driver=nl80211 +ctrl_interface=/var/run/hostapd +ssid=mwlwifi_ap_test +ignore_broadcast_ssid=0 +hw_mode=a +channel=36 +auth_algs=1 +wmm_enabled=1 +ieee80211n=1 +ht_capab=[LDPC][SHORT-GI-20][SHORT-GI-40] +ieee80211ac=1 +vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE][MAX-A-MPDU-LEN-EXP7][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] +bss=wlan1_0 +ssid=mwlwifi_ap_tset2 +wpa=2 +wpa_passphrase=12345678 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=CCMP + diff --git a/test/hostapd.conf.36.open b/test/hostapd.conf.36.open new file mode 100644 index 0000000..08cd7f8 --- /dev/null +++ b/test/hostapd.conf.36.open @@ -0,0 +1,14 @@ +interface=wlan1 +driver=nl80211 +ctrl_interface=/var/run/hostapd +ssid=mwlwifi_ap_test +ignore_broadcast_ssid=0 +hw_mode=a +channel=36 +auth_algs=1 +wmm_enabled=1 +ieee80211n=1 +ht_capab=[LDPC][SHORT-GI-20][SHORT-GI-40] +ieee80211ac=1 +vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE][MAX-A-MPDU-LEN-EXP7][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] + diff --git a/test/hostapd.conf.36.open.80mhz b/test/hostapd.conf.36.open.80mhz new file mode 100644 index 0000000..8310bba --- /dev/null +++ b/test/hostapd.conf.36.open.80mhz @@ -0,0 +1,16 @@ +interface=wlan1 +driver=nl80211 +ctrl_interface=/var/run/hostapd +ssid=mwlwifi_ap_test +ignore_broadcast_ssid=0 +hw_mode=a +channel=36 +auth_algs=1 +wmm_enabled=1 +ieee80211n=1 +ht_capab=[LDPC][HT40+][SHORT-GI-20][SHORT-GI-40] +ieee80211ac=1 +vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE][MAX-A-MPDU-LEN-EXP7][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] +vht_oper_chwidth=1 +vht_oper_centr_freq_seg0_idx=42 + diff --git a/test/hostapd.conf.36.wpa2pskaes b/test/hostapd.conf.36.wpa2pskaes new file mode 100644 index 0000000..4bf4061 --- /dev/null +++ b/test/hostapd.conf.36.wpa2pskaes @@ -0,0 +1,18 @@ +interface=wlan1 +driver=nl80211 +ctrl_interface=/var/run/hostapd +ssid=mwlwifi_ap_test +ignore_broadcast_ssid=0 +hw_mode=a +channel=36 +auth_algs=1 +wpa=2 +wpa_passphrase=12345678 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=CCMP +wmm_enabled=1 +ieee80211n=1 +ht_capab=[LDPC][SHORT-GI-20][SHORT-GI-40] +ieee80211ac=1 +vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE][MAX-A-MPDU-LEN-EXP7][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] + diff --git a/test/hostapd.conf.36.wpa2pskaes.80mhz b/test/hostapd.conf.36.wpa2pskaes.80mhz new file mode 100644 index 0000000..5ab66a7 --- /dev/null +++ b/test/hostapd.conf.36.wpa2pskaes.80mhz @@ -0,0 +1,21 @@ +interface=wlan1 +driver=nl80211 +ctrl_interface=/var/run/hostapd +ssid=mwlwifi_ap_test +ignore_broadcast_ssid=0 +hw_mode=a +channel=36 +auth_algs=1 +wpa=2 +wpa_passphrase=12345678 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=CCMP +wmm_enabled=1 +ieee80211n=1 +ht_capab=[LDPC][HT40+][SHORT-GI-20][SHORT-GI-40] +ieee80211ac=1 +vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE][MAX-A-MPDU-LEN-EXP7][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] +vht_oper_chwidth=1 +vht_oper_centr_freq_seg0_idx=42 + + diff --git a/test/setup.sh b/test/setup.sh new file mode 100644 index 0000000..142ec83 --- /dev/null +++ b/test/setup.sh @@ -0,0 +1,4 @@ +iw reg set US +insmod mwlwifi.ko fw_name=88W8864.bin pwr_tbl=Mamba_FCC_v1.2_5G4TX.ini +hostapd -B ./hostapd.conf +brctl addif br-lan wlan1 diff --git a/test/setup_multi_bssid.sh b/test/setup_multi_bssid.sh new file mode 100644 index 0000000..3e76607 --- /dev/null +++ b/test/setup_multi_bssid.sh @@ -0,0 +1,5 @@ +iw reg set US +insmod mwlwifi.ko fw_name=88W8864.bin pwr_tbl=Mamba_FCC_v1.2_5G4TX.ini +hostapd -B ./hostapd.conf +brctl addif br-lan wlan1 +brctl addif br-lan wlan1_0 -- cgit v1.2.3